]> git.notmuchmail.org Git - notmuch/commitdiff
notmuch (0.28.2-1) unstable; urgency=medium
authorDavid Bremner <bremner@debian.org>
Sun, 17 Feb 2019 11:30:33 +0000 (07:30 -0400)
committerDavid Bremner <bremner@debian.org>
Sun, 17 Feb 2019 11:30:33 +0000 (07:30 -0400)
  * [notmuch-emacs] Invoke gpg from with --batch and --no-tty

[dgit import unpatched notmuch 0.28.2-1]

802 files changed:
.dir-locals.el [new file with mode: 0644]
.gitignore
.mailmap [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
COPYING-GPL-3 [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.global [new file with mode: 0644]
Makefile.local [new file with mode: 0644]
NEWS
README [new file with mode: 0644]
README.rst [new file with mode: 0644]
bindings/Makefile [new file with mode: 0644]
bindings/Makefile.local [new file with mode: 0644]
bindings/python/.gitignore [new file with mode: 0644]
bindings/python/MANIFEST.in [new file with mode: 0644]
bindings/python/README [new file with mode: 0644]
bindings/python/docs/COPYING [new file with mode: 0644]
bindings/python/docs/Makefile [new file with mode: 0644]
bindings/python/docs/source/conf.py [new file with mode: 0644]
bindings/python/docs/source/database.rst [new file with mode: 0644]
bindings/python/docs/source/filesystem.rst [new file with mode: 0644]
bindings/python/docs/source/index.rst [new file with mode: 0644]
bindings/python/docs/source/message.rst [new file with mode: 0644]
bindings/python/docs/source/messages.rst [new file with mode: 0644]
bindings/python/docs/source/notes.rst [new file with mode: 0644]
bindings/python/docs/source/query.rst [new file with mode: 0644]
bindings/python/docs/source/quickstart.rst [new file with mode: 0644]
bindings/python/docs/source/status_and_errors.rst [new file with mode: 0644]
bindings/python/docs/source/tags.rst [new file with mode: 0644]
bindings/python/docs/source/thread.rst [new file with mode: 0644]
bindings/python/docs/source/threads.rst [new file with mode: 0644]
bindings/python/notmuch/__init__.py [new file with mode: 0644]
bindings/python/notmuch/compat.py [new file with mode: 0644]
bindings/python/notmuch/database.py [new file with mode: 0644]
bindings/python/notmuch/directory.py [new file with mode: 0644]
bindings/python/notmuch/errors.py [new file with mode: 0644]
bindings/python/notmuch/filenames.py [new file with mode: 0644]
bindings/python/notmuch/globals.py [new file with mode: 0644]
bindings/python/notmuch/message.py [new file with mode: 0644]
bindings/python/notmuch/messages.py [new file with mode: 0644]
bindings/python/notmuch/query.py [new file with mode: 0644]
bindings/python/notmuch/tag.py [new file with mode: 0644]
bindings/python/notmuch/thread.py [new file with mode: 0644]
bindings/python/notmuch/threads.py [new file with mode: 0644]
bindings/python/notmuch/version.py [new file with mode: 0644]
bindings/python/setup.py [new file with mode: 0644]
bindings/ruby/.gitignore [new file with mode: 0644]
bindings/ruby/README [new file with mode: 0644]
bindings/ruby/database.c [new file with mode: 0644]
bindings/ruby/defs.h [new file with mode: 0644]
bindings/ruby/directory.c [new file with mode: 0644]
bindings/ruby/extconf.rb [new file with mode: 0644]
bindings/ruby/filenames.c [new file with mode: 0644]
bindings/ruby/init.c [new file with mode: 0644]
bindings/ruby/message.c [new file with mode: 0644]
bindings/ruby/messages.c [new file with mode: 0644]
bindings/ruby/query.c [new file with mode: 0644]
bindings/ruby/rdoc.sh [new file with mode: 0755]
bindings/ruby/status.c [new file with mode: 0644]
bindings/ruby/tags.c [new file with mode: 0644]
bindings/ruby/thread.c [new file with mode: 0644]
bindings/ruby/threads.c [new file with mode: 0644]
changelog [deleted file]
command-line-arguments.c [new file with mode: 0644]
command-line-arguments.h [new file with mode: 0644]
compat [deleted file]
compat/.gitignore [new file with mode: 0644]
compat/Makefile [new file with mode: 0644]
compat/Makefile.local [new file with mode: 0644]
compat/README [new file with mode: 0644]
compat/canonicalize_file_name.c [new file with mode: 0644]
compat/check_asctime.c [new file with mode: 0644]
compat/check_getpwuid.c [new file with mode: 0644]
compat/compat.h [new file with mode: 0644]
compat/function-attributes.h [new file with mode: 0644]
compat/gen_zlib_pc.c [new file with mode: 0644]
compat/getdelim.c [new file with mode: 0644]
compat/getline.c [new file with mode: 0644]
compat/have_canonicalize_file_name.c [new file with mode: 0644]
compat/have_d_type.c [new file with mode: 0644]
compat/have_getline.c [new file with mode: 0644]
compat/have_strcasestr.c [new file with mode: 0644]
compat/have_strsep.c [new file with mode: 0644]
compat/have_timegm.c [new file with mode: 0644]
compat/strcasestr.c [new file with mode: 0644]
compat/strsep.c [new file with mode: 0644]
compat/timegm.c [new file with mode: 0644]
completion/Makefile [new file with mode: 0644]
completion/Makefile.local [new file with mode: 0644]
completion/README [new file with mode: 0644]
completion/notmuch-completion.bash [new file with mode: 0644]
completion/zsh/_email-notmuch [new file with mode: 0644]
completion/zsh/_notmuch [new file with mode: 0644]
configure [new file with mode: 0755]
contrib/go/.gitignore [new file with mode: 0644]
contrib/go/LICENSE [new file with mode: 0644]
contrib/go/Makefile [new file with mode: 0644]
contrib/go/README [new file with mode: 0644]
contrib/go/src/notmuch-addrlookup/addrlookup.go [new file with mode: 0644]
contrib/go/src/notmuch/notmuch.go [new file with mode: 0644]
contrib/notmuch-mutt/.gitignore [new file with mode: 0644]
contrib/notmuch-mutt/Makefile [new file with mode: 0644]
contrib/notmuch-mutt/README [new file with mode: 0644]
contrib/notmuch-mutt/notmuch-mutt [new file with mode: 0755]
contrib/notmuch-mutt/notmuch-mutt.rc [new file with mode: 0644]
control [deleted file]
copyright [deleted file]
debian/.gitignore [new file with mode: 0644]
debian/NEWS [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/elpa-notmuch.elpa [new file with mode: 0644]
debian/elpa-test [new file with mode: 0644]
debian/gbp.conf [new file with mode: 0644]
debian/libnotmuch-dev.install [new file with mode: 0644]
debian/libnotmuch5.install [new file with mode: 0644]
debian/libnotmuch5.symbols [new file with mode: 0644]
debian/notmuch-emacs.README.Debian [new file with mode: 0644]
debian/notmuch-emacs.maintscript [new file with mode: 0644]
debian/notmuch-mutt.docs [new file with mode: 0644]
debian/notmuch-mutt.install [new file with mode: 0644]
debian/notmuch-mutt.manpages [new file with mode: 0644]
debian/notmuch-vim.README.Debian [new file with mode: 0644]
debian/notmuch-vim.dirs [new file with mode: 0644]
debian/notmuch-vim.docs [new file with mode: 0644]
debian/notmuch-vim.install [new file with mode: 0644]
debian/notmuch.dirs [new file with mode: 0644]
debian/notmuch.docs [new file with mode: 0644]
debian/notmuch.install [new file with mode: 0644]
debian/notmuch.maintscript [new file with mode: 0644]
debian/notmuch.manpages [new file with mode: 0644]
debian/python-notmuch.install [new file with mode: 0644]
debian/python3-notmuch.install [new file with mode: 0644]
debian/ruby-notmuch.install [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
debian/source/options [new file with mode: 0644]
debugger.c [new file with mode: 0644]
devel/RELEASING [new file with mode: 0644]
devel/STYLE [new file with mode: 0644]
devel/TODO [new file with mode: 0644]
devel/check-out-of-tree-build.sh [new file with mode: 0755]
devel/emacs-keybindings.org [new file with mode: 0644]
devel/gen-testdb.sh [new file with mode: 0755]
devel/man-to-mdwn.pl [new file with mode: 0755]
devel/news2wiki.pl [new file with mode: 0755]
devel/nmbug/doc/.gitignore [new file with mode: 0644]
devel/nmbug/doc/Makefile [new file with mode: 0644]
devel/nmbug/doc/conf.py [new file with mode: 0644]
devel/nmbug/doc/index.rst [new file with mode: 0644]
devel/nmbug/doc/man1/notmuch-report.1.rst [new file with mode: 0644]
devel/nmbug/doc/man5/notmuch-report.json.5.rst [new file with mode: 0644]
devel/nmbug/nmbug [new file with mode: 0755]
devel/nmbug/notmuch-report [new file with mode: 0755]
devel/nmbug/notmuch-report.json [new file with mode: 0644]
devel/printmimestructure [new file with mode: 0755]
devel/release-checks.sh [new file with mode: 0755]
devel/schemata [new file with mode: 0644]
devel/try-emacs-mua [new file with mode: 0755]
devel/uncrustify.cfg [new file with mode: 0644]
doc/.gitignore [new file with mode: 0644]
doc/INSTALL [new file with mode: 0644]
doc/Makefile [new file with mode: 0644]
doc/Makefile.local [new file with mode: 0644]
doc/conf.py [new file with mode: 0644]
doc/doxygen.cfg [new file with mode: 0644]
doc/index.rst [new file with mode: 0644]
doc/man1/notmuch-address.rst [new file with mode: 0644]
doc/man1/notmuch-compact.rst [new file with mode: 0644]
doc/man1/notmuch-config.rst [new file with mode: 0644]
doc/man1/notmuch-count.rst [new file with mode: 0644]
doc/man1/notmuch-dump.rst [new file with mode: 0644]
doc/man1/notmuch-emacs-mua.rst [new file with mode: 0644]
doc/man1/notmuch-insert.rst [new file with mode: 0644]
doc/man1/notmuch-new.rst [new file with mode: 0644]
doc/man1/notmuch-reindex.rst [new file with mode: 0644]
doc/man1/notmuch-reply.rst [new file with mode: 0644]
doc/man1/notmuch-restore.rst [new file with mode: 0644]
doc/man1/notmuch-search.rst [new file with mode: 0644]
doc/man1/notmuch-show.rst [new file with mode: 0644]
doc/man1/notmuch-tag.rst [new file with mode: 0644]
doc/man1/notmuch.rst [new file with mode: 0644]
doc/man5/notmuch-hooks.rst [new file with mode: 0644]
doc/man7/notmuch-properties.rst [new file with mode: 0644]
doc/man7/notmuch-search-terms.rst [new file with mode: 0644]
doc/notmuch-emacs.rst [new file with mode: 0644]
elpa-notmuch.elpa [deleted file]
elpa-test [deleted file]
emacs/.gitignore [new file with mode: 0644]
emacs/Makefile [new file with mode: 0644]
emacs/Makefile.local [new file with mode: 0644]
emacs/coolj.el [new file with mode: 0644]
emacs/make-deps.el [new file with mode: 0644]
emacs/notmuch-address.el [new file with mode: 0644]
emacs/notmuch-company.el [new file with mode: 0644]
emacs/notmuch-compat.el [new file with mode: 0644]
emacs/notmuch-crypto.el [new file with mode: 0644]
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 [new file with mode: 0644]
emacs/notmuch-jump.el [new file with mode: 0644]
emacs/notmuch-lib.el [new file with mode: 0644]
emacs/notmuch-logo.png [new file with mode: 0644]
emacs/notmuch-maildir-fcc.el [new file with mode: 0644]
emacs/notmuch-message.el [new file with mode: 0644]
emacs/notmuch-mua.el [new file with mode: 0644]
emacs/notmuch-parser.el [new file with mode: 0644]
emacs/notmuch-pkg.el.tmpl [new file with mode: 0644]
emacs/notmuch-print.el [new file with mode: 0644]
emacs/notmuch-query.el [new file with mode: 0644]
emacs/notmuch-show.el [new file with mode: 0644]
emacs/notmuch-tag.el [new file with mode: 0644]
emacs/notmuch-tree.el [new file with mode: 0644]
emacs/notmuch-version.el.tmpl [new file with mode: 0644]
emacs/notmuch-wash.el [new file with mode: 0644]
emacs/notmuch.el [new file with mode: 0644]
gbp.conf [deleted file]
gmime-filter-reply.c [new file with mode: 0644]
gmime-filter-reply.h [new file with mode: 0644]
hooks.c [new file with mode: 0644]
lib/Makefile [new file with mode: 0644]
lib/Makefile.local [new file with mode: 0644]
lib/add-message.cc [new file with mode: 0644]
lib/built-with.c [new file with mode: 0644]
lib/config.cc [new file with mode: 0644]
lib/database-private.h [new file with mode: 0644]
lib/database.cc [new file with mode: 0644]
lib/directory.cc [new file with mode: 0644]
lib/filenames.c [new file with mode: 0644]
lib/index.cc [new file with mode: 0644]
lib/indexopts.c [new file with mode: 0644]
lib/message-file.c [new file with mode: 0644]
lib/message-id.c [new file with mode: 0644]
lib/message-private.h [new file with mode: 0644]
lib/message-property.cc [new file with mode: 0644]
lib/message.cc [new file with mode: 0644]
lib/messages.c [new file with mode: 0644]
lib/notmuch-private.h [new file with mode: 0644]
lib/notmuch.h [new file with mode: 0644]
lib/notmuch.sym [new file with mode: 0644]
lib/parse-time-vrp.cc [new file with mode: 0644]
lib/parse-time-vrp.h [new file with mode: 0644]
lib/query-fp.cc [new file with mode: 0644]
lib/query-fp.h [new file with mode: 0644]
lib/query.cc [new file with mode: 0644]
lib/regexp-fields.cc [new file with mode: 0644]
lib/regexp-fields.h [new file with mode: 0644]
lib/sha1.c [new file with mode: 0644]
lib/string-list.c [new file with mode: 0644]
lib/string-map.c [new file with mode: 0644]
lib/tags.c [new file with mode: 0644]
lib/thread-fp.cc [new file with mode: 0644]
lib/thread-fp.h [new file with mode: 0644]
lib/thread.cc [new file with mode: 0644]
libnotmuch-dev.install [deleted file]
libnotmuch5.install [deleted file]
libnotmuch5.symbols [deleted file]
mime-node.c [new file with mode: 0644]
notmuch-client.h [new file with mode: 0644]
notmuch-compact.c [new file with mode: 0644]
notmuch-config.c [new file with mode: 0644]
notmuch-count.c [new file with mode: 0644]
notmuch-dump.c [new file with mode: 0644]
notmuch-emacs.README.Debian [deleted file]
notmuch-emacs.maintscript [deleted file]
notmuch-insert.c [new file with mode: 0644]
notmuch-mutt.docs [deleted file]
notmuch-mutt.install [deleted file]
notmuch-mutt.manpages [deleted file]
notmuch-new.c [new file with mode: 0644]
notmuch-reindex.c [new file with mode: 0644]
notmuch-reply.c [new file with mode: 0644]
notmuch-restore.c [new file with mode: 0644]
notmuch-search.c [new file with mode: 0644]
notmuch-setup.c [new file with mode: 0644]
notmuch-show.c [new file with mode: 0644]
notmuch-tag.c [new file with mode: 0644]
notmuch-time.c [new file with mode: 0644]
notmuch-vim.README.Debian [deleted file]
notmuch-vim.dirs [deleted file]
notmuch-vim.docs [deleted file]
notmuch-vim.install [deleted file]
notmuch.c [new file with mode: 0644]
notmuch.dirs [deleted file]
notmuch.docs [deleted file]
notmuch.install [deleted file]
notmuch.maintscript [deleted file]
notmuch.manpages [deleted file]
packaging/debian [new file with mode: 0644]
packaging/fedora/notmuch.spec [new file with mode: 0644]
parse-time-string/Makefile [new file with mode: 0644]
parse-time-string/Makefile.local [new file with mode: 0644]
parse-time-string/README [new file with mode: 0644]
parse-time-string/parse-time-string.c [new file with mode: 0644]
parse-time-string/parse-time-string.h [new file with mode: 0644]
performance-test/.gitignore [new file with mode: 0644]
performance-test/M00-new.sh [new file with mode: 0755]
performance-test/M01-dump-restore.sh [new file with mode: 0755]
performance-test/M02-show.sh [new file with mode: 0755]
performance-test/M03-search.sh [new file with mode: 0755]
performance-test/M04-reply.sh [new file with mode: 0755]
performance-test/M05-reindex.sh [new file with mode: 0755]
performance-test/M06-insert.sh [new file with mode: 0755]
performance-test/Makefile [new file with mode: 0644]
performance-test/Makefile.local [new file with mode: 0644]
performance-test/README [new file with mode: 0644]
performance-test/T00-new.sh [new file with mode: 0755]
performance-test/T01-dump-restore.sh [new file with mode: 0755]
performance-test/T02-tag.sh [new file with mode: 0755]
performance-test/T03-reindex.sh [new file with mode: 0755]
performance-test/T04-thread-subquery.sh [new file with mode: 0755]
performance-test/download/.gitignore [new file with mode: 0644]
performance-test/download/notmuch-email-corpus-0.3.tar.xz.asc [new file with mode: 0644]
performance-test/download/notmuch-email-corpus-0.4.tar.xz.asc [new file with mode: 0644]
performance-test/notmuch-memory-test [new file with mode: 0755]
performance-test/notmuch-time-test [new file with mode: 0755]
performance-test/perf-test-lib.sh [new file with mode: 0644]
performance-test/version.sh [new file with mode: 0644]
python-notmuch.install [deleted file]
python3-notmuch.install [deleted file]
query-string.c [new file with mode: 0644]
ruby-notmuch.install [deleted file]
rules [deleted file]
source/format [deleted file]
source/options [deleted file]
sprinter-json.c [new file with mode: 0644]
sprinter-sexp.c [new file with mode: 0644]
sprinter-text.c [new file with mode: 0644]
sprinter.h [new file with mode: 0644]
status.c [new file with mode: 0644]
tag-util.c [new file with mode: 0644]
tag-util.h [new file with mode: 0644]
test/.gitignore [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/Makefile.local [new file with mode: 0644]
test/README [new file with mode: 0644]
test/T000-basic.sh [new file with mode: 0755]
test/T010-help-test.sh [new file with mode: 0755]
test/T020-compact.sh [new file with mode: 0755]
test/T030-config.sh [new file with mode: 0755]
test/T040-setup.sh [new file with mode: 0755]
test/T050-new.sh [new file with mode: 0755]
test/T060-count.sh [new file with mode: 0755]
test/T070-insert.sh [new file with mode: 0755]
test/T080-search.sh [new file with mode: 0755]
test/T090-search-output.sh [new file with mode: 0755]
test/T095-address.sh [new file with mode: 0755]
test/T100-search-by-folder.sh [new file with mode: 0755]
test/T110-search-position-overlap-bug.sh [new file with mode: 0755]
test/T120-search-insufficient-from-quoting.sh [new file with mode: 0755]
test/T130-search-limiting.sh [new file with mode: 0755]
test/T140-excludes.sh [new file with mode: 0755]
test/T150-tagging.sh [new file with mode: 0755]
test/T160-json.sh [new file with mode: 0755]
test/T170-sexp.sh [new file with mode: 0755]
test/T180-text.sh [new file with mode: 0755]
test/T190-multipart.sh [new file with mode: 0755]
test/T200-thread-naming.sh [new file with mode: 0755]
test/T205-author-naming.sh [new file with mode: 0755]
test/T210-raw.sh [new file with mode: 0755]
test/T220-reply.sh [new file with mode: 0755]
test/T230-reply-to-sender.sh [new file with mode: 0755]
test/T240-dump-restore.sh [new file with mode: 0755]
test/T250-uuencode.sh [new file with mode: 0755]
test/T260-thread-order.sh [new file with mode: 0755]
test/T270-author-order.sh [new file with mode: 0755]
test/T280-from-guessing.sh [new file with mode: 0755]
test/T290-long-id.sh [new file with mode: 0755]
test/T300-encoding.sh [new file with mode: 0755]
test/T310-emacs.sh [new file with mode: 0755]
test/T320-emacs-large-search-buffer.sh [new file with mode: 0755]
test/T330-emacs-subject-to-filename.sh [new file with mode: 0755]
test/T340-maildir-sync.sh [new file with mode: 0755]
test/T350-crypto.sh [new file with mode: 0755]
test/T355-smime.sh [new file with mode: 0755]
test/T357-index-decryption.sh [new file with mode: 0755]
test/T360-symbol-hiding.sh [new file with mode: 0755]
test/T370-search-folder-coherence.sh [new file with mode: 0755]
test/T380-atomicity.sh [new file with mode: 0755]
test/T390-python.sh [new file with mode: 0755]
test/T395-ruby.sh [new file with mode: 0755]
test/T400-hooks.sh [new file with mode: 0755]
test/T410-argument-parsing.sh [new file with mode: 0755]
test/T420-emacs-test-functions.sh [new file with mode: 0755]
test/T430-emacs-address-cleaning.sh [new file with mode: 0755]
test/T440-emacs-hello.sh [new file with mode: 0755]
test/T450-emacs-show.sh [new file with mode: 0755]
test/T455-emacs-charsets.sh [new file with mode: 0755]
test/T460-emacs-tree.sh [new file with mode: 0755]
test/T470-missing-headers.sh [new file with mode: 0755]
test/T480-hex-escaping.sh [new file with mode: 0755]
test/T490-parse-time-string.sh [new file with mode: 0755]
test/T500-search-date.sh [new file with mode: 0755]
test/T510-thread-replies.sh [new file with mode: 0755]
test/T520-show.sh [new file with mode: 0755]
test/T530-upgrade.sh [new file with mode: 0755]
test/T550-db-features.sh [new file with mode: 0755]
test/T560-lib-error.sh [new file with mode: 0755]
test/T570-revision-tracking.sh [new file with mode: 0755]
test/T580-thread-search.sh [new file with mode: 0755]
test/T585-thread-subquery.sh [new file with mode: 0755]
test/T590-libconfig.sh [new file with mode: 0755]
test/T590-thread-breakage.sh [new file with mode: 0755]
test/T600-named-queries.sh [new file with mode: 0755]
test/T610-message-property.sh [new file with mode: 0755]
test/T620-lock.sh [new file with mode: 0755]
test/T630-emacs-draft.sh [new file with mode: 0755]
test/T640-database-modified.sh [new file with mode: 0755]
test/T650-regexp-query.sh [new file with mode: 0755]
test/T660-bad-date.sh [new file with mode: 0755]
test/T670-duplicate-mid.sh [new file with mode: 0755]
test/T680-html-indexing.sh [new file with mode: 0755]
test/T690-command-line-args.sh [new file with mode: 0755]
test/T700-reindex.sh [new file with mode: 0755]
test/T710-message-id.sh [new file with mode: 0755]
test/aggregate-results.sh [new file with mode: 0755]
test/arg-test.c [new file with mode: 0644]
test/atomicity.py [new file with mode: 0644]
test/corpora/README [new file with mode: 0644]
test/corpora/broken/broken-cc [new file with mode: 0644]
test/corpora/broken/loop/loop-12 [new file with mode: 0644]
test/corpora/broken/loop/loop-21 [new file with mode: 0644]
test/corpora/crypto/simple-encrypted [new file with mode: 0644]
test/corpora/default/01:2, [new file with mode: 0644]
test/corpora/default/02:2, [new file with mode: 0644]
test/corpora/default/bar/17:2, [new file with mode: 0644]
test/corpora/default/bar/18:2, [new file with mode: 0644]
test/corpora/default/bar/baz/05:2, [new file with mode: 0644]
test/corpora/default/bar/baz/23:2, [new file with mode: 0644]
test/corpora/default/bar/baz/24:2, [new file with mode: 0644]
test/corpora/default/bar/baz/cur/25:2, [new file with mode: 0644]
test/corpora/default/bar/baz/cur/26:2, [new file with mode: 0644]
test/corpora/default/bar/baz/new/27:2, [new file with mode: 0644]
test/corpora/default/bar/baz/new/28:2, [new file with mode: 0644]
test/corpora/default/bar/cur/19:2, [new file with mode: 0644]
test/corpora/default/bar/cur/20:2, [new file with mode: 0644]
test/corpora/default/bar/new/21:2, [new file with mode: 0644]
test/corpora/default/bar/new/22:2, [new file with mode: 0644]
test/corpora/default/cur/29:2, [new file with mode: 0644]
test/corpora/default/cur/30:2, [new file with mode: 0644]
test/corpora/default/cur/31:2, [new file with mode: 0644]
test/corpora/default/cur/32:2, [new file with mode: 0644]
test/corpora/default/cur/33:2, [new file with mode: 0644]
test/corpora/default/cur/34:2, [new file with mode: 0644]
test/corpora/default/cur/35:2, [new file with mode: 0644]
test/corpora/default/cur/36:2, [new file with mode: 0644]
test/corpora/default/cur/37:2, [new file with mode: 0644]
test/corpora/default/cur/38:2, [new file with mode: 0644]
test/corpora/default/cur/39:2, [new file with mode: 0644]
test/corpora/default/cur/40:2, [new file with mode: 0644]
test/corpora/default/cur/41:2, [new file with mode: 0644]
test/corpora/default/cur/42:2, [new file with mode: 0644]
test/corpora/default/cur/43:2, [new file with mode: 0644]
test/corpora/default/cur/44:2, [new file with mode: 0644]
test/corpora/default/cur/45:2, [new file with mode: 0644]
test/corpora/default/cur/46:2, [new file with mode: 0644]
test/corpora/default/cur/47:2, [new file with mode: 0644]
test/corpora/default/cur/48:2, [new file with mode: 0644]
test/corpora/default/cur/49:2, [new file with mode: 0644]
test/corpora/default/cur/50:2, [new file with mode: 0644]
test/corpora/default/cur/51:2, [new file with mode: 0644]
test/corpora/default/cur/52:2, [new file with mode: 0644]
test/corpora/default/cur/53:2, [new file with mode: 0644]
test/corpora/default/foo/06:2, [new file with mode: 0644]
test/corpora/default/foo/baz/11:2, [new file with mode: 0644]
test/corpora/default/foo/baz/12:2, [new file with mode: 0644]
test/corpora/default/foo/baz/cur/13:2, [new file with mode: 0644]
test/corpora/default/foo/baz/cur/14:2, [new file with mode: 0644]
test/corpora/default/foo/baz/new/15:2, [new file with mode: 0644]
test/corpora/default/foo/baz/new/16:2, [new file with mode: 0644]
test/corpora/default/foo/cur/07:2, [new file with mode: 0644]
test/corpora/default/foo/cur/08:2, [new file with mode: 0644]
test/corpora/default/foo/new/03:2, [new file with mode: 0644]
test/corpora/default/foo/new/09:2, [new file with mode: 0644]
test/corpora/default/foo/new/10:2, [new file with mode: 0644]
test/corpora/default/new/04:2, [new file with mode: 0644]
test/corpora/html/attribute-text [new file with mode: 0644]
test/corpora/html/embedded-image [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000260:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000261:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000265:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000323:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000324:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000325:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000539:2, [new file with mode: 0644]
test/corpora/lkml/cur/1354585346.000541:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001724:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001730:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001731:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001732:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001733:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001734:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001735:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001736:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001738:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001739:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001740:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001887:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001892:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.001970:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002189:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002193:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002194:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002195:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002196:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002197:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002201:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002228:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002878:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002912:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002915:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002917:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.002997:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003106:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003112:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003117:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003118:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003171:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003317:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003318:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.003486:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.004581:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298587.004582:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001724:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001730:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001731:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001732:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001733:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001734:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001735:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001736:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001738:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001739:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001740:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001887:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.001892:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002189:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002191:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002193:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002194:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002195:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002196:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002197:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002201:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002878:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002879:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002911:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002912:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002915:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002917:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002930:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.002997:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003106:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003117:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003118:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003171:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003317:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003318:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003486:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.003499:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.004581:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298770.004582:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.002830:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.002978:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.002992:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.002999:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.003976:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.004354:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.004363:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298775.004374:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002253:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002254:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002255:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002256:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002257:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002258:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002259:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002260:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002261:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002262:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002263:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002264:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002265:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002266:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002267:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002268:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002269:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002270:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002271:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002272:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002273:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002274:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002275:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002276:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002277:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002278:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002279:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002280:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002281:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002282:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002283:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002284:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002285:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002286:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002287:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002288:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002289:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002290:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002292:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002293:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002294:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002296:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002297:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002298:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002299:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002302:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002309:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002329:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002340:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002400:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002432:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002468:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002543:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002557:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002575:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002576:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002639:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002642:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002661:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002662:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002663:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002664:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002665:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002666:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002667:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002668:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002669:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002670:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002671:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002679:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002688:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.002699:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003013:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003145:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003148:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003216:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003231:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003278:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003295:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003316:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003334:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003340:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003448:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003459:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003462:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003468:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003471:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003472:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003478:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003497:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003501:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003503:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.003971:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.004059:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.004091:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298793.004190:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298795.000299:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298795.001362:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298795.002635:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298796.001941:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004526:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004551:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004613:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004614:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004615:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004617:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004618:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004619:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004636:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004638:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004639:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004640:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004642:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004653:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004665:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004680:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004688:2, [new file with mode: 0644]
test/corpora/lkml/cur/1382298805.004906:2, [new file with mode: 0644]
test/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S [new file with mode: 0644]
test/corpora/threading/ghost-root/1532672447.R3166642290392477575.len:2,S [new file with mode: 0644]
test/corpora/threading/ghost-root/1532672447.R6968667928580738175.len:2,S [new file with mode: 0644]
test/corpora/threading/ghost-root/child [new file with mode: 0644]
test/corpora/threading/ghost-root/fake-root [new file with mode: 0644]
test/corpora/threading/ghost-root/grand-child [new file with mode: 0644]
test/corpora/threading/ghost-root/grand-child2 [new file with mode: 0644]
test/corpora/threading/ghost-root/great-grand-child [new file with mode: 0644]
test/corpora/threading/ghost-root/real-root [new file with mode: 0644]
test/corpora/threading/parent-priority/cur/child [new file with mode: 0644]
test/corpora/threading/parent-priority/cur/grand-child [new file with mode: 0644]
test/corpora/threading/parent-priority/cur/root [new file with mode: 0644]
test/database-test.c [new file with mode: 0644]
test/database-test.h [new file with mode: 0644]
test/emacs-address-cleaning.el [new file with mode: 0644]
test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off [new file with mode: 0644]
test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on [new file with mode: 0644]
test/emacs-show.expected-output/notmuch-show-indent-thread-content-off [new file with mode: 0644]
test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off [new file with mode: 0644]
test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-on [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-show-window [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-single-thread [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-tag-inbox [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-tag-inbox-tagged [new file with mode: 0644]
test/emacs-tree.expected-output/notmuch-tree-tag-inbox-thread-tagged [new file with mode: 0644]
test/emacs.expected-output/attachment [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-empty-custom-queries-section [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-empty-custom-tags-section [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-long-names [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-new-section [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-no-saved-searches [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-section-counts [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-section-hidden-tag [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-section-with-empty [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-view-inbox [new file with mode: 0644]
test/emacs.expected-output/notmuch-hello-with-empty [new file with mode: 0644]
test/emacs.expected-output/notmuch-search-tag-inbox [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-message-with-headers-hidden [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-message-with-headers-visible [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-maildir-storage [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-with-all-messages-collapsed [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-with-all-messages-uncollapsed [new file with mode: 0644]
test/emacs.expected-output/notmuch-show-thread-with-hidden-messages [new file with mode: 0644]
test/emacs.expected-output/raw-message-cf0c4d-52ad0a [new file with mode: 0644]
test/export-dirs.sh [new file with mode: 0644]
test/gen-threads.py [new file with mode: 0644]
test/ghost-report.cc [new file with mode: 0644]
test/gnupg-secret-key.NOTE [new file with mode: 0644]
test/gnupg-secret-key.asc [new file with mode: 0644]
test/hex-xcode.c [new file with mode: 0644]
test/make-db-version.cc [new file with mode: 0644]
test/message-id-parse.c [new file with mode: 0644]
test/notmuch-test [new file with mode: 0755]
test/notmuch-test.h [new file with mode: 0644]
test/parse-time.c [new file with mode: 0644]
test/random-corpus.c [new file with mode: 0644]
test/smime/README [new file with mode: 0644]
test/smime/key+cert.pem [new file with mode: 0644]
test/smime/test.crt [new file with mode: 0644]
test/smtp-dummy.c [new file with mode: 0644]
test/symbol-test.cc [new file with mode: 0644]
test/test-databases/.gitignore [new file with mode: 0644]
test/test-databases/Makefile [new file with mode: 0644]
test/test-databases/Makefile.local [new file with mode: 0644]
test/test-databases/database-v1.tar.xz.sha256 [new file with mode: 0644]
test/test-lib-FREEBSD.sh [new file with mode: 0644]
test/test-lib-common.sh [new file with mode: 0644]
test/test-lib.el [new file with mode: 0644]
test/test-lib.sh [new file with mode: 0644]
test/test-verbose [new file with mode: 0755]
test/test.expected-output/test-verbose-no [new file with mode: 0644]
test/test.expected-output/test-verbose-yes [new file with mode: 0644]
test/valgrind/suppressions [new file with mode: 0644]
test/valgrind/valgrind.sh [new file with mode: 0755]
util/Makefile [new file with mode: 0644]
util/Makefile.local [new file with mode: 0644]
util/crypto.c [new file with mode: 0644]
util/crypto.h [new file with mode: 0644]
util/error_util.c [new file with mode: 0644]
util/error_util.h [new file with mode: 0644]
util/gmime-extra.c [new file with mode: 0644]
util/gmime-extra.h [new file with mode: 0644]
util/hex-escape.c [new file with mode: 0644]
util/hex-escape.h [new file with mode: 0644]
util/string-util.c [new file with mode: 0644]
util/string-util.h [new file with mode: 0644]
util/talloc-extra.c [new file with mode: 0644]
util/talloc-extra.h [new file with mode: 0644]
util/util.c [new file with mode: 0644]
util/util.h [new file with mode: 0644]
util/xutil.c [new file with mode: 0644]
util/xutil.h [new file with mode: 0644]
util/zlib-extra.c [new file with mode: 0644]
util/zlib-extra.h [new file with mode: 0644]
version [new file with mode: 0644]
vim/Makefile [new file with mode: 0644]
vim/README [new file with mode: 0644]
vim/notmuch.txt [new file with mode: 0644]
vim/notmuch.vim [new file with mode: 0644]
vim/notmuch.yaml [new file with mode: 0644]
vim/syntax/notmuch-compose.vim [new file with mode: 0644]
vim/syntax/notmuch-folders.vim [new file with mode: 0644]
vim/syntax/notmuch-git-diff.vim [new file with mode: 0644]
vim/syntax/notmuch-search.vim [new file with mode: 0644]
vim/syntax/notmuch-show.vim [new file with mode: 0644]

diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644 (file)
index 0000000..fc75ae6
--- /dev/null
@@ -0,0 +1,25 @@
+;; emacs local configuration settings for notmuch source
+;; surmised by dkg on 2010-11-23 13:43:18-0500
+;; amended by amdragon on 2011-06-06
+
+((c-mode
+  (indent-tabs-mode . t)
+  (tab-width . 8)
+  (c-basic-offset . 4)
+  (c-file-style . "linux"))
+ (c++-mode
+  (indent-tabs-mode . t)
+  (tab-width . 8)
+  (c-basic-offset . 4)
+  (c-file-style . "linux"))
+ (emacs-lisp-mode
+  (indent-tabs-mode . t)
+  (tab-width . 8))
+ (shell-mode
+  (indent-tabs-mode . t)
+  (tab-width . 8)
+  (sh-basic-offset . 4)
+  (sh-indentation . 4))
+ (nil
+  (fill-column . 70))
+ )
index cd0decc29f365c497f8bb410a4d7fa99f8fd7141..e06101ce51c67c65d9118b198b4c165501b0d6e5 100644 (file)
@@ -1,14 +1,17 @@
-/tmp/
-/libnotmuch-dev/
-/libnotmuch*/
-/notmuch-emacs/
-/elpa-notmuch/
-/notmuch/
-/notmuch-mutt/
-/notmuch-vim/
-/ruby-notmuch/
-/python*-notmuch/
-/*.debhelper
-/*.debhelper.log
-/*.substvars
-/files
+/.first-build-message
+/Makefile.config
+/sh.config
+/version.stamp
+TAGS
+tags
+*cscope*
+/.deps
+/notmuch
+/notmuch-shared
+/lib/libnotmuch.so*
+/lib/libnotmuch*.dylib
+*.[ao]
+*~
+.*.swp
+/releases
+/.stamps
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..935c6eb
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Peter Feigl <craven@gmx.net>
+Nate <nstraz@redhat.com>
+Ali Polatel <alip@penguen.ev>
+Stefan <aeuii@posteo.de>
+Patrick Totzke <patricktotzke@googlemail.com>
+Patrick Totzke <patricktotzke@gmail.com>
+Patrick Totzke <p.totzke@ed.ac.uk>
+Mark Walters <markwalters1009@gmail.com>
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..79802b7
--- /dev/null
@@ -0,0 +1,25 @@
+language: c
+
+dist: trusty
+sudo: false
+
+addons:
+  apt:
+    packages:
+    - dtach
+    - libxapian-dev
+    - libgmime-2.6-dev
+    - libtalloc-dev
+    - python3-sphinx
+    - gpgsm
+
+script:
+  - ./configure
+  - make download-test-databases
+  - make test
+
+notifications:
+  irc:
+    channels:
+      - "chat.freenode.net#notmuch"
+    on_success: change
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..5fe5006
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,30 @@
+Carl Worth <cworth@cworth.org> is the primary author of Notmuch.
+But there's really not much that he's done. There's been a lot of
+standing on shoulders here:
+
+William Morgan deserves credit for providing the primary inspiration
+for Notmuch with his program Sup (https://sup-heliotrope.github.io/).
+
+Some people have contributed code that has made it into Notmuch
+without their specific knowledge (but with their full permission
+thanks to the GNU General Public License). This includes:
+
+Brian Gladman (with Mikhail Gusarov <dottedmag@dottedmag.net>)
+       Implementation of SHA-1 (nice and small) (libsha1.c)
+
+Please see the various files in the Notmuch distribution for
+individual copyright statements.
+
+And of course, though their code isn't distributed here, Notmuch would
+be not much of anything without the contributors to Xapian, the search
+engine that does the really heavy lifting, as well as the various
+system libraries, compilers, and the kernel that make it all work
+(thanks GNU, thanks Linux). Thanks to everyone who has played a part!
+
+Here is an incomplete list of other people that have made
+contributions to Notmuch (whether by code, bug reporting/fixes,
+ideas, inspiration, testing or feedback):
+
+Martin Krafft
+Keith Packard
+Jamey Sharp
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..4e744d2
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,15 @@
+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.
+
+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, (in the COPYING-GPL-3 file in this
+directory). If not, see https://www.gnu.org/licenses/
diff --git a/COPYING-GPL-3 b/COPYING-GPL-3
new file mode 100644 (file)
index 0000000..4c49354
--- /dev/null
@@ -0,0 +1,676 @@
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                      TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+  
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..6e6f479
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,104 @@
+Build and install instructions for Notmuch.
+
+Compilation commands
+--------------------
+The process for compiling and installing Notmuch is the very standard
+sequence of:
+
+       ./configure
+       make
+       sudo make install
+
+In fact, if you don't plan to pass any arguments to the configure
+script, then you can skip that step and just start with "make", (which
+will call configure for you). See this command:
+
+       ./configure --help
+
+for detailed documentation of the things you can control at the
+configure stage.
+
+Dependencies
+------------
+Notmuch depends on four libraries: Xapian, GMime 2.6,
+Talloc, and zlib which are each described below:
+
+       Xapian
+       ------
+       Xapian is the search-engine library underlying Notmuch.
+
+       It provides all the real machinery of indexing and searching,
+       (including the very nice parsing of the query string).
+
+       Xapian is available from https://xapian.org
+
+       Note: Notmuch will work best with Xapian 1.0.18 (or later) or
+       Xapian 1.1.4 (or later). Previous versions of Xapian (whether
+       1.0 or 1.1) had a performance bug that made notmuch very slow
+       when modifying tags. This would cause distracting pauses when
+       reading mail while notmuch would wait for Xapian when removing
+       the "inbox" and "unread" tags from messages in a thread.
+
+       GMime
+       -----
+       GMime provides decoding of MIME email messages for Notmuch.
+
+       Without GMime, Notmuch would not be able to extract and index
+       the actual text from email message encoded as BASE64, etc.
+
+       GMime is available from https://github.com/jstedfast/gmime
+
+       Talloc
+       ------
+       Talloc is a memory-pool allocator used by Notmuch.
+
+       Talloc is an extremely lightweight and easy-to-use tool for
+       allocating memory in a hierarchical fashion and then freeing
+       it with a single call of the top-level handle. Using it has
+       made development of Notmuch much easier and much less prone to
+       memory leaks.
+
+       Talloc is available from https://talloc.samba.org/
+
+       zlib
+       ----
+
+       zlib is an extremely popular compression library. It is used
+       by Xapian, so if you installed that you will already have
+       zlib. You may need to install the zlib headers separately.
+
+       Notmuch needs the transparent write feature of zlib introduced
+       in version 1.2.5.2 (Dec. 2011).
+
+       zlib is available from https://zlib.net
+
+Building Documentation
+----------------------
+
+To build the documentation for notmuch you need at least version 1.0
+of sphinx (Jul. 2010).
+
+Sphinx is available from www.sphinx-doc.org.
+
+To install the documentation as "info" pages, you will need the
+additional tools makeinfo and install-info.
+
+Installing Dependencies from Packages
+-------------------------------------
+
+On a modern, package-based operating system you can install all of the
+dependencies with a single simple command line. For example:
+
+  For Debian and similar:
+
+        sudo apt-get install libxapian-dev libgmime-3.0-dev libtalloc-dev zlib1g-dev python3-sphinx texinfo install-info
+
+  For Fedora and similar:
+
+       sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel python3-sphinx texinfo info
+
+On other systems, a similar command can be used, but the details of
+the package names may be different.
+
+       
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..d2010fe
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,71 @@
+# We want the all target to be the implicit target (if no target is
+# given explicitly on the command line) so mention it first.
+all:
+
+# Sub-directory Makefile.local fragments can append to these variables
+# to have directory-specific cflags as necessary.
+
+extra_cflags :=
+extra_cxxflags :=
+
+# Get settings from the output of configure by running it to generate
+# Makefile.config if it doesn't exist yet.
+
+# If Makefile.config doesn't exist, then srcdir won't be
+# set. Conditionally set it (assuming a plain srcdir build) so that
+# the rule to generate Makefile.config can actually work.
+srcdir ?= .
+
+include Makefile.config
+
+# We make all targets depend on the Makefiles themselves.
+global_deps = Makefile Makefile.config Makefile.local \
+       $(subdirs:%=%/Makefile) $(subdirs:%=%/Makefile.local)
+
+INCLUDE_MORE := yes
+ifneq ($(filter clean distclean dataclean, $(word 1, $(MAKECMDGOALS))),)
+CLEAN_GOAL := $(word 1, $(MAKECMDGOALS))
+
+# If there are more goals following CLEAN_GOAL, run $(MAKE)s in parts.
+ifneq ($(word 2, $(MAKECMDGOALS)),)
+INCLUDE_MORE := no
+FOLLOWING_GOALS := $(wordlist 2, 99, $(MAKECMDGOALS))
+
+.PHONY: $(FOLLOWING_GOALS) make_in_parts
+$(FOLLOWING_GOALS):
+       @true
+$(CLEAN_GOAL): make_in_parts
+make_in_parts:
+       $(MAKE) $(CLEAN_GOAL)
+       $(MAKE) $(FOLLOWING_GOALS) configure_options="$(configure_options)"
+endif
+
+else
+CLEAN_GOAL :=
+endif
+
+# Potentially speedup make clean, distclean and dataclean ; avoid
+# re-creating Makefile.config if it exists but configure is newer.
+ifneq ($(CLEAN_GOAL),)
+Makefile.config: | $(srcdir)/configure
+else
+Makefile.config: $(srcdir)/configure
+endif
+ifeq ($(configure_options),)
+       @echo ""
+       @echo "Note: Calling ./configure with no command-line arguments. This is often fine,"
+       @echo "      but if you want to specify any arguments (such as an alternate prefix"
+       @echo "      into which to install), call ./configure explicitly and then make again."
+       @echo "      See \"./configure --help\" for more details."
+       @echo ""
+endif
+       $(srcdir)/configure $(configure_options)
+
+ifeq ($(INCLUDE_MORE),yes)
+# 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.
+
+include $(subdirs:%=%/Makefile.local) Makefile.local
+endif
diff --git a/Makefile.global b/Makefile.global
new file mode 100644 (file)
index 0000000..cae4c7d
--- /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
+SHA256_FILE=$(TAR_FILE).sha256
+GPG_FILE=$(SHA256_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 -lnotmuch_util -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)
diff --git a/Makefile.local b/Makefile.local
new file mode 100644 (file)
index 0000000..82145e1
--- /dev/null
@@ -0,0 +1,309 @@
+# -*- makefile -*-
+
+.PHONY: all
+all: notmuch notmuch-shared build-man build-info ruby-bindings
+ifeq ($(MAKECMDGOALS),)
+ifeq ($(shell cat .first-build-message 2>/dev/null),)
+       @NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all
+       @echo ""
+       @echo "Compilation of notmuch is now complete. You can install notmuch with:"
+       @echo ""
+       @echo " make install"
+       @echo ""
+       @echo "Note that depending on the prefix to which you are installing"
+       @echo "you may need root permission (such as \"sudo make install\")."
+       @echo "See \"./configure --help\" for help on setting an alternate prefix."
+       @echo Printed > .first-build-message
+endif
+endif
+
+# Depend (also) on the file 'version'. In case of ifeq ($(IS_GIT),yes)
+# this file may already have been updated.
+version.stamp: $(srcdir)/version
+       echo $(VERSION) > $@
+
+$(TAR_FILE):
+       if git tag -v $(UPSTREAM_TAG) >/dev/null 2>&1; then \
+           ref=$(UPSTREAM_TAG); \
+        else \
+           ref="HEAD" ; \
+          echo "Warning: No signed tag for $(VERSION)"; \
+       fi ; \
+       git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ $$ref > $(TAR_FILE).tmp
+       echo $(VERSION) > version.tmp
+       ct=`git --no-pager log -1 --pretty=format:%ct $$ref` ; \
+       tar --owner root --group root --append -f $(TAR_FILE).tmp \
+               --transform s_^_$(PACKAGE)-$(VERSION)/_  \
+               --transform 's_.tmp$$__' --mtime=@$$ct version.tmp
+       rm version.tmp
+       gzip -n < $(TAR_FILE).tmp > $(TAR_FILE)
+       @echo "Source is ready for release in $(TAR_FILE)"
+
+$(SHA256_FILE): $(TAR_FILE)
+       sha256sum $^ > $@
+
+$(GPG_FILE): $(SHA256_FILE)
+       gpg --armor --sign $^
+
+.PHONY: dist
+dist: $(TAR_FILE)
+
+.PHONY: update-versions
+
+update-versions:
+       sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \
+           -e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \
+           ${PV_FILE}
+
+# We invoke make recursively only to force ordering of our phony
+# targets in the case of parallel invocation of make (-j).
+#
+# We carefully ensure that our VERSION variable is passed down to any
+# sub-ordinate make invocations (which won't otherwise know that they
+# are part of the release and need to take the version from the
+# version file).
+.PHONY: release
+release: verify-source-tree-and-version
+       $(MAKE) VERSION=$(VERSION) verify-newer
+       $(MAKE) VERSION=$(VERSION) clean
+       $(MAKE) VERSION=$(VERSION) test
+       git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG)
+       $(MAKE) VERSION=$(VERSION) $(GPG_FILE)
+       ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
+       pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
+       git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG)
+       mkdir -p releases
+       mv $(TAR_FILE) $(SHA256_FILE) $(GPG_FILE) releases
+       $(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce
+ifeq ($(REALLY_UPLOAD),yes)
+       git push origin $(VERSION) $(DEB_TAG) release pristine-tar
+       cd releases && scp $(TAR_FILE) $(SHA256_FILE) $(GPG_FILE) $(RELEASE_HOST):$(RELEASE_DIR)
+       ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)"
+endif
+       @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template."
+
+.PHONY: pre-release
+pre-release:
+       $(MAKE) VERSION=$(VERSION) clean
+       $(MAKE) VERSION=$(VERSION) test
+       git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG)
+       git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG)
+       $(MAKE) VERSION=$(VERSION) $(TAR_FILE)
+       ln -sf $(TAR_FILE) $(DEB_TAR_FILE)
+       pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG)
+       mkdir -p releases
+       mv $(TAR_FILE) $(DEB_TAR_FILE) releases
+
+.PHONY: debian-snapshot
+debian-snapshot:
+       make VERSION=$(VERSION) clean
+       TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX);         \
+         cp debian/changelog $${TMPFILE};              \
+         EDITOR=/bin/true dch -b -v $(VERSION)+1       \
+           -D UNRELEASED 'test build, not for upload'; \
+         echo '3.0 (native)' > debian/source/format;   \
+         debuild -us -uc;                              \
+         mv -f $${TMPFILE} debian/changelog;           \
+         echo '3.0 (quilt)' > debian/source/format
+
+.PHONY: release-message
+release-message:
+       @echo "To: notmuch@notmuchmail.org"
+       @echo "Subject: $(PACKAGE) release $(VERSION) now available"
+       @echo ""
+       @echo "Where to obtain notmuch $(VERSION)"
+       @echo "==========================="
+       @echo "  $(RELEASE_URL)/$(TAR_FILE)"
+       @echo ""
+       @echo "Which can be verified with:"
+       @echo ""
+       @echo "  $(RELEASE_URL)/$(SHA256_FILE)"
+       @echo -n "  "
+       @cat releases/$(SHA256_FILE)
+       @echo ""
+       @echo "  $(RELEASE_URL)/$(GPG_FILE)"
+       @echo "  (signed by `getent passwd "$$USER" | cut -d: -f 5 | cut -d, -f 1`)"
+       @echo ""
+       @echo "What's new in notmuch $(VERSION)"
+       @echo "========================="
+       @sed -ne '/^[Nn]otmuch $(VERSION)/{n;n;b NEWS}; d; :NEWS /^===/q; {p;n;b NEWS}' < NEWS | head -n -2
+       @echo ""
+       @echo "What is notmuch"
+       @echo "==============="
+       @echo "Notmuch is a system for indexing, searching, reading, and tagging"
+       @echo "large collections of email messages in maildir or mh format. It uses"
+       @echo "the Xapian library to provide fast, full-text search with a convenient"
+       @echo "search syntax."
+       @echo ""
+       @echo "For more about notmuch, see https://notmuchmail.org"
+
+# This is a chain of dependencies rather than a simple list simply to
+# avoid the messages getting interleaved in the case of a parallel
+# make invocation.
+.PHONY: verify-source-tree-and-version
+verify-source-tree-and-version: verify-no-dirty-code
+
+.PHONY: verify-no-dirty-code
+verify-no-dirty-code: release-checks
+ifeq ($(IS_GIT),yes)
+       @printf "Checking that source tree is clean..."
+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 --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."
+       @false
+else
+       @echo "Good"
+endif
+endif
+
+.PHONY: release-checks
+release-checks:
+       devel/release-checks.sh
+
+.PHONY: verify-newer
+verify-newer:
+       @echo -n "Checking that no $(VERSION) release already exists..."
+       @wget -q --no-check-certificate -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \
+       case $$? in \
+          8) echo "Good." ;; \
+          0) echo "Ouch."; \
+            echo "Found: $(RELEASE_URL)/$(TAR_FILE)"; \
+            echo "Refusing to replace an existing release."; \
+            echo "Don't forget to update \"version\" as described in RELEASING before release." ; \
+            false ;; \
+         *) echo "An unexpected error occurred"; \
+            false;; esac
+
+# The user has not set any verbosity, default to quiet mode and inform the
+# user how to enable verbose compiles.
+ifeq ($(V),)
+quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
+quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
+endif
+# The user has explicitly enabled quiet compilation.
+ifeq ($(V),0)
+quiet = @printf "$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
+endif
+# Otherwise, print the full command line.
+quiet ?= $($(word 1, $(1)))
+
+%.o: %.cc $(global_deps)
+       @mkdir -p $(patsubst %/.,%,.deps/$(@D))
+       $(call quiet,CXX $(CPPFLAGS) $(CXXFLAGS)) -c $(FINAL_CXXFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
+
+%.o: %.c $(global_deps)
+       @mkdir -p $(patsubst %/.,%,.deps/$(@D))
+       $(call quiet,CC $(CPPFLAGS) $(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
+
+CPPCHECK=cppcheck
+.stamps/cppcheck/%: %
+       @mkdir -p $(@D)
+       $(call quiet,CPPCHECK,$<) --template=gcc --error-exitcode=1 --quiet $<
+       @touch $@
+
+CLEAN := $(CLEAN) .stamps
+
+.PHONY : clean
+clean:
+       rm -rf $(CLEAN)
+
+.PHONY: distclean
+distclean: clean
+       rm -rf $(DISTCLEAN)
+
+.PHONY: dataclean
+dataclean: distclean
+       rm -rf $(DATACLEAN)
+
+notmuch_client_srcs =          \
+       $(notmuch_compat_srcs)  \
+       command-line-arguments.c\
+       debugger.c              \
+       status.c                \
+       gmime-filter-reply.c    \
+       hooks.c                 \
+       notmuch.c               \
+       notmuch-compact.c       \
+       notmuch-config.c        \
+       notmuch-count.c         \
+       notmuch-dump.c          \
+       notmuch-insert.c        \
+       notmuch-new.c           \
+       notmuch-reindex.c       \
+       notmuch-reply.c         \
+       notmuch-restore.c       \
+       notmuch-search.c        \
+       notmuch-setup.c         \
+       notmuch-show.c          \
+       notmuch-tag.c           \
+       notmuch-time.c          \
+       sprinter-json.c         \
+       sprinter-sexp.c         \
+       sprinter-text.c         \
+       query-string.c          \
+       mime-node.c             \
+       tag-util.c
+
+notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
+
+notmuch.o: version.stamp
+
+notmuch: $(notmuch_client_modules) lib/libnotmuch.a util/libnotmuch_util.a parse-time-string/libparse-time-string.a
+       $(call quiet,CXX $(CFLAGS)) $^ $(FINAL_LIBNOTMUCH_LDFLAGS) -o $@
+
+notmuch-shared: $(notmuch_client_modules) lib/$(LINKER_NAME)
+       $(call quiet,$(FINAL_NOTMUCH_LINKER) $(CFLAGS)) $(notmuch_client_modules) $(FINAL_NOTMUCH_LDFLAGS) -o $@
+
+.PHONY: install
+install: all install-man install-info
+       mkdir -p "$(DESTDIR)$(prefix)/bin/"
+       install notmuch-shared "$(DESTDIR)$(prefix)/bin/notmuch"
+ifeq ($(MAKECMDGOALS), install)
+       @echo ""
+       @echo "Notmuch is now installed to $(DESTDIR)$(prefix)"
+       @echo ""
+       @echo "New users should simply run \"notmuch\" to be guided"
+       @echo "through the process of configuring notmuch and creating"
+       @echo "a database of existing email messages. The \"notmuch\""
+       @echo "command will also offer some sample search commands."
+ifeq ($(WITH_EMACS), 1)
+       @echo ""
+       @echo "Beyond the command-line interface, notmuch also offers"
+       @echo "a full-featured interface for reading and writing mail"
+       @echo "within emacs. To use this, each user should add the"
+       @echo "following line to the ~/.emacs file:"
+       @echo ""
+       @echo " (require 'notmuch)"
+       @echo ""
+       @echo "And then run emacs as \"emacs -f notmuch\" or invoke"
+       @echo "the command \"M-x notmuch\" from within emacs."
+endif
+endif
+
+SRCS  := $(SRCS) $(notmuch_client_srcs)
+CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
+CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
+CLEAN := $(CLEAN) .deps
+
+DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config
+
+CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%)
+.PHONY: cppcheck
+ifeq ($(HAVE_CPPCHECK),1)
+cppcheck: ${CPPCHECK_STAMPS}
+else
+cppcheck:
+       @echo "No cppcheck found during configure; skipping static checking"
+endif
+
+
+DEPS := $(SRCS:%.c=.deps/%.d)
+DEPS := $(DEPS:%.cc=.deps/%.d)
+-include $(DEPS)
+
+.SUFFIXES: # Delete the default suffixes. Old-Fashioned Suffix Rules not used.
diff --git a/NEWS b/NEWS
index bf661fe4a01cec0f2c48429a0dbd5007d3e07fbe..e469ba063c63a31727d668d951f92f709ddd5a03 100644 (file)
--- a/NEWS
+++ b/NEWS
-notmuch (0.21~rc1-1) experimental; urgency=medium
+Notmuch 0.28.2 (2019-02-17)
+===========================
 
-  This release of notmuch requires a non-reversible database upgrade
-  to support database revision tracking. This upgrade will happen on
-  the first run of 'notmuch-new' after updating. Notmuch will backup
-  your tags for your before doing the upgrade, but it never hurts to
-  make your own backup with notmuch dump.
+Emacs
+-----
 
- -- David Bremner <bremner@debian.org>  Thu, 15 Oct 2015 08:13:04 -0300
+Invoke gpg with --batch and --no-tty.
 
-notmuch (0.19-1) experimental; urgency=medium
+Python Bindings
+---------------
 
-  This release of notmuch again requires a non-reversable database
-  upgrade to support database features. This upgrade will happen on
-  the first run of 'notmuch-new' after updating. Notmuch will backup
-  your tags for your before doing the upgrade, but it never hurts to
-  make your own backup with notmuch dump.
+Fix documentation build with Python 3.7. Note that Python >= 3.3 is
+now needed to build this documentation.
 
- -- David Bremner <bremner@debian.org>  Fri, 14 Nov 2014 20:34:55 +0100
+Notmuch 0.28.1 (2019-02-01)
+===========================
 
-notmuch (0.18~rc0-1) experimental; urgency=low
+Build System
+------------
 
-  This release of notmuch requires a non-reversable database upgrade
-  to support the new path: and updated folder: prefixes. Notmuch
-  will backup your tags for your before doing the upgrade, but it
-  never hurts to make your own backup with notmuch dump before
-  next running 'notmuch new'
+`configure` no longer uses the special variable BASH, as this causes
+problems on systems where /bin/sh is bash.
 
- -- David Bremner <bremner@debian.org>  Tue, 22 Apr 2014 09:32:11 +0900
+Notmuch 0.28 (2018-10-12)
+=========================
 
-notmuch (0.17-1) unstable; urgency=low
+General
+-------
 
-  Previously on big endian architectures like sparc and powerpc the
-  computation of SHA1 hashes was incorrect. This meant that messages
-  with overlong or missing message-ids were given different computed
-  message-ids than on more common little endian architectures like
-  i386 and amd64.  If you use notmuch on a big endian architecture,
-  you are strongly advised to make a backup of your tags using
-  `notmuch dump` before this upgrade.  You can locate the affected
-  files using something like:
+Improve threading
 
-  notmuch dump | \
-    awk '/^notmuch-sha1-[0-9a-f]{40} / \
-      {system("notmuch search --exclude=false --output=files id:" $1)}'
+  The threading algorithm has been updated to consider all references,
+  not just the heuristically chosen parent (e.g. when that parent is
+  not in the database). The heuristic for choosing a parent message
+  has also been updated to again consider the In-Reply-To header, if
+  it looks sensible. Re-indexing might be needed to take advantage of
+  the latter change.
 
- -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:31:16 -0400
+Handle mislabelled Windows-1252 parts
 
-notmuch (0.16-1) unstable; urgency=low
+  Messages that contain Windows-1252 are apparently frequently
+  mislabelled as ISO 8859-1. Use GMime functionality to apply the
+  correct encoding for such messages.
 
-  The vim interface has been rewritten from scratch. In particular
-  it requires a version of vim with ruby support.
+Command Line Interface
+----------------------
 
- -- David Bremner <bremner@debian.org>  Sat, 16 Feb 2013 08:12:02 -0400
+Support relative database paths
 
-notmuch (0.14-1) unstable; urgency=low
+  Database paths (i.e. parameters to `notmuch config set
+  database.path`) without a leading `/` are now interpreted relative
+  to $HOME of the invoking user.
 
-  There is an incompatible change in option syntax for dump and restore
-  in this release. Please update your scripts.
+Emacs
+-----
 
-  From upstream NEWS:
+Improve stderr handling
 
-  The deprecated positional output file argument to notmuch dump has
-  been replaced with an --output option. The input file positional
-  argument for restore has been replaced with an --input option for
-  consistency with dump.
+  Add a real sentinel process to clean up stderr buffer. This is
+  needed on e.g. macOS.
 
- -- David Bremner <bremner@debian.org>  Sun, 05 Aug 2012 11:52:49 -0300
+Call `notmuch-mua-send-hook` hooks when sending a message
 
-notmuch (0.6~238) unstable; urgency=low
+  This hook was documented, but not functional for a very long time.
 
-  The emacs user interface to notmuch is now contained in a separate
-  package called notmuch-emacs.
+Completion
+----------
 
- -- David Bremner <bremner@debian.org>  Mon, 20 Jun 2011 23:57:55 -0300
+The zsh completion has been updated to cover most of the notmuch
+CLI. Internally it uses regexp searching, so needs at least Notmuch
+0.24.
+
+Build System
+------------
+
+The build system now installs notmuch-mutt and notmuch-emacs-mua with
+absolute shebangs, following the conventions of most Linux
+distributions.
+
+Test Suite
+----------
+
+Fix certain tests that were failing with GMime 2.6. Users are reminded
+that support for versions of GMime before 3.0.3 has been deprecated
+since Notmuch 0.25.
+
+Notmuch 0.27 (2018-06-13)
+=========================
+
+General
+-------
+
+Add support for thread:{} queries
+
+  Queries of the form `thread:{foo} and thread:{bar}` match threads
+  containing (possibly distinct) messages matching foo and bar. See
+  `notmuch-search-terms(7)` for details.
+
+Command Line Interface
+----------------------
+
+Add the --full-scan option to `notmuch new`
+
+  This option disables mtime based optimization of scanning for new mail.
+
+Add new --decrypt=stash option for `notmuch show`
+
+  This facilitates a workflow for encrypted messages where message
+  cleartext are indexed on first read, but the user's decryption key
+  does not have to be available during message receipt.
+
+Documentation
+-------------
+
+An initial manual for `notmuch-emacs` is now installed by default (in
+`info` format).
+
+Dependencies
+------------
+
+As of this release, support for versions of Xapian before 1.4.0 is
+deprecated, and may disappear in a future release of notmuch.
+
+Notmuch 0.26.2 (2018-04-28)
+===========================
+
+Library Changes
+---------------
+
+Work around Xapian bug with `get_mset(0,0, x)`
+
+  This causes aborts in `_notmuch_query_count_documents` on
+  e.g. Fedora 28.  The underlying bug is fixed in Xapian commit
+  f92e2a936c1592, and will be fixed in Xapian 1.4.6.
+
+Make thread indexing more robust against reference loops
+
+  Choose a thread root by date in case of reference loops. Fix a
+  related abort in `notmuch show`.
+
+Notmuch 0.26.1 (2018-04-02)
+===========================
+
+Library Changes
+---------------
+
+Bump the library minor version. This should have happened in 0.26, but
+better late than never.
+
+
+Notmuch 0.26 (2018-01-09)
+=========================
+
+Command Line Interface
+----------------------
+
+Support for re-indexing existing messages
+
+  There is a new subcommand, `notmuch reindex`, which re-indexes all
+  messages matching supplied search terms.  This permits users to
+  change the way specific messages are indexed.
+
+  Note that for messages with multiple variants in the message
+  archive, the recorded Subject: of may change upon reindexing,
+  depending on the order in which the variants are indexed.
+
+Improved error reporting in notmuch new
+
+  Give more details when reporting certain Xapian exceptions.
+
+Support maildir synced tags in `new.tags`
+
+  Tags `draft`, `flagged`, `passed`, and `replied` are now supported
+  in `new.tags`. The tag `unread` is still special in the presence of
+  maildir syncing, and will be added for files in `new/` regardless of
+  the setting of `new.tags`.
+
+Support /regex/ in new.ignore
+
+  Files and directories may be ignored based on regular expressions.
+
+Allow `notmuch insert --folder=""`
+
+  This inserts into the top level folder.
+
+Strip trailing '/' from folder path for notmuch insert
+
+  This prevents a potential problem with duplicated database records.
+
+New option --output=address for notmuch address
+
+Make `notmuch show` more robust against deleting duplicate files
+
+The option --decrypt now takes an explicit argument
+
+  The --decrypt option to `notmuch show` and `notmuch reply` now takes
+  an explicit argument.  If you were used to invoking `notmuch show
+  --decrypt`, you should switch to `notmuch show --decrypt=true`.
+
+Boolean and keyword arguments now take a `--no-` prefix
+
+Encrypted Mail
+--------------
+
+Indexing cleartext of encrypted e-mails
+
+  It's now possible to include the cleartext of encrypted e-mails in
+  the notmuch index.  This makes it possible to search your encrypted
+  e-mails with the same ease as searching cleartext.  This can be done
+  on a per-message basis by passing --decrypt=true to indexing
+  commands (new, insert, reindex), or by default by running "notmuch
+  config set index.decrypt true".
+
+  Encrypted messages whose cleartext is indexed will typically also
+  have their session keys stashed as properties associated with the
+  message.  Stashed session keys permit rapid rendering of long
+  encrypted threads, and disposal of expired encryption-capable keys.
+  If for some reason you want cleartext indexing without stashed
+  session keys, use --decrypt=nostash for your indexing commands (or
+  run "notmuch config set index.decrypt nostash"). See `index.decrypt`
+  in notmuch-config(1) for more details.
+
+  Note that stashed session keys permit reconstruction of the
+  cleartext of the encrypted message itself, and the contents of the
+  index are roughly equivalent to the cleartext as well.  DO NOT USE
+  this feature without considering the security of your index.
+
+Emacs
+-----
+
+Guard against concurrent searches in notmuch-tree
+
+Use make-process when available
+
+  This allows newer Emacs to separate stdout and stderr from the
+  notmuch command without using temporary files.
+
+Library Changes
+---------------
+
+Indexing files with duplicate message-id
+
+  Files with duplicate message-id's are now indexed, and searchable
+  via terms and phrases. There are known issues related to
+  presentation of results and regular-expression search, but in
+  principle no mail file should be completely unsearchable now.
+
+New functions to count files
+
+  Two new functions in the libnotmuch API:
+  `notmuch_message_count_files`, and `notmuch_thread_get_total_files`.
+
+New function to remove properties
+
+  A new function was added to the libnotmuch API to make it easier to
+  drop all properties with a common pattern:
+  `notmuch_message_remove_all_properties_with_prefix`
+
+Change of return value of `notmuch_thread_get_authors`
+
+  In certain corner cases, `notmuch_thread_get_authors` previously
+  returned NULL.  This has been replaced by an empty string, since the
+  possibility of NULL was not documented.
+
+Transition `notmuch_database_add_message` to `notmuch_database_index_file`
+
+  When indexing an e-mail message, the new
+  `notmuch_database_index_file` function is the preferred form, and
+  the old `notmuch_database_add_message` is deprecated.  The new form
+  allows passing a set of options to the indexing engine, which the
+  operator may decide to change from message to message.
+
+Test Suite
+----------
+
+Out-of-tree builds
+
+  The test suite now works properly with out-of-tree builds, i.e. with
+  separate source and build directories. The --root option to tests
+  has been dropped. The same can now be achieved more reliably using
+  out-of-tree builds.
+
+Python Bindings
+---------------
+
+Python bindings specific Debian packaging is removed
+
+  The bindings have been build by the top level Debian packaging for a
+  long time, and `bindings/python/debian` has bit-rotted.
+
+Open mail files in binary mode when using Python 3
+
+  This avoids certain encoding related crashes under Python 3.
+
+Add python bindings for `notmuch_database_{get,set}_config*`
+
+Optional `decrypt_policy` flag is available for notmuch.database().index_file()
+
+nmbug
+-----
+
+nmbug's internal version increases to 0.3 in this notmuch release.
+User-facing changes with this notmuch release:
+
+* Accept failures to unset `core.worktree` in `clone`, which allows
+  nmbug to be used with Git 2.11.0 and later.
+* Auto-checkout in `clone` if it wouldn't clobber existing content,
+  which makes the initial clone more convenient.
+* Only error for invalid diff lines in `tags/`, which allows for
+  `README`s and similar in nmbug repositories.
+
+Documentation
+-------------
+
+New man page: notmuch-properties(7)
+
+  This new page to the manual describes common conventions for how
+  properties are used by libnotmuch, the CLI, and associated programs.
+  External projects that use properties are encouraged to claim their
+  properties and conventions here to avoid collisions.
+
+Notmuch 0.25.3 (2017-12-08)
+===========================
+
+Emacs
+-----
+
+Extend mitigation (disabling handling x-display in text/enriched) for
+Emacs bug #28350 to Emacs versions before 24.4 (i.e. without
+`advice-add`).
+
+Command Line Interface
+----------------------
+
+Correctly report userid validity. Fix test suite failure for GMime >=
+3.0.3. This change raises the minimum supported version of GMime 3.x
+to 3.0.3.
+
+Notmuch 0.25.2 (2017-11-05)
+===========================
+
+Command Line Interface
+----------------------
+
+Fix segfault in notmuch-show crypto handling when compiled against
+GMime 2.6; this was a regression in 0.25.
+
+General
+-------
+
+Support for GMime before 3.0 is now deprecated, and will be removed in
+a future release.
+
+Notmuch 0.25.1 (2017-09-11)
+===========================
+
+Emacs
+-----
+
+Disable handling x-display in text/enriched messages. Mitigation for
+Emacs bug #28350.
+
+Notmuch 0.25 (2017-07-25)
+=========================
+
+General
+-------
+
+Add regexp searching for mid, paths, and tags.
+
+Skip HTML tags when indexing
+
+  In particular this avoids indexing large inline images.
+
+Command Line Interface
+----------------------
+
+Bash completion is now installed to /usr/share by default.
+
+Allow space as separator for keyword arguments.
+
+Emacs
+-----
+
+Support for stashing message timestamp in show and tree views
+
+  Invoking `notmuch-show-stash-date` with a prefix argument
+  stashes the unix timestamp of the current message instead of
+  the date string.
+
+Don't use 'function' as variable name, workaround emacs bug 26406.
+
+Library Changes
+---------------
+
+Add workaround for date parsing of bad input in older GMime
+
+  In certain circumstances, older GMime libraries could return
+  negative numbers when parsing syntactically invalid dates.
+
+Replace deprecated functions with status returning versions
+
+  API of notmuch_query_{search,count}_{messages,threads} has
+  changed.  notmuch_query_add_tag_exclude now returns a status
+  value.
+
+Add support for building against GMime 3.0.
+
+Rename libutil.a to libnotmuch_util.a.
+
+libnotmuch SONAME is incremented to libnotmuch.so.5.
+
+Notmuch 0.24.2 (2017-06-01)
+===========================
+
+Command Line Interface
+----------------------
+
+Fix output from `notmuch dump --include=properties` to not include tags.
+
+Emacs
+-----
+
+Fix filename stashing in tree view.
+
+Notmuch 0.24.1 (2017-04-01)
+===========================
+
+General
+-------
+
+Fix regressions in non-regexp search for `from:` and `subject:`
+
+  The regexp search code in 0.24 introduced a regression in the
+  handling of empty queries and wildcards. These are both corrected in
+  this release.
+
+Command Line Interface
+----------------------
+
+Fix several memory leaks in `notmuch show`
+
+Update NEWS for 0.24 to mention schema changes
+
+Fix bug in dump header
+
+  The previous version of the dump header failed to mention the
+  inclusion of tags. This fix bumps the version number of the dump
+  format to 3. There are no other changes to the format.
+
+Library Changes
+---------------
+
+Fix a read-after-free in the library.
+
+Notmuch 0.24 (2017-03-12)
+=========================
+
+General
+-------
+
+Regular expression searches supported for `from:` and `subject:`
+
+  This requires recent Xapian (1.4+) See notmuch-search-terms(7) for
+  details.
+
+Command Line Interface
+----------------------
+
+Run external `notmuch-` prefixed commands as subcommands
+
+  You can now add your own `notmuch-` prefixed commands in PATH, and
+  have notmuch run them as if they were notmuch commands. See the
+  `notmuch(1)` man page for details
+
+New default output format to 3
+
+  See devel/schemata for details. Users of the structured output
+  format are reminded of the `--format-version` argument to `notmuch
+  show` and `notmuch search` which can prevent breakage when the
+  default format changes.
+
+Emacs
+-----
+
+Postpone and resume messages in `notmuch-message-mode` (composition)
+
+  Notmuch now has built in support for postponing, saving and resuming
+  messages. The default bindings are C-x C-s to save a draft, C-c C-p
+  to postpone a draft (save and exit compose buffer), and "e" in show
+  or tree view to resume.
+
+  Draft messages are tagged with `notmuch-draft-tags` (draft by
+  default) so you may wish to add that to the excluded tags list. When
+  saving a previously saved draft message the earlier draft gets
+  tagged deleted.
+
+  Note that attachments added before postponing will be included as
+  they were when you postponed in the final message.
+
+Address Completion
+
+  It is now possible to save the list of address completions for
+  notmuch's internal completion between runs of emacs. This makes the
+  first calls to address completion much better and faster. For
+  privacy reasons it is disabled by default, to enable set or
+  customize `notmuch-address-save-filename`.
+
+Tag jump menu
+
+  It is now possible to configure tagging shortcuts (with an interface
+  like notmuch jump). For example (by default) k u will remove the
+  unread tag, and k s will add a tag "spam" and remove the inbox
+  tag. Pressing k twice will do the reverse operation so, for example,
+  k k s removes the spam tag and adds the inbox tag. See the customize
+  variable `notmuch-tagging-keys` for more information.
+
+Refresh all buffers
+
+  It is now possible to refresh all notmuch buffers to reflect the
+  current state of the database with a single command, `M-=`.
+
+Stop display of `application/*` parts
+
+  By default gnus displays all `application/*` parts such as
+  application/zip in the message buffer. This has several undesirable
+  effects for notmuch (security, triggering errors etc). Notmuch now
+  overrides this and does not display them by default. If you have
+  customized `mm-inline-override-types` then we assume you know what
+  you want and do not interfere; if you do want to stop the display of
+  `application/*` add `application/*` to your customization. If you want
+  to allow `application/*` then set `mm-inline-override-types` to
+  "non/existent".
+
+Small change in the api for notmuch-search-tag
+
+  When `notmuch-search-tag` is called non-interactively and the region
+  is set, then it only tags the threads in the region. (Previously it
+  only tagged the current thread.)
+
+Bugfix for sending messages with very long headers
+
+  Previously emacs didn't fold very long headers when sending which
+  could cause the MTA to refuse to send the message. This makes sure
+  it does fold any long headers so the message is RFC compliant.
+
+`notmuch emacs-mua` command installed with the Emacs interface
+
+  We've carried a `notmuch-emacs-mua` script in the source tree for
+  quite some time. It can be used to launch the Notmuch Emacs
+  interface from the command line in many different ways. Starting
+  with this release, it will be installed with the Emacs
+  interface. With the new external subcommand support, the script
+  transparently becomes a new notmuch command. See the
+  `notmuch-emacs-mua(1)` man page for details.
+
+Notmuch Emacs desktop integration
+
+  The desktop integration file will now be installed with the Notmuch
+  Emacs interface, adding a Notmuch menu item and configuration to
+  allow the user to set up Notmuch Emacs as the `mailto:` URL handler.
+
+Library changes
+---------------
+
+`notmuch_query_count_messages` is now non-destructive
+
+  Internally the implementation of excludes has changed to make this
+  possible.
+
+Improved handling of DatabaseModifiedError
+
+  Previously uncaught exceptions reading message metadata are now
+  handled.
+
+Notmuch 0.23.7 (2017-02-28)
+===========================
+
+Test Suite
+----------
+
+Drop use of gpgconf --create-socketdir. Move $GNUPGHOME to /tmp.
+
+  It turns out the hardcoded use of /run/user in gpg doesn't work out
+  that well in some environments. The more low tech fix is to move all
+  of $GNUPGHOME to somewhere where we can control the length of the
+  paths.
+
+Notmuch 0.23.6 (2017-02-27)
+===========================
+
+Command Line Interface
+----------------------
+
+Fix read-after-free bug in `notmuch new`.
+
+Test Suite
+----------
+
+Use gpgconf --create-socketdir if available.
+
+  GnuPG has a facility to use sockets in /run or /var/run to avoid
+  problems with long socket paths, but this is not enabled by default
+  for GNUPGHOME other than $HOME/.gnupg. Enable it, if possible.
+
+Notmuch 0.23.5 (2017-01-09)
+===========================
+
+Build system
+------------
+
+Fix quoting bug in configure. This had introduced a RUNPATH into the
+notmuch binary in cases where it was not not needed.
+
+Notmuch 0.23.4 (2016-12-24)
+===========================
+
+Command Line Interface
+----------------------
+
+Improve error handling in notmuch insert
+
+  Database lock errors no longer prevent message file delivery to the
+  filesystem.  Certain errors during `notmuch insert` most likely to
+  be temporary return EX_TEMPFAIL.
+
+Emacs
+-----
+
+Restore autoload cookie for notmuch-search.
+
+Notmuch 0.23.3 (2016-11-27)
+===========================
+
+Command Line Interface
+----------------------
+
+Treat disappearing files during notmuch new as non-fatal.
+
+Test Suite
+----------
+
+Fix incompatibility (related to signature size) with gnupg 2.1.16.
+
+Notmuch 0.23.2 (2016-11-20)
+===========================
+
+Emacs
+-----
+
+Fix notmuch-interesting-buffer and notmuch-cycle-notmuch-buffers.
+
+  notmuch-tree-mode and notmuch-message-mode buffers are now
+  considered interesting by `notmuch-interesting-buffer` and
+  `notmuch-cycle-notmuch-buffers`.
+
+Restore compatibility with Emacs 23.
+
+  Notmuch support for Emacs 23 is now deprecated.
+
+Notmuch 0.23.1 (2016-10-23)
+===========================
+
+General
+-------
+
+Require Xapian >= 1.2.6
+
+  The ifdef branch for older Xapian (pre-compact API) had bit-rotted.
+
+Emacs
+-----
+
+Fix default colours for unread and flagged messages
+
+  In 0.23 the default colours for unread and flagged messages in
+  search view were accidentally swapped. This release returns them to
+  the original colours.
+
+  A related change in 0.23 broke the customize widget for
+  notmuch-search-line-faces. This is now fixed.
+
+Fix test failure with Emacs 25.1
+
+  A previously undiscovered jit-lock related bug was exposed by Emacs
+  25, causing a notmuch-show mode test to fail. This release fixes the
+  bug, and hence the test.
+
+Notmuch 0.23 (2016-10-03)
+=========================
+
+General (Xapian 1.4+)
+---------------------
+
+Compiling against Xapian 1.4 enables several new features.
+
+Support for single argument date: queries
+
+  `date:<expr>` is equivalent to `date:<expr>..<expr>`.
+
+Support for blocking opens
+
+  When opening a database notmuch by default will wait for another
+  process to release a write lock, rather than returning an error.
+
+Support for named queries
+
+  Named queries (also known as 'saved searches') can be defined with a
+  `query:name` format. The expansion of these queries is stored in the
+  database and they can be used from any notmuch client.
+
+Library
+-------
+
+Message property API
+
+  libnotmuch now supports the attachment of arbitrary key-value pairs
+  to messages. These can be used by various tools to manage their
+  private data without polluting the user tag space. They also support
+  iteration of values with the same key or same key prefix.
+
+Bug fix for `notmuch_directory_set_mtime`
+
+  Update cached mtime to match on-disk mtime.
+
+CLI
+---
+
+Support for compile time options
+
+  A group of `built_with` keys is now supported for notmuch
+  config. Initial keys in this group are `compact`, `field_processor`,
+  and `retry_lock`.
+
+Dump/Restore support for configuration information and properties
+
+  Any configuration information stored in the database (initially just
+  named queries) is dumped and restored. Similarly any properties
+  attached to messages are also dumped and restored. Any new
+  information in the dump format is prefixed by '#' to allow existing
+  scripts to ignore it.
+
+Emacs
+-----
+
+Make notmuch-message-mode use insert for fcc
+
+  Notmuch-message-mode now defaults to using notmuch insert for
+  fcc. The old file based fcc behaviour can be restored by setting the
+  defcustom `notmuch-maildir-use-notmuch-insert` to nil.
+
+  When using notmuch insert, `notmuch-fcc-dirs` must be a subdirectory
+  of the mailstore (absolute paths are not permitted) followed by any
+  tag changes to be applied to the inserted message. The tag changes
+  are applied after the default tagging for new messages. For example
+  setting the header to "sentmail -inbox +sent" would insert the
+  message in the subdirectory sentmail of the mailstore, add the tag
+  "sent", and not add the (normally added) "inbox" tag.
+
+  Finally, if the insert fails (e.g. if the database is locked) the
+  user is presented with the option to retry, ignore, or edit the
+  header.
+
+Make internal address completion customizable
+
+  There is a new defcustom `notmuch-address-internal-completion` which
+  controls how the internal completion works: it allows the user to
+  choose whether to match on messages the user sent, or the user
+  received, and to filter the messages used for the match, for example
+  by date.
+
+Allow internal address completion on an individual basis
+
+  There is a new function `notmuch-address-toggle-internal-completion`
+  (by default it has no keybinding) which allows users who normally
+  use an external completion command to use the builtin internal
+  completion for the current buffer.
+
+  Alternatively, if the user has company-mode enabled, then the user
+  can use company mode commands such as `company-complete` to
+  activate the builtin completion for an individual completion.
+
+Resend messages
+
+  The function `notmuch-show-resend-message` (bound to `b` in show
+  and tree modes) will (attempt to) send current message to new
+  recipients. The headers of the message won't be altered (e.g. `To:`
+  may point to yourself). New `Resent-To:`, `Resent-From:` and so on
+  will be added instead.
+
+Face customization is easier
+
+  New faces `notmuch-tag-unread`, `notmuch-tag-flagged`,
+  `notmuch-tag-deleted`, `notmuch-tag-added`,
+  `notmuch-search-flagged-face` and `notmuch-search-unread-face` are
+  now used by default. Customize `notmuch-faces` to modify them.
+
+Omit User-Agent header by default when sending mail
+
+Ruby Bindings
+-------------
+
+Add support for `notmuch_database_get_all_tags`
+
+Go Bindings
+-----------
+
+Go bindings moved to contrib
+
+Add support for `notmuch_threads_t` and `notmuch_thread_t`
+
+Fixed constant values so they are not all zero anymore
+
+  Previously, it was impossible to open writable database handles,
+  because `DATABASE_MODE_READ_ONLY` and `DATABASE_MODE_READ_WRITE` were
+  both set to zero.
+  The same issue occurred with sort modes.
+
+Notmuch 0.22.2 (2016-09-08)
+===========================
+
+Test Suite
+----------
+
+Silence gdb more
+
+  Have gdb write to a log file instead of stdout, hiding some more
+  (harmless) stderr chatter which causes test failures.
+
+Hardcode fingerprint in PGP/MIME tests
+
+  Make the tests more robust against changing GnuPG output formats.
+
+Notmuch 0.22.1 (2016-07-19)
+===========================
+
+Library
+-------
+
+Correct the definition of `LIBNOTMUCH_CHECK_VERSION`.
+
+Document the (lack of) operations permitted on a closed database.
+
+Test Suite
+----------
+
+Fix race condition in dump / restore tests.
+
+Notmuch-Mutt
+------------
+
+Use `env` to locate perl.
+
+Emacs
+-----
+
+Tell `message-mode` mode that outgoing messages are mail
+
+  This makes message-mode configuration behave more predictably.
+
+Respect charset of MIME parts when reading them
+
+  Fix previous assumption that everyone uses UTF-8.
+
+Notmuch 0.22 (2016-04-26)
+=========================
+
+General
+-------
+
+Xapian 1.3 support
+
+  Notmuch should now build (and the test suite should pass) on recent
+  releases of Xapian 1.3.x. It has been tested with Xapian 1.3.5.
+
+Limited support for S/MIME messages
+
+  Signature verification is supported, but not decryption. S/MIME
+  signature creation and S/MIME encryption are supported via built-in
+  support in Emacs. S/MIME support is not extensively tested at this
+  time.
+
+Bug Fixes
+
+   Fix for threading bug involving deleting and re-adding
+   messages. Fix for case-sensitive content disposition headers. Fix
+   handling of 1 character directory names at top level.
+
+Command Line Interface
+----------------------
+
+`notmuch show` now supports verifying S/MIME signatures
+
+  This support relies on an appropriately configured `gpgsm`.
+
+Build System
+------------
+
+Drop dependency on "pkg-config emacs".
+
+Emacs Interface
+---------------
+
+Notmuch replies now include all parts shown in the show view
+
+  There are two main user visible changes. The first is that rfc822
+  parts are now included in replies.
+
+  The second change is that part headers are now included in the reply
+  buffer to provide visible separation of the parts. The choice of
+  which part headers to show is customizable via the variable
+  `notmuch-mua-reply-insert-header-p-function`.
+
+Filtering or Limiting messages is now bound to `l` in the search view
+
+  This binding now matches the analogous binding in show view.
+
+`F` forwards all open messages in a thread
+
+  When viewing a thread of messages, the new binding `F` can be used
+  to generate a new outgoing message which forwards all of the open
+  messages in the thread. This is analogous to the `f` binding, which
+  forwards only the current message.
+
+Preferred content type can be determined from the message content
+
+  More flexibility in choosing which sub-part of a
+  multipart/alternative part is initially shown is available by
+  setting `notmuch-multipart/alternative-discouraged` to a function
+  that returns a list of discouraged types. The function so specified
+  is passed the message as an argument and can examine the message
+  content to determine which content types should be discouraged. This
+  is in addition to the current capabilities (i.e. setting
+  `notmuch-multipart/alternative-discouraged` to a list of discouraged
+  types).
+
+When viewing a thread ("show" mode), queries that match no messages no
+longer generate empty buffers
+
+  Should an attempt be made to view the thread corresponding to a
+  query that matches no messages, a warning message is now displayed
+  and the terminal bell rung rather than displaying an empty buffer
+  (or, in some cases, displaying an empty buffer and throwing an
+  error). This also affects re-display of the current thread.
+
+Handle S/MIME signatures in emacs
+
+  The emacs interface is now capable making and verifying S/MIME
+  signatures.
+
+`notmuch-message-address-insinuate` is now a no-op
+
+  This reduces the amount of interference with non-notmuch uses of
+  message-mode.
+
+Address completion improvements
+
+  An external script is no longer needed for address completion; if
+  you previously configured one, customize the variable
+  `notmuch-address-command` to try the internal completion. If
+  `company-mode` is available, notmuch uses it by default for
+  interactive address completion.
+
+Test and experiment with the emacs MUA available in source tree
+
+  `./devel/try-emacs-mua` runs emacs and fills the window with
+  information how to try the MUA safely. Emacs is configured to use
+  the notmuch (lisp) files located in `./emacs` directory.
+
+Documentation
+-------------
+
+New `notmuch-report(1)` and `notmuch-report.json(5)` man pages
+describe `notmuch-report` and its JSON configuration file.  You can
+build these files by running `make` in the `devel/nmbug/doc`
+directory.
+
+notmuch-report
+--------------
+
+Renamed from `nmbug-status`.  This script generates reports based on
+notmuch queries, and doesn't really have anything to do with nmbug,
+except for sharing the `NMBGIT` environment variable.  The new name
+focuses on the script's action, instead of its historical association
+with the nmbug workflow.  This should make it more discoverable for
+users looking for generic notmuch reporting tools.
+
+The default configuration file name (extracted from the `config`
+branch of `NBMGIT` has changed from `status-config.json` to
+`notmuch-report.json` so it is more obviously associated with the
+report-generating script.  The configuration file also has a new
+`meta.message-url` setting, which is documented in
+`notmuch-report.json(5)`.
+
+`notmuch-report` now wraps query phrases in parentheses when and-ing
+them together, to avoid confusion about clause grouping.
+
+Notmuch 0.21 (2015-10-29)
+=========================
+
+General
+-------
+
+Notmuch now requires gmime >= 2.6.7. The gmime 2.4 series is no longer
+supported.
+
+Database revision tracking: `lastmod:` queries
+
+  Each message now has a metadata revision number that increases with
+  every tagging operation. See the discussion of `lastmod:` in
+  `notmuch-search-terms(7)` for more information.
+
+Date queries now support `date:<expr>..!` shorthand for
+`date:<expr>..<expr>`
+
+  You can use, for example, `date:yesterday..!` to match from the
+  beginning of yesterday to the end of yesterday. For further details,
+  please refer to the `notmuch-search-terms` manual page.
+
+Notmuch database upgrade to support `lastmod:` queries
+
+  The above mentioned `lastmod:` prefix. This will be done
+  automatically, without prompting on the next time `notmuch new` is
+  run after the upgrade. The upgrade is not reversible, and the
+  upgraded database will not be readable by older versions of
+  Notmuch. As a safeguard, a database dump will be created in the
+  `.notmuch` directory before upgrading.
+
+Build System
+------------
+
+The ruby bindings are now built as part of the main notmuch build
+process. This can be disabled with the `--without-ruby` option to
+configure.
+
+Building the documentation can be disabled with the `--without-docs`
+option to configure.
+
+Skipped individual tests are no longer considered as failures.
+
+Command Line Interface
+----------------------
+
+Database revision tracking
+
+  Two new options were added to support revision tracking. A global
+  option "--uuid" (`notmuch(1)`) was added for to detect counter
+  rollover and reinitialization, and `notmuch-count(1)` gained a
+  `--lastmod` option to query database revision tracking data.
+
+The `notmuch address` command supports new deduplication schemes
+
+  `notmuch address` has gained a new `--deduplicate` option to specify
+  how the results should be deduplicated, if at all. The alternatives
+  are `no` (do not deduplicate, useful for processing the results with
+  external tools), `mailbox` (deduplicate based on the full, case
+  sensitive name and email address), and `address` (deduplicate based
+  on the case insensitive address part). See the `notmuch-address`
+  manual page for further information.
+
+Emacs Interface
+---------------
+
+`notmuch-emacs-version` is used in `User-Agent` header
+
+  The value of recently introduced variable `notmuch-emacs-version` is
+  now used as a part of `User-Agent` header when sending emails.
+
+Removed `notmuch-version` function by renaming it to `notmuch-cli-version`
+
+  With existing variable `notmuch-emacs-version` the accompanied
+  function which retrieves the version of `notmuch-command` is
+  better named as `notmuch-cli-version`.
+
+Query input now supports completion for "is:<tag>"
+
+New message composition mode: `notmuch-compose-mode`
+
+  This is mainly to fix fcc handling, but may be useful for user
+  customization as well.
+
+Allow filtering of search results in `notmuch-show`
+
+Add function to rerun current tree-view search in search mode
+
+Bug fix for replying to encrypted messages in `notmuch-tree` mode
+
+Allow saved searched to specify tree view rather than search view
+
+  Applies to saved searches run from `notmuch-hello`, or by a keyboard
+  shortcut (`notmuch-jump`).  Can be set in the customize interface, or
+  by adding :search-type tree to the appropriate saved search plist in
+  `notmuch-saved-searches`.
+
+Increase maximum size of rendered text parts
+
+  The variable `notmuch-show-max-text-part-size` controls the maximum
+  size (in bytes) which is automatically rendered. This may make
+  rendering large threads slower. To get the previous behaviour set
+  this variable to 10000.
+
+Library
+-------
+
+The use of absolute paths is now enforced when calling
+`notmuch_database_{open, create}`
+
+New function `notmuch_directory_delete` to delete directory documents
+
+  Previously there was no way to delete directory documents from the
+  database, leading to confusing results when the "ghost" directory
+  document of a renamed or deleted filesystem directory was
+  encountered every time the parent directory was being scanned by
+  `notmuch new`. The mtime of the old directory document was also used
+  if a directory by the same name was added again in the filesystem,
+  potentially bypassing the scan for the directory. The issues are
+  fixed by providing a library call to delete directory documents, and
+  deleting the old documents in `notmuch new` on filesystem directory
+  removal or rename.
+
+Database revision tracking
+
+  Revision tracking is supported via a new prefix "lastmod:" in the
+  query parser and the new function
+  `notmuch_database_get_revision`. For the latter, see `notmuch(3)`.
+
+New status code returning API for n_query_count_{messages,threads}
+
+Deprecated functions
+
+  `notmuch_query_search_threads`, `notmuch_query_search_messages`,
+  `notmuch_query_count_messages`, and `notmuch_query_count_threads`
+  are all deprecated as of this release.  Clients are encouraged to
+  transition to the `_st` variants supporting better error reporting.
+
+nmbug-status
+------------
+
+`nmbug-status` now supports specifying the sort order for each view.
+
+Notmuch 0.20.2 (2015-06-27)
+===========================
+
+Emacs Interface
+---------------
+
+Bug fix for marking messages read in `notmuch-tree` mode.
+
+Notmuch 0.20.1 (2015-06-01)
+===========================
+
+Test Suite
+----------
+
+Work around apparent gdb bug on arm64.
+
+Notmuch 0.20 (2015-05-31)
+=========================
+
+Command-Line Interface
+----------------------
+
+There is a new `mimetype:` search prefix
+
+  The new `mimetype:` search prefix allows searching for the
+  content-type of attachments, which is now indexed. See the
+  `notmuch-search-terms` manual page for details.
+
+Path to gpg is now configurable
+
+  On systems with multiple versions of gpg, you can tell
+  notmuch which one to use by setting `crypto.gpg_path`
+
+Emacs
+-----
+
+Avoid rendering large text attachments.
+
+Improved rendering of CID references in HTML.
+
+Vim
+---
+
+Vim client now respects excluded tags.
+
+Notmuch-Mutt
+------------
+
+Support messages without Message-IDs.
+
+Library
+-------
+
+Undeprecate single message mboxes
+
+  It seems more trouble to remove this feature than expected, so
+  `notmuch new` will no longer nag about mboxes with a single message.
+
+New error logging facility
+
+  Clients should call `notmuch_database_status_string` to retrieve
+  output formerly printed to stderr.
+
+Several bug fixes related to stale iterators
+
+New status code returning API for n_query_search_{messages,thread}
+
+Fix for library `install_name` on Mac OS X
+
+Fix for rounding of seconds
+
+Documentation
+-------------
+
+Sphinx is now mandatory to build docs
+
+  Support for using rst2man in place of sphinx to build the
+  documentation has been removed.
+
+Improved notmuch-search-terms.7
+
+  The man page `notmuch-search-terms(7)` has been extended, merging
+  some material from the relicensed wiki.
+
+Contrib
+-------
+
+`notmuch-deliver` is removed. As far as we know, all functionality
+previously provided by `notmuch-deliver` should now be provided by
+`notmuch insert`, provided by the main notmuch binary.
+
+nmbug-status
+------------
+
+`nmbug-status` now only matches local branches when reading
+`status-config.json` from the `config` branch of the `NMBGIT`
+repository.  To help new users running `nmbug-status`, `nmbug clone`
+now creates a local `config` branch tracking `origin/config`.  Folks
+who use `nmbug-status` with an in-Git config (i.e. you don't use the
+`--config` option) who already have `NMBGIT` set up are encouraged to
+run:
+
+    git checkout config origin/config
+
+in their `NMBGIT` repository (usually `~/.nmbug`).
+
+Notmuch 0.19 (2014-11-14)
+=========================
+
+Overview
+--------
+
+This release improves the reliability of `notmuch dump` and the error
+handling for `notmuch insert`. The new `notmuch address` command is
+intended to make searching for email addresses more convenient. At the
+library level the revised handling of missing messages fixes at least
+one bug in threading. The release also includes several interface
+improvements to the emacs interface, most notably the ability to bind
+keyboard shortcuts to saved searches.
+
+Command-Line Interface
+----------------------
+
+Stopped `notmuch dump` failing if someone writes to the database
+
+  The dump command now takes the write lock when running. This
+  prevents other processes from writing to the database during the
+  dump which would cause the dump to fail. Note, if another notmuch
+  process already has the write lock the dump will not start, so
+  script callers should still check the return value.
+
+`notmuch insert` requires successful message indexing for success status
+
+  Previously the `notmuch insert` subcommand indicated success even if
+  the message indexing failed, as long as the message was delivered to
+  file system. This could have lead to delivered messages missing
+  tags, etc. `notmuch insert` is now more strict, also requiring
+  successful indexing for success status. Use the new `--keep` option
+  to revert to the old behaviour (keeping the delivered message file
+  and returning success even if indexing fails).
+
+`notmuch insert` has gained support for `post-insert` hook
+
+  The new `post-insert` hook is run after message delivery, similar to
+  `post-new`. There's also a new option `notmuch insert --no-hooks` to
+  skip the hook. See the notmuch-hooks(1) man page for details.
+
+`notmuch deliver` is deprecated
+
+  With this release we believe that `notmuch insert` has reached
+  parity with `notmuch deliver`. We recommend that all users of
+  `notmuch deliver` switch to `notmuch insert` as the former is
+  currently unmaintained.
+
+`notmuch search` now supports `--duplicate=N` option with `--output=messages`
+
+  Complementing the `notmuch search --duplicate=N --output=files`
+  options, the new `--duplicate=N --output=messages` combination
+  limits output of message IDs to messages matching search terms that
+  have at least `N` files associated with them.
+
+Added `notmuch address` subcommand
+
+  This new subcommand searches for messages matching the given search
+  terms, and prints the addresses from them. Duplicate addresses are
+  filtered out. The `--output` option controls which of the following
+  information is printed: sender addresses, recipient addresses and
+  count of duplicate addresses.
+
+Emacs Interface
+---------------
+
+Use the `j` key to access saved searches from anywhere in notmuch
+
+  `j` is now globally bound to `notmuch-jump`, which provides fast,
+  interactive keyboard shortcuts to saved searches.  For example,
+  with the default saved searches `j i` from anywhere in notmuch will
+  bring up the inbox.
+
+Improved handling of the unread tag
+
+  Notmuch now marks an open message read (i.e., removes the unread
+  tag) if point enters the message at any time in a show buffer
+  regardless of how point got there (mouse click, cursor command, page
+  up/down, notmuch commands such as n,N etc). This fixes various
+  anomalies or bugs in the previous handling. Additionally it is
+  possible to customize the mark read handling by setting
+  `notmuch-show-mark-read-function` to a custom function.
+
+Expanded default saved search settings
+
+  The default saved searches now include several more common searches,
+  as well as shortcut keys for `notmuch-jump`.
+
+Improved `q` binding in notmuch buffers
+
+  `q` will now bury rather than kill a notmuch search, show or tree
+  buffer if there are multiple windows showing the buffer. If only a
+  single window is showing the buffer, it is killed.
+
+`notmuch-show-stash-mlarchive-link-alist` now supports functions
+
+  Some list archives may use a more complicated scheme for referring
+  to messages than just concatenated URL and message ID. For example,
+  patchwork requires a query to translate message ID to a patchwork
+  patch ID. `notmuch-show-stash-mlarchive-link-alist` now supports
+  functions to better cover such cases. See the help documentation for
+  the variable for details.
+
+Library changes
+---------------
+
+Introduced database version 3 with support for "database features."
+
+  Features are independent aspects of the database schema.
+  Representing these independently of the database version number will
+  let us evolve the database format faster and more incrementally,
+  while maintaining better forwards and backwards compatibility.
+
+Library users are no longer required to call `notmuch_database_upgrade`
+
+  Previously, library users were required to call
+  `notmuch_database_needs_upgrade` and `notmuch_database_upgrade`
+  before using a writable database.  Even the CLI didn't get this
+  right, and it is no longer required.  Now, individual APIs may
+  return `NOTMUCH_STATUS_UPGRADE_REQUIRED` if the database format is
+  too out of date for that API.
+
+Library users can now abort an atomic section by closing the database
+
+  Previously there was no supported way to abort an atomic section.
+  Callers can now simply close the database, and any outstanding
+  atomic section will be aborted.
+
+Add return status to `notmuch_database_close` and
+`notmuch_database_destroy`
+
+Bug fixes and performance improvements for thread linking
+
+  The database now represents missing-but-referenced messages ("ghost
+  messages") similarly to how it represents regular messages.  This
+  enables an improved thread linking algorithm that performs better
+  and fixes a bug that sometimes prevented notmuch from linking
+  messages into the same thread.
+
+nmbug
+-----
+
+The Perl script has been translated to Python; you'll need Python 2.7
+or anything from the 3.x line.  Most of the user-facing interface is
+the same, but `nmbug help` is now `nmbug --help`, and the following nmbug
+commands have slightly different interfaces: `archive`, `commit`,
+`fetch`, `log`, `pull`, `push`, and `status`.  For details on the
+new interface for a given command, run `nmbug COMMAND --help`.
+
+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>",
+         ...
+      },
+      ...
+    }
+
+Python Bindings
+---------------
+
+Add support for `notmuch_query_add_tag_exclude`
+
+Build System
+------------
+
+The notmuch binaries and libraries are now build with debugging symbols
+by default.  Users concerned with disk space should change the
+defaults when configuring or use the strip(1) command.
+
+Notmuch 0.18.2 (2014-10-25)
+===========================
+
+Test Suite
+----------
+
+Translate T380-atomicity to use gdb/python
+
+  The new version is compatible with gdb 7.8
+
+Emacs 24.4 related bug fixes
+
+  The Messages buffer became read-only, and the generated mime
+  structure for signatures changed slightly.
+
+Simplify T360-symbol-hiding
+
+   Replace the use of `objdump` on the object files with `nm` on the
+   resulting lib.
+
+Notmuch 0.18.1 (2014-06-25)
+===========================
+
+This is a bug fix and portability release.
+
+Build System
+------------
+
+Add a workaround for systems without zlib.pc
+
+Make emacs install robust against the non-existence of emacs
+
+Put notmuch lib directory first in RPATH
+
+Fix handling of `html_static_path` in sphinx
+
+  Both the python bindings and the main docs had spurious settings of
+  this variable.
+
+Test Suite
+----------
+
+Use --quick when starting emacs
+
+  This avoids a hang in the T160-json tests.
+
+Allow pending break points in atomicity script
+
+  This allows the atomicity tests to run on several more architectures/OSes.
+
+Command-Line Interface
+----------------------
+
+To improve portability use fsync instead of fdatasync in
+`notmuch-dump`. There should be no functional difference.
+
+Library changes
+---------------
+
+Resurrect support for single-message mbox files
+
+  The removal introduced a bug with previously indexed single-message
+  mboxes.  This support remains deprecated.
+
+Fix for phrase indexing
+
+  There were several bugs where words intermingled from different
+  headers and MIME parts could match a single phrase query.  This fix
+  will affect only newly indexed messages.
+
+Emacs Interface
+---------------
+
+Make sure tagging on an empty query is harmless
+
+  Previously tagging an empty query could lead to tags being
+  unintentionally removed.
+
+Notmuch 0.18 (2014-05-06)
+=========================
+
+Overview
+--------
+
+This new release includes some enhancements to searching for messages
+by filesystem location (`folder:` and `path:` prefixes under *General*
+below).  Saved searches in *Emacs* have also been enhanced to allow
+distinct search orders for each one.  Another enhancement to the
+*Emacs* interface is that replies to encrypted messages are now
+encrypted, reducing the risk of unintentional information disclosure.
+The default dump output format has changed to the more robust
+`batch-tag` format. The previously deprecated parsing of single
+message mboxes has been removed. For detailed release notes, see
+below.
+
+General
+-------
+
+The `folder:` search prefix now requires an exact match
+
+  The `folder:` prefix has been changed to search for email messages
+  by the exact, case sensitive maildir or MH folder name. Wildcard
+  matching (`folder:foo*`) is no longer supported. The new behaviour
+  allows for more accurate mail folder based searches, makes it
+  possible to search for messages in the top-level folder, and should
+  lead to less surprising results than the old behaviour. Users are
+  advised to see the `notmuch-search-terms` manual page for details,
+  and review how the change affects their existing `folder:` searches.
+
+There is a new `path:` search prefix
+
+  The new `path:` search prefix complements the `folder:` prefix. The
+  `path:` prefix searches for email messages that are in particular
+  directories within the mail store, optionally recursively using a
+  special syntax. See the `notmuch-search-terms` manual page for
+  details.
+
+Notmuch database upgrade due to `folder:` and `path:` changes
+
+  The above mentioned changes to the `folder:` prefix and the addition
+  of `path:` prefix require a Notmuch database upgrade. This will be
+  done automatically, without prompting on the next time `notmuch new`
+  is run after the upgrade. The upgrade is not reversible, and the
+  upgraded database will not be readable by older versions of
+  Notmuch. As a safeguard, a database dump will be created in the
+  `.notmuch` directory before upgrading.
+
+Library changes
+---------------
+
+Notmuch database upgrade
+
+  The libnotmuch consumers are reminded to handle database upgrades
+  properly, either by relying on running `notmuch new`, or checking
+  `notmuch_database_needs_upgrade()` and calling
+  `notmuch_database_upgrade()` as necessary. This has always been the
+  case, but in practise there have been no database upgrades in any
+  released version of Notmuch before now.
+
+Support for indexing mbox files has been dropped
+
+  There has never been proper support for mbox files containing
+  multiple messages, and the support for single-message mbox files has
+  been deprecated since Notmuch 0.15. The support has now been
+  dropped, and all mbox files will be rejected during indexing.
+
+Message header parsing changes
+
+  Notmuch previously had an internal parser for message headers. The
+  parser has now been dropped in favour of letting GMime parse both
+  the headers and the message MIME structure at the same pass. This is
+  mostly an internal change, but the GMime parser is stricter in its
+  interpretation of the headers. This may result in messages with
+  slightly malformed message headers being now rejected.
+
+Command-Line Interface
+----------------------
+
+`notmuch dump` now defaults to `batch-tag` format
+
+  The old format is still available with `--format=sup`.
+
+`notmuch new` has a --quiet option
+
+  This option suppresses the progress and summary reports.
+
+`notmuch insert` respects maildir.synchronize_flags config option
+
+  Do not synchronize tags to maildir flags in `notmuch insert` if the
+  user does not want it.
+
+The commands set consistent exit status codes on failures
+
+  The cli commands now consistently set exit status of 1 on failures,
+  except where explicitly otherwise noted. The notable exceptions are
+  the status codes for format version mismatches for commands that
+  support formatted output.
+
+Bug fix for checking configured new.tags for invalid tags
+
+  `notmuch new` and `notmuch insert` now check the user configured
+  new.tags for invalid tags, and refuse to apply them, similar to
+  `notmuch tag`. Invalid tags are currently the empty string and tags
+  starting with `-`.
+
+Emacs Interface
+---------------
+
+Init file
+
+  If the file pointed by new variable `notmuch-init-file` (typically
+  `~/.emacs.d/notmuch-config.el`) exists, it is loaded at the end of
+  `notmuch.el`. Users can put their personal notmuch emacs lisp based
+  configuration/customization items there instead of filling
+  `~/.emacs` with these.
+
+Changed format for saved searches
+
+  The format for `notmuch-saved-searches` has changed, but old style
+  saved searches are still supported. The new style means that a saved
+  search can store the desired sort order for the search, and it can
+  store a separate query to use for generating the count notmuch
+  shows.
+
+  The variable is fully customizable and any configuration done
+  through customize should *just work*, with the additional options
+  mentioned above. For manual customization see the documentation for
+  `notmuch-saved-searches`.
+
+  IMPORTANT: a new style notmuch-saved-searches variable will break
+  previous versions of notmuch-emacs (even search will not work); to
+  fix remove the customization for notmuch-saved-searches.
+
+  If you have a custom saved search sort function (not unsorted or
+  alphabetical) then the sort function will need to be
+  modified. Replacing (car saved-search) by (notmuch-saved-search-get
+  saved-search :name) and (cdr saved-search) by
+  (notmuch-saved-search-get saved-search :query) should be sufficient.
+
+The keys of `notmuch-tag-formats` are now regexps
+
+  Previously, the keys were literal strings.  Customized settings of
+  `notmuch-tag-formats` will continue to work as before unless tags
+  contain regexp special characters like `.` or `*`.
+
+Changed tags are now shown in the buffer
+
+  Previously tag changes made in a buffer were shown immediately. In
+  some cases (particularly automatic tag changes like marking read)
+  this made it hard to see what had happened (e.g., whether the
+  message had been unread).
+
+  The changes are now shown explicitly in the buffer: by default
+  deleted tags are displayed with red strike-through and added tags
+  are displayed underlined in green (inverse video is used for deleted
+  tags if the terminal does not support strike-through).
+
+  The variables `notmuch-tag-deleted-formats` and
+  `notmuch-tag-added-formats`, which have the same syntax as
+  `notmuch-tag-formats`, allow this to be customized.
+
+  Setting `notmuch-tag-deleted-formats` to `'((".*" nil))` and
+  `notmuch-tag-added-formats` to `'((".*" tag))` will give the old
+  behavior of hiding deleted tags and showing added tags identically
+  to tags already present.
+
+Version variable
+
+  The new, build-time generated variable `notmuch-emacs-version` is used
+  to distinguish between notmuch cli and notmuch emacs versions.
+  The function `notmuch-hello-versions` (bound to 'v' in notmuch-hello
+  window) prints both notmuch cli and notmuch emacs versions in case
+  these differ from each other.
+  This is especially useful when using notmuch remotely.
+
+Ido-completing-read initialization in Emacs 23
+
+  `ido-completing-read` in Emacs 23 versions 1 through 3 freezes unless
+  it is initialized. Defadvice-based *Ido* initialization is defined
+  for these Emacs versions.
+
+Bug fix for saved searches with newlines in them
+
+  Split lines confuse `notmuch count --batch`, so we remove embedded
+  newlines before calling notmuch count.
+
+Bug fixes for sender identities
+
+  Previously, Emacs would rewrite some sender identities in unexpected
+  and undesirable ways.  Now it will use identities exactly as
+  configured in `notmuch-identities`.
+
+Replies to encrypted messages will be encrypted by default
+
+  In the interest of maintaining confidentiality of communications,
+  the Notmuch Emacs interface now automatically adds the mml tag to
+  encrypt replies to encrypted messages. This should make it less
+  likely to accidentally reply to encrypted messages in plain text.
+
+Reply pushes mark before signature
+
+  We push mark and set point on reply so that the user can easily cut
+  the quoted text. The mark is now pushed before the signature, if
+  any, instead of end of buffer so the signature is preserved.
+
+Message piping uses the originating buffer's working directory
+
+  `notmuch-show-pipe-message` now uses the originating buffer's
+  current default directory instead of that of the `*notmuch-pipe*`
+  buffer's.
+
+nmbug
+-----
+
+nmbug adds a `clone` command for setting up the initial repository and
+uses `@{upstream}` instead of `FETCH_HEAD` to track upstream changes.
+
+  The `@{upstream}` change reduces ambiguity when fetching multiple
+  branches, but requires existing users update their `NMBGIT`
+  repository (usually `~/.nmbug`) to distinguish between local and
+  remote-tracking branches.  The easiest way to do this is:
+
+  1. If you have any purely local commits (i.e. they aren't in the
+     nmbug repository on nmbug.tethera.net), push them to a remote
+     repository.  We'll restore them from the backup in step 4.
+  2. Remove your `NMBGIT` repository (e.g. `mv .nmbug .nmbug.bak`).
+  3. Use the new `clone` command to create a fresh clone:
+
+        nmbug clone https://nmbug.notmuchmail.org/git/nmbug-tags.git
+
+  4. If you had local commits in step 1, add a remote for that
+     repository and fetch them into the new repository.
+
+Notmuch 0.17 (2013-12-30)
+=========================
+
+Incompatible change in SHA1 computation
+---------------------------------------
+
+Previously on big endian architectures like sparc and powerpc the
+computation of SHA1 hashes was incorrect. This meant that messages
+with overlong or missing message-ids were given different computed
+message-ids than on more common little endian architectures like i386
+and amd64.  If you use notmuch on a big endian architecture, you are
+strongly advised to make a backup of your tags using `notmuch dump`
+before this upgrade. You can locate the affected files using something
+like:
+
+    notmuch dump | \
+      awk '/^notmuch-sha1-[0-9a-f]{40} / \
+        {system("notmuch search --exclude=false --output=files id:" $1)}'
+
+Command-Line Interface
+----------------------
+
+New options to better support handling duplicate messages
+
+  If more than one message file is associated with a message-id,
+  `notmuch search --output=files` will print all of them. A new
+  `--duplicate=N` option can be used to specify which duplicate to
+  print for each message.
+
+  `notmuch count` now supports `--output=files` option to output the
+  number of files associated with matching messages. This may be
+  bigger than the number of matching messages due to duplicates
+  (i.e. multiple files having the same message-id).
+
+Improved `notmuch new` performance for unchanged folders
+
+  `notmuch new` now skips over unchanged folders more efficiently,
+  which can substantially improve the performance of checking for new
+  mail in some situations (like NFS-mounted Maildirs).
+
+`notmuch reply --format=text` RFC 2047-encodes headers
+
+  Previously, this used a mix of standard MIME encoding for the reply
+  body and UTF-8 for the headers.  Now, the text format reply template
+  RFC 2047-encodes the headers, making the output a valid RFC 2822
+  message.  The JSON/sexp format is unchanged.
+
+`notmuch compact` command
+
+  The new `compact` command exposes Xapian's compaction
+  functionality through a more convenient interface than
+  `xapian-compact`. `notmuch compact` will compact the database to a
+  temporary location, optionally backup the original database, and
+  move the compacted database into place.
+
+Emacs Interface
+---------------
+
+`notmuch-tree` (formerly `notmuch-pick`) has been added to mainline
+
+  `notmuch-tree` is a threaded message view for the emacs
+  interface. Each message is one line in the results and the thread
+  structure is shown using UTF-8 box drawing characters (similar to
+  Mutt's threaded view). It comes between search and show in terms of
+  amount of output and can be useful for viewing both single threads
+  and multiple threads.
+
+  Using `notmuch-tree`
+
+  The main key entries to notmuch tree are
+
+  'z' enter a query to view using notmuch tree (works in hello,
+      search, show and tree mode itself)
+
+  'Z' view the current query in tree notmuch tree (works from search
+      and show)
+
+  Once in tree mode, keybindings are mostly in line with the rest of
+  notmuch and are all viewable with '?' as usual.
+
+  Customising `notmuch-tree`
+
+  `notmuch-tree` has several customisation variables. The most
+  significant is the first notmuch-tree-show-out which determines the
+  behaviour when selecting a message (with RET) in tree view. By
+  default tree view uses a split window showing the single message in
+  the bottom pane. However, if this option is set then it views the
+  whole thread in the complete window jumping to the selected message
+  in the thread. In either case command-prefix selects the other option.
+
+Tagging threads in search is now race-free
+
+  Previously, adding or removing a tag from a thread in a search
+  buffer would affect messages that had arrived after the search was
+  performed, resulting in, for example, archiving messages that were
+  never seen.  Tagging now affects only the messages that were in the
+  thread when the search was performed.
+
+`notmuch-hello` refreshes when switching to the buffer
+
+  The hello buffer now refreshes whenever you switch to the buffer,
+  regardless of how you get there.  You can disable automatic
+  refreshing by customizing `notmuch-hello-auto-refresh`.
+
+Specific mini-buffer prompts for tagging operations
+
+  When entering tags to add or remove, the mini-buffer prompt now
+  indicates what operation will be performed (e.g., "Tag thread", "Tag
+  message", etc).
+
+Built-in help improvements
+
+  Documentation for many commands has been improved, as displayed by
+  `notmuch-help` (usually bound to "?").  The bindings listed by
+  `notmuch-help` also now include descriptions of prefixed commands.
+
+Quote replies as they are displayed in show view
+
+  We now render the parts for reply quoting the same way they are
+  rendered for show. At this time, the notable change is that replies
+  to text/calendar are now pretty instead of raw vcalendar.
+
+Fixed inconsistent use of configured search order
+
+  All ways of interactively invoking search now honor the value of
+  `notmuch-search-oldest-first`.
+
+Common keymap for notmuch-wide bindings
+
+  Several key bindings have been moved from mode-specific keymaps to
+  the single `notmuch-common-keymap`, which is inherited by each
+  notmuch mode.  If you've customized your key bindings, you may want
+  to move some of them to the common keymap.
+
+The `notmuch-tag` function now requires a list of tag changes
+
+  For users who have scripted the Emacs interface: the `notmuch-tag`
+  API has changed.  Previously, it accepted either a list of tag
+  changes or a space-separated string of tag changes.  The latter is
+  no longer supported and the function now returns nothing.
+
+Fixed `notmuch-reply` putting reply in primary selection
+
+  On emacs 24 notmuch-reply used to put the cited text into the
+  primary selection (which could lead to inadvertently pasting this
+  cited text elsewhere). Now the primary-selection is not changed.
+
+Fixed `notmuch-show` invisible part handling
+
+  In some obscure cases part buttons and invisibility had strange
+  interactions: in particular, the default action for some parts gave
+  the wrong action. This has been fixed.
+
+Fixed `notmuch-show` attachment viewers and stderr
+
+  In emacs 24.3+ viewing an attachment could cause spurious text to
+  appear in the show buffer (any stderr or stdout the viewer
+  produced). By default this output is now discarded. For debugging,
+  setting `notmuch-show-attachment-debug` causes notmuch to keep the
+  viewer's stderr and stdout in a separate buffer.
+
+Fixed `notmuch-mua-reply` point placement when signature involved
+
+  By restricting cursor movement to body section for cursor placement
+  after signature is inserted, the cursor cannot "leak" to header
+  section anymore. Now inserted citation content will definitely go to
+  the body part of the message.
+
+Vim Interface
+-------------
+
+  It is now possible to compose new messages in the Vim interface, as
+  opposed reply to existing messages.  There is also support for
+  going straight to a search (bypassing the folders view).
+
+Notmuch 0.16 (2013-08-03)
+=========================
+
+Command-Line Interface
+----------------------
+
+Support for delivering messages to Maildir
+
+  There is a new command `insert` that adds a message to a Maildir
+  folder and notmuch index.
+
+`notmuch count --batch` option
+
+  `notmuch count` now supports batch operations similar to `notmuch
+  tag`. This is mostly an optimization for remote notmuch usage.
+
+`notmuch tag` option to remove all tags from matching messages
+
+  `notmuch tag --remove-all` option has been added to remove all tags
+  from matching messages. This can be combined with adding new tags,
+  resulting in setting (rather than modifying) the tags of the
+  messages.
+
+Decrypting commands explicitly expect a gpg-agent
+
+  Decryption in `notmuch show` and `notmuch reply` has only ever
+  worked with a functioning gpg-agent. This is now made explicit in
+  code and documentation. The functional change is that it's now
+  possible to have gpg-agent running, but gpg "use-agent"
+  configuration option disabled, not forcing the user to use the agent
+  everywhere.
+
+Configuration file saves follow symbolic links
+
+  The notmuch commands that save the configuration file now follow
+  symbolic links instead of overwrite them.
+
+Top level option to specify configuration file
+
+  It's now possible to specify the configuration file to use on the
+  command line using the `notmuch --config=FILE` option.
+
+Bash command-line completion
+
+  The notmuch command-line completion support for the bash shell has
+  been rewritten. Supported completions include all the notmuch
+  commands, command-line arguments, values for keyword arguments,
+  search prefixes (such as "subject:" or "from:") in all commands that
+  use search terms, tags after + and - in `notmuch tag`, tags after
+  "tag:" prefix, user's email addresses after "from:" and "to:"
+  prefixes, and config options (and some config option values) in
+  `notmuch config`. The new completion support depends on the
+  bash-completion package.
+
+Deprecated commands "part" and "search-tags" are removed.
+
+Emacs Interface
+---------------
+
+New keymap to view/save parts; removed s/v/o/| part button bindings
+
+  The commands to view, save, and open MIME parts are now prefixed
+  with "." (". s" to save, ". v" to view, etc) and can be invoked with
+  point anywhere in a part, unlike the old commands, which were
+  restricted to part buttons.  The old "s"/"v"/"o"/"|" commands on
+  part buttons have been removed since they clashed with other
+  bindings (notably "s" for search!) and could not be invoked when
+  there was no part button.  The new, prefixed bindings appear in
+  show's help, so you no longer have to memorize them.
+
+Default part save directory is now `mm-default-directory`
+
+  Previously, notmuch offered to save parts and attachments to a mix
+  of `mm-default-directory`, `mailcap-download-directory`, and `~/`.
+  This has been standardized on `mm-default-directory`.
+
+Key bindings for next/previous thread
+
+  Show view has new key bindings M-n and M-p to move to the next and
+  previous thread in the search results.
+
+Better handling of errors in search buffers
+
+  Instead of interleaving errors in search result buffers, search mode
+  now reports errors in the minibuffer.
+
+Faster search and show
+
+  Communication between Emacs and the notmuch CLI is now more
+  efficient because it uses the CLI's S-expression support.  As a
+  result, search mode should now fill search buffers faster and
+  threads should show faster.
+
+No Emacs 22 support
+
+  The Emacs 22 support added late 2010 was sufficient only for a short
+  period of time. After being incomplete for roughly 2 years the code
+  in question was now removed from this release.
+
+Vim Front-End
+-------------
+
+The vim based front-end has been replaced with a new one that uses the Ruby
+bindings. The old font-end is available in the contrib subfolder.
+
+Python Bindings
+---------------
+
+Fix loading of libnotmuch shared library on OS X (Darwin) systems.
+
+Notmuch 0.15.2 (2013-02-17)
+===========================
+
+Build fixes
+-----------
+
+Update dependencies to avoid problems when building in parallel.
+
+Internal test framework changes
+-------------------------------
+
+Adjust Emacs test watchdog mechanism to cope with `process-attributes`
+being unimplemented.
+
+Notmuch 0.15.1 (2013-01-24)
+===========================
+
+Internal test framework changes
+-------------------------------
+
+Set a default value for TERM when running tests. This fixes certain
+build failures in non-interactive environments.
+
+Notmuch 0.15 (2013-01-18)
+=========================
+
+General
+-------
+
+Date range search support
+
+  The `date:` prefix can now be used in queries to restrict the results
+  to only messages within a particular time range (based on the Date:
+  header) with a range syntax of `date:<since>..<until>`. Notmuch
+  supports a wide variety of expressions in `<since>` and
+  `<until>`. Please refer to the `notmuch-search-terms(7)` manual page
+  for details.
+
+Empty tag names and tags beginning with "-" are deprecated
+
+  Such tags have been a frequent source of confusion and cause
+  (sometimes unresolvable) conflicts with other syntax.  notmuch tag
+  no longer allows such tags to be added to messages.  Removing such
+  tags continues to be supported to allow cleanup of existing tags,
+  but may be removed in a future release.
+
+Command-Line Interface
+----------------------
+
+`notmuch new` no longer chokes on mboxes
+
+  `notmuch new` now rejects mbox files containing more than one
+  message, rather than treating the file as one giant message.
+
+Support for single message mboxes is deprecated
+
+  For historical reasons, `notmuch new` will index mbox files
+  containing a single message; however, this behavior is now
+  officially deprecated.
+
+Fixed `notmuch new` to skip ignored broken symlinks
+
+  `notmuch new` now correctly skips symlinks if they are in the
+  ignored files list.  Previously, it would abort when encountering
+  broken symlink, even if it was ignored.
+
+New dump/restore format and tagging interface
+
+  There is a new `batch-tag` format for dump and restore that is more
+  robust, particularly with respect to tags and message-ids containing
+  whitespace.
+
+  `notmuch tag` now supports the ability to read tag operations and
+  queries from an input stream, in a format compatible with the new
+  dump/restore format.
+
+Bcc and Reply-To headers are now available in notmuch show json output
+
+  The `notmuch show --format=json` now includes "Bcc" and "Reply-To" headers.
+  For example notmuch Emacs client can now have these headers visible
+  when the headers are added to the `notmuch-message-headers` variable.
+
+CLI callers can now request a specific output format version
+
+  `notmuch` subcommands that support structured output now support a
+  `--format-version` argument for requesting a specific version of the
+  structured output, enabling better compatibility and error handling.
+
+`notmuch search` has gained a null character separated text output format
+
+  The new --format=text0 output format for `notmuch search` prints
+  output separated by null characters rather than newline
+  characters. This is similar to the find(1) -print0 option, and works
+  together with the xargs(1) -0 option.
+
+Emacs Interface
+---------------
+
+Removal of the deprecated `notmuch-folders` variable
+
+  `notmuch-folders` has been deprecated since the introduction of saved
+  searches and the notmuch hello view in notmuch 0.3. `notmuch-folders`
+  has now been removed. Any remaining users should migrate to
+  `notmuch-saved-searches`.
+
+Visibility of MIME parts can be toggled
+
+  Each part of a multi-part MIME email can now be shown or hidden
+  using the button at the top of each part (by pressing RET on it or
+  by clicking).  For emails with multiple alternative formats (e.g.,
+  plain text and HTML), only the preferred format is shown initially,
+  but other formats can be shown using their part buttons.  To control
+  the behavior of this, see
+  `notmuch-multipart/alternative-discouraged` and
+  `notmuch-show-all-multipart/alternative-parts`.
+
+  Note notmuch-show-print-message (bound to '#' by default) will print
+  all parts of multipart/alternative message regardless of whether
+  they are currently hidden or shown in the buffer.
+
+Emacs now buttonizes mid: links
+
+  mid: links are a standardized way to link to messages by message ID
+  (see RFC 2392).  Emacs now hyperlinks mid: links to the appropriate
+  notmuch search.
+
+Handle errors from bodypart insertions
+
+  If displaying the text of a message in show mode causes an error (in
+  the `notmuch-show-insert-part-*` functions), notmuch no longer cuts
+  off thread display at the offending message.  The error is now
+  simply displayed in place of the message.
+
+Emacs now detects version mismatches with the notmuch CLI
+
+  Emacs now detects and reports when the Emacs interface version and
+  the notmuch CLI version are incompatible.
+
+Improved text/calendar content handling
+
+  Carriage returns in embedded text/calendar content caused insertion
+  of the calendar content fail. Now CRs are removed before calling icalendar
+  to extract icalendar data. In case icalendar extraction fails an error
+  is thrown for the bodypart insertion function to deal with.
+
+Disabled coding conversions when reading in `with-current-notmuch-show-message`
+
+  Depending on the user's locale, saving attachments containing 8-bit
+  data may have performed an unintentional encoding conversion,
+  corrupting the saved attachment.  This has been fixed by making
+  `with-current-notmuch-show-message` disable coding conversion.
+
+Fixed errors with HTML email containing images in Emacs 24
+
+  Emacs 24 ships with a new HTML renderer that produces better output,
+  but is slightly buggy.  We work around a bug that caused it to fail
+  for HTML email containing images.
+
+Fixed handling of tags with unusual characters in them
+
+  Emacs now handles tags containing spaces, quotes, and parenthesis.
+
+Fixed buttonization of id: links without quote characters
+
+  Emacs now correctly buttonizes id: links where the message ID is not
+  quoted.
+
+`notmuch-hello` refresh point placement improvements
+
+  Refreshing the `notmuch-hello` buffer does a better job of keeping
+  the point where it was.
+
+Automatic tag changes are now unified and customizable
+
+  All the automatic tag changes that the Emacs interface makes when
+  reading, archiving, or replying to messages, can now be
+  customized. Any number of tag additions and removals is supported
+  through the `notmuch-show-mark-read`, `notmuch-archive-tags`, and
+  `notmuch-message-replied-tags` customization variables.
+
+Support for stashing the thread id in show view
+
+  Invoking `notmuch-show-stash-message-id` with a prefix argument
+  stashes the (local and database specific) thread id of the current
+  thread instead of the message id.
+
+New add-on tool: notmuch-pick
+-----------------------------
+
+The new contrib/ tool `notmuch-pick` is an experimental threaded message
+view for the emacs interface. Each message is one line in the results
+and the thread structure is shown using UTF-8 box drawing characters
+(similar to Mutt's threaded view). It comes between search and show in
+terms of amount of output and can be useful for viewing both single
+threads and multiple threads. See the notmuch-pick README file for
+further details and installation.
+
+Portability
+-----------
+
+notmuch now builds on OpenBSD.
+
+Internal test framework changes
+-------------------------------
+
+The emacsclient binary is now user-configurable
+
+  The test framework now accepts `TEST_EMACSCLIENT` in addition to
+  `TEST_EMACS` for configuring the emacsclient to use.  This is
+  necessary to avoid using an old emacsclient with a new emacs, which
+  can result in buggy behavior.
+
+Notmuch 0.14 (2012-08-20)
+=========================
+
+General bug fixes
+-----------------
+
+Maildir tag synchronization
+
+  Maildir flag-to-tag synchronization now applies only to messages in
+  maildir-like directory structures.  Previously, it applied to any
+  message that had a maildir "info" part, which meant it could
+  incorrectly synchronize tags for non-maildir messages, while at the
+  same time failing to synchronize tags for newly received maildir
+  messages (typically causing new messages to not receive the "unread"
+  tag).
+
+Command-Line Interface
+----------------------
+
+  The deprecated positional output file argument to `notmuch dump` has
+  been replaced with an `--output` option. The input file positional
+  argument to `notmuch restore` has been replaced with an `--input`
+  option for consistency with dump.  These changes simplify the syntax
+  of dump/restore options and make them more consistent with other
+  notmuch commands.
+
+Emacs Interface
+---------------
+
+Search results now get re-colored when tags are updated
+
+The formatting of tags in search results can now be customized
+
+  Previously, attempting to change the format of tags in
+  `notmuch-search-result-format` would usually break tagging from
+  search-mode.  We no longer make assumptions about the format.
+
+Experimental support for multi-line search result formats
+
+  It is now possible to embed newlines in
+  `notmuch-search-result-format` to make individual search results
+  span multiple lines.
+
+Next/previous in search and show now move by boundaries
+
+  All "next" and "previous" commands in the search and show modes now
+  move to the next/previous result or message boundary.  This doesn't
+  change the behavior of "next", but "previous" commands will first
+  move to the beginning of the current result or message if point is
+  inside the result or message.
+
+Search now uses the JSON format internally
+
+  This should address problems with unusual characters in authors and
+  subject lines that could confuse the old text-based search parser.
+
+The date shown in search results is no longer padded before applying
+user-specified formatting
+
+  Previously, the date in the search results was padded to fixed width
+  before being formatted with `notmuch-search-result-format`.  It is
+  no longer padded.  The default format has been updated, but if
+  you've customized this variable, you may have to change your date
+  format from `"%s "` to `"%12s "`.
+
+The thread-id for the `target-thread` argument for `notmuch-search` should
+now be supplied without the "thread:" prefix.
+
+Notmuch 0.13.2 (2012-06-02)
+===========================
+
+Bug-fix release
+---------------
+
+Update `contrib/notmuch-deliver` for API changes in 0.13. This fixes a
+compilation error for this contrib package.
+
+Notmuch 0.13.1 (2012-05-29)
+===========================
+
+Bug-fix release
+---------------
+
+Fix inserting of UTF-8 characters from *text/plain* parts in reply
+
+  While notmuch gained ability to insert content from other than *text/plain*
+  parts of email whenever *text/plain* parts are not available (notably
+  HTML-only emails), replying to mails that do have *text/plain* the
+  non-ASCII characters were incorrectly decoded. This is now fixed.
+
+`notmuch_database_get_directory` and
+`notmuch_database_find_message_by_filename` now work on read-only
+databases
+
+  Previously, these functions attempted to create directory documents
+  that didn't exist and would return an error or abort when given a
+  read-only database.  Now they no longer create directory documents
+  and simply return a `NULL` object if the directory does not exist,
+  as documented.
+
+Fix compilation of ruby bindings
+
+  Revert to dynamic linking, since the statically linked bindings did
+  not work well.
+
+Notmuch 0.13 (2012-05-15)
+=========================
+
+Command-Line Interface
+----------------------
+
+JSON reply format
+
+  `notmuch reply` can now produce JSON output that contains the headers
+  for a reply message and full information about the original message
+  begin replied to. This allows MUAs to create replies intelligently.
+  For example, an MUA that can parse HTML might quote HTML parts.
+
+  Calling notmuch reply with `--format=json` imposes the restriction that
+  only a single message is returned by the search, as replying to
+  multiple messages does not have a well-defined behavior. The default
+  retains its current behavior for multiple message replies.
+
+Tag exclusion
+
+  Tags can be automatically excluded from search results by adding them
+  to the new `search.exclude_tags` option in the Notmuch config file.
+
+  This behaviour can be overridden by explicitly including an excluded
+  tag in your query, for example:
+
+        notmuch search $your_query and tag:$excluded_tag
+
+  Existing users will probably want to run `notmuch setup` again to add
+  the new well-commented [search] section to the configuration file.
+
+  For new configurations, accepting the default setting will cause the
+  tags "deleted" and "spam" to be excluded, equivalent to running:
+
+        notmuch config set search.exclude_tags deleted spam
+
+Raw show format changes
+
+  The output of show `--format=raw` has changed for multipart and
+  message parts.  Previously, the output was a mash of somewhat-parsed
+  headers and transfer-decoded bodies.  Now, such parts are reproduced
+  faithfully from the original source.  Message parts (which includes
+  part 0) output the full message, including the message headers (but
+  not the transfer headers).  Multipart parts output the part as
+  encoded in the original message, including the part's headers.  Leaf
+  parts, as before, output the part's transfer-decoded body.
+
+Listing configuration items
+
+  The new `config list` command prints out all configuration items and
+  their values.
+
+Emacs Interface
+---------------
+
+Changes to tagging interface
+
+  The user-facing tagging functions in the Emacs interface have been
+  normalized across all notmuch modes.  The tagging functions are now
+  notmuch-search-tag in search-mode, and notmuch-show-tag in
+  show-mode.  They accept a string representing a single tag change,
+  or a list of tag changes.  See 'M-x describe-function notmuch-tag'
+  for more information.
+
+  NOTE: This breaks compatibility with old tagging functions, so user
+  may need to update in custom configurations.
+
+Reply improvement using the JSON format
+
+  Emacs now uses the JSON reply format to create replies. It obeys
+  the customization variables message-citation-line-format and
+  message-citation-line-function when creating the first line of the
+  reply body, and it will quote HTML parts if no text/plain parts are
+  available.
+
+New add-on tool: notmuch-mutt
+-----------------------------
+
+The new contrib/ tool `notmuch-mutt` provides Notmuch integration for
+the Mutt mail user agent. Using it, Mutt users can perform mail
+search, thread reconstruction, and mail tagging/untagging without
+leaving Mutt.  notmuch-mutt, formerly distributed under the name
+`mutt-notmuch` by Stefano Zacchiroli, will be maintained as a notmuch
+contrib/ from now on.
+
+Library changes
+---------------
+
+The API changes detailed below break binary and source compatibility,
+so libnotmuch has been bumped to version 3.0.0.
+
+The function `notmuch_database_close` has been split into
+`notmuch_database_close` and `notmuch_database_destroy`
+
+  This makes it possible for long running programs to close the xapian
+  database and thus release the lock associated with it without
+  destroying the data structures obtained from it.
+
+`notmuch_database_open`, `notmuch_database_create`, and
+`notmuch_database_get_directory` now return errors
+
+  The type signatures of these functions have changed so that the
+  functions now return a `notmuch_status_t` and take an out-argument for
+  returning the new database object or directory object.
+
+Go bindings changes
+-------------------
+
+Go 1 compatibility
+
+  The go bindings and the `notmuch-addrlookup` utility are now
+  compatible with go 1.
+
+Notmuch 0.12 (2012-03-20)
+=========================
+
+Command-Line Interface
+----------------------
+
+Reply to sender
+
+  `notmuch reply` has gained the ability to create a reply template
+  for replying just to the sender of the message, in addition to reply
+  to all. The feature is available through the new command line option
+  `--reply-to=(all|sender)`.
+
+Mail store folder/file ignore
+
+  A new configuration option, `new.ignore`, lets users specify a
+  ;-separated list of file and directory names that will not be
+  searched for messages by `notmuch new`.
+
+  NOTE: *Every* file/directory that goes by one of those names will
+  be ignored, independent of its depth/location in the mail store.
+
+Unified help and manual pages
+
+  The notmuch help command now runs man for the appropriate page.  If
+  you install notmuch somewhere "unusual", you may need to update
+  MANPATH.
+
+Manual page for notmuch configuration options
+
+  The notmuch CLI configuration file options are now documented in the
+  notmuch-config(1) manual page in addition to the configuration file
+  itself.
+
+Emacs Interface
+---------------
+
+Reply to sender
+
+  The Emacs interface has, with the new CLI support, gained the
+  ability to reply to sender in addition to reply to all. In both show
+  and search modes, 'r' has been bound to reply to sender, replacing
+  reply to all, which now has key binding 'R'.
+
+More flexible and consistent tagging operations
+
+  All tagging operations ('+', '-', '*') now accept multiple tags with
+  '+' or '-' prefix, like '*' operation in notmuch-search view before.
+
+  '*' operation (`notmuch-show-tag-all`) is now available in
+  notmuch-show view.
+
+  `notmuch-show-{add,remove}-tag` functions no longer accept tag
+  argument, `notmuch-show-tag-message` should be used instead.  Custom
+  bindings using these functions should be updated, e.g.:
+
+        (notmuch-show-remove-tag "unread")
+
+  should be changed to:
+
+        (notmuch-show-tag-message "-unread")
+
+Refreshing the show view ('=' by default) no longer opens or closes messages
+
+  To get the old behavior of putting messages back in their initial
+  opened/closed state, use a prefix argument, e.g., 'C-u ='.
+
+Attachment buttons can be used to view or save attachments.
+
+  When the cursor is on an attachment button the key 's' can be used
+  to save the attachment, the key 'v' to view the attachment in the
+  default mailcap application, and the key 'o' prompts the user for an
+  application to use to open the attachment. By default Enter or mouse
+  button 1 saves the attachment but this is customisable (option
+  Notmuch Show Part Button Default Action).
+
+New functions
+
+  `notmuch-show-stash-mlarchive-link{,-and-go}` allow stashing and
+  optionally visiting a URI to the current message at one of a number
+  of Mailing List Archives.
+
+Fix MML tag quoting in replies
+
+  The MML tag quoting fix of 0.11.1 unintentionally quoted tags
+  inserted in `message-setup-hook`. Quoting is now limited to the
+  cited message.
+
+Show view archiving key binding changes
+
+  The show view archiving key bindings 'a' and 'x' now remove the
+  "inbox" tag from the current message only (instead of thread), and
+  move to the next message. At the last message, 'a' proceeds to the
+  next thread in search results, and 'x' returns to search
+  results. The thread archiving functions are now available in 'A' and
+  'X'.
+
+Support text/calendar MIME type
+
+  The text/calendar MIME type is now supported in addition to
+  text/x-vcalendar.
+
+Generate inline patch fake attachment file names from message subject
+
+  Use the message subject to generate file names for the inline patch
+  fake attachments. The names are now similar to the ones generated by
+  'git format-patch' instead of just "inline patch". See "Notmuch Show
+  Insert Text/Plain Hook" in the notmuch customize interface.
+
+Enable `notmuch-search-line-faces` by default
+
+  Make the `notmuch-search-line-faces` functionality more discoverable
+  for new users by showing "unread" messages bold and "flagged"
+  messages blue by default in the search view.
+
+Printing Support
+
+  notmuch-show mode now has simple printing support, bound to '#' by
+  default. You can customize the variable notmuch-print-mechanism.
+
+Library changes
+---------------
+
+New functions
+
+  `notmuch_query_add_tag_exclude` supports the new tag exclusion
+  feature.
+
+Python bindings changes
+-----------------------
+
+Python 3.2 compatibility
+
+  The python bindings are now compatible with both python 2.5+ and 3.2.
+
+Added missing unicode conversions
+
+  Python strings have to be encoded to and decoded from utf-8 when
+  calling libnotmuch functions. Porting the bindings to python 3.2
+  revealed a few function calls that were missing these conversions.
+
+Build fixes
+-----------
+
+Compatibility with GMime 2.6
+
+  It is now possible to build notmuch against both GMime 2.4 and 2.6.
+  However, a bug in GMime 2.6 before 2.6.5 causes notmuch not to
+  report signatures where the signer key is unavailable (GNOME bug
+  668085).  For compatibility with GMime 2.4's tolerance of "From "
+  headers we require GMime 2.6 >= 2.6.7.
+
+Notmuch 0.11.1 (2012-02-03)
+===========================
+
+Bug-fix release
+---------------
+
+Fix error handling in python bindings
+
+  The python bindings in 0.11 failed to detect NULL pointers being
+  returned from libnotmuch functions and thus failed to raise
+  exceptions to indicate the error condition. Any subsequent calls
+  into libnotmuch caused segmentation faults.
+
+Quote MML tags in replies
+
+  MML tags are text codes that Emacs uses to indicate attachments
+  (among other things) in messages being composed.  The Emacs
+  interface did not quote MML tags in the quoted text of a reply.
+  User could be tricked into replying to a maliciously formatted
+  message and not editing out the MML tags from the quoted text.  This
+  could lead to files from the user's machine being attached to the
+  outgoing message.  The Emacs interface now quotes these tags in
+  reply text, so that they do not effect outgoing messages.
+
+Notmuch 0.11 (2012-01-13)
+=========================
+
+Command-Line Interface
+----------------------
+
+Hooks
+
+  Hooks have been introduced to notmuch. Hooks are scripts that notmuch
+  invokes before and after certain actions. Initially, `notmuch new`
+  supports `pre-new` and `post-new` hooks that are run before and after
+  importing new messages into the database.
+
+`notmuch reply --decrypt bugfix`
+
+  The `notmuch reply` command with `--decrypt` argument had a rarely
+  occurring bug that caused an encrypted message not to be decrypted
+  sometimes. This is now fixed.
+
+Performance
+-----------
+
+Automatic tag query optimization
+
+  `notmuch tag` now automatically optimizes the user's query to
+  exclude messages whose tags won't change.  In the past, we've
+  suggested that people do this by hand; this is no longer necessary.
+
+Don't sort messages when creating a dump file
+
+  This speeds up tag dumps considerably, without any loss of
+  information. To replicate the old behavior of sorted output (for
+  example to compare two dump files), one can use e.g. `sort(1)`.
+
+Memory Management
+-----------------
+
+Reduction of memory leaks
+
+  Two memory leaks when searching and showing messages were identified
+  and fixed in this release.
+
+Emacs Interface
+---------------
+
+Bug fixes
+
+  notmuch-show-advance (bound to the spacebar in notmuch-show-mode) had
+  a bug that caused it to always jump to the next message, even if it
+  should have scrolled down to show more of the current message instead.
+  This is now fixed.
+
+Support `notmuch new` as a notmuch-poll-script
+
+  It's now possible to use `notmuch new` as a notmuch-poll-script
+  directly. This is also the new default. This allows taking better
+  advantage of the `notmuch new` hooks from emacs without intermediate
+  scripts.
+
+Improvements in saved search management
+
+  New saved searches are now appended to the list of saved searches,
+  not inserted in front. It's also possible to define a sort function
+  for displaying saved searches; alphabetical sort is provided.
+
+Hooks for notmuch-hello
+
+  Two new hooks have been added: "notmuch-hello-mode-hook" (called after
+  entering notmuch-hello-mode) and "notmuch-hello-refresh-hook" (called
+  after updating a notmuch-hello buffer).
+
+New face for crypto parts headers
+
+  Crypto parts used to be displayed with a hardcoded color. A new face
+  has been introduced to fix this: notmuch-crypto-part-header. It
+  defaults to the same value as before, but can be customized to match
+  other color themes.
+
+Use space as default thousands separator
+
+  Large numbers in notmuch-hello are now displayed using a space as
+  thousands separator (e.g. "123 456" instead of "123,456"). This can be
+  changed by customizing "notmuch-hello-thousands-separator".
+
+Call notmuch-show instead of notmuch-search when clicking on
+buttonized id: links
+
+New function notmuch-show-advance
+
+  This new function advances through just the current thread, and is
+  less invasive than notmuch-show-advance-and-archive.  It can easily
+  be bound to SPC with:
+
+        (define-key notmuch-show-mode-map " " 'notmuch-show-advance)
+
+Various performance improvements
+
+New add-on tool
+---------------
+
+The tool `contrib/notmuch-deliver` helps with initial delivery and
+tagging of mail (replacing running `notmuch new`).
+
+
+Notmuch 0.10.2 (2011-12-04)
+===========================
+
+Bug-fix release
+---------------
+
+Fix crash in python bindings
+
+  The python bindings did not call `g_type_init`, which caused crashes
+  for some, but not all users.
+
+Notmuch 0.10.1 (2011-11-25)
+===========================
+
+Bug-fix release
+---------------
+
+Fix `--help` argument
+
+  Argument processing changes in 0.10 introduced a bug where
+  `notmuch --help` crashed while `notmuch help` worked fine.
+  This is fixed in 0.10.1.
+
+Notmuch 0.10 (2011-11-23)
+=========================
+
+New build and testing features
+------------------------------
+
+Emacs tests are now done in `dtach`. This means that dtach is now
+needed to run the notmuch test suite, at least until the checking for
+prerequisites is improved.
+
+Full test coverage of the stashing feature in Emacs.
+
+New command-line features
+-------------------------
+
+Add `notmuch restore --accumulate` option
+
+  The `--accumulate` switch causes the union of the existing and new tags to
+  be applied, instead of replacing each message's tags as they are read in
+  from the dump file.
+
+Add search terms to `notmuch dump`
+
+  The dump command now takes an optional search term much like notmuch
+  search/show/tag. The output file argument of dump is deprecated in
+  favour of using stdout.
+
+Add `notmuch search` `--offset` and `--limit` options
+
+  The search command now takes options `--offset=[-]N` and `--limit=N` to
+  limit the number of results shown.
+
+Add `notmuch count --output` option
+
+  The count command is now capable of counting threads in addition to
+  messages. This is selected using the new `--output=(threads|messages)`
+  option.
+
+New emacs UI features
+---------------------
+
+Add tab-completion for `notmuch-search` and `notmuch-search-filter`
+
+  These functions now support completion tags for query parts
+  starting with "tag:".
+
+Turn "id:MSG-ID" links into buttons associated with notmuch searches
+
+  Text of the form "id:MSG-ID" in mails is now a clickable button that
+  opens a notmuch search for the given message id.
+
+Add keybinding ('c I') for stashing Message-ID's without an id: prefix
+
+  Reduces manual labor when stashing them for use outside notmuch.
+
+Do not query on `notmuch-search` exit
+
+  It is harmless to kill the external notmuch process, so the user
+  is no longer interrogated when they interrupt a search.
+
+Performance
+-----------
+
+Emacs now constructs large search buffers more efficiently
+
+Search avoids opening and parsing message files
+
+  We now store more information in the database so search no longer
+  has to open every message file to get basic headers.  This can
+  improve search speed by as much as 10X, but taking advantage of this
+  requires a database rebuild:
+
+        notmuch dump > notmuch.dump
+        # Backup, then remove notmuch database ($MAIL/.notmuch)
+        notmuch new
+        notmuch restore notmuch.dump
+
+New collection of add-on tools
+------------------------------
+
+The source directory "contrib" contains tools built on notmuch.  These
+tools are not part of notmuch, and you should check their individual
+licenses.  Feel free to report problems with them to the notmuch
+mailing list.
+
+nmbug - share tags with a given prefix
+
+  nmbug helps maintain a git repo containing all tags with a given
+  prefix (by default "notmuch::"). Tags can be shared by committing
+  them to git in one location and restoring in another.
+
+Notmuch 0.9 (2011-10-01)
+========================
+
+New, general features
+---------------------
+
+Correct handling of interruptions during `notmuch new`
+
+  `notmuch new` now operates as a series of small, self-consistent
+  transactions, so it can correctly resume after an interruption or
+  crash.  Previously, interruption could lose existing tags, fail to
+  detect messages on resume, or leave the database in a state
+  temporarily or permanently inconsistent with the mail store.
+
+Library changes
+---------------
+
+New functions
+
+  `notmuch_database_begin_atomic` and `notmuch_database_end_atomic`
+  allow multiple database operations to be performed atomically.
+
+  `notmuch_database_find_message_by_filename` does exactly what it says.
+
+API changes
+
+  `notmuch_database_find_message` (and `n_d_f_m_by_filename`) now return
+  a status indicator and uses an output parameter for the
+  message. This change required changing the SONAME of libnotmuch to
+  libnotmuch.so.2
+
+Python bindings changes
+-----------------------
+
+  - Re-encode python unicode objects to utf-8 before passing back to
+    libnotmuch.
+  - Support `Database().begin_atomic()/end_atomic()`
+  - Support `Database().find_message_by_filename()`
+    NB! This needs a db opened in READ-WRITE mode currently, or it will crash
+    the python process. The is a limitation (=bug) of the underlying libnotmuch.
+  - Fixes where we would not throw NotmuchErrors when we should (Justus Winter)
+  - Update for `n_d_find_message*` API changes (see above).
+
+Ruby bindings changes
+---------------------
+
+  - Wrap new library functions `notmuch_database_{begin,end}_atomic.`
+  - Add new exception `Notmuch::UnbalancedAtomicError.`
+  - Rename destroy to destroy! according to Ruby naming conventions.
+  - Update for `n_d_find_message*` API changes (see above).
+
+Emacs improvements
+------------------
+
+  * Add gpg callback to crypto sigstatus buttons to retrieve/refresh
+    signing key.
+  * Add `notmuch-show-refresh-view` function (and corresponding binding)
+    to refresh the view of a notmuch-show buffer.
+
+Reply formatting cleanup
+------------------------
+
+  `notmuch reply` no longer includes notification that non-leafnode
+  MIME parts are being suppressed.
+
+Notmuch 0.8 (2011-09-10)
+========================
+
+Improved handling of message/rfc822 parts
+
+  Both in the CLI and the emacs interface.  Output of rfc822 parts now
+  includes the primary headers, as well as the body and all subparts.
+  Output of the completely raw rfc822-formatted message, including all
+  headers, is unfortunately not yet supported (but hopefully will be
+  soon).
+
+Improved Build system portability
+
+  Certain parts of the shell script generating notmuch.sym were
+  specific to the GNU versions of sed and nm. The new version should
+  be more portable to e.g. OpenBSD.
+
+Documentation update for Ruby bindings
+
+  Added documentation, typo fixes, and improved support for rdoc.
+
+Unicode, iterator, PEP8 changes for python bindings
+
+  - PEP8 (code formatting) changes for python files.
+  - Remove `Tags.__len__` ; see 0.6 release notes for motivation.
+  - Decode headers as UTF8, encode (unicode) database paths as UTF8.
+
+Notmuch 0.7 (2011-08-01)
+========================
+
+Vim interface improvements
+--------------------------
+
+Jason Woofenden provided a number of bug fixes for the Vim interface
+
+  * fix citation/signature fold lengths
+  * fix cig/cit parsing within multipart/*
+  * fix on-screen instructions for show-signature
+  * fix from list reformatting in search view
+  * fix space key: now archives (did opposite)
+
+Uwe Kleine-König contributed
+
+  * use full path for sendmail/doc fix
+  * fix compose temp file name
+
+Python Bindings changes
+-----------------------
+
+Sebastian Spaeth contributed two changes related to unicode and UTF8:
+
+  * message tags are now explicitly unicode
+  * query string is encoded as a UTF8 byte string
+
+Build-System improvements
+-------------------------
+
+Generate notmuch.sym after the relevant object files
+
+  This fixes a bug in parallel building. Thanks to Thomas Jost for the
+  patch.
+
+Notmuch 0.6.1 (2011-07-17)
+==========================
+
+Bug-fix release
+---------------
+
+Re-export Xapian exception typeinfo symbols
+
+  It turned out our aggressive symbol hiding caused problems for
+  people running gcc 4.4.5.
+
+Notmuch 0.6 (2011-07-01)
+=======================
+
+New, general features
+---------------------
+
+Folder-based searching
+
+  Notmuch queries can now include a search term to match the
+  directories in which mail files are stored (within the mail
+  storage). The syntax is as follows:
+
+        folder:<path>
+
+  For example, one might use things such as:
+
+        folder:spam
+        folder:2011-*
+        folder:work/todo
+
+  to match any path containing a directory "spam", "work/todo", or
+  containing a directory starting with "2011-", respectively.
+
+  This feature is particularly useful for users of delivery-agent
+  software (such as procmail or maildrop) that is filtering mail and
+  delivering it to particular folders, or users of systems such as
+  Gmail that use filesystem directories to indicate message tags.
+
+  NOTE: Only messages that are newly indexed with this version of
+  notmuch will be searchable with folder: terms. In order to enable
+  this feature for all mail, the entire notmuch index will need to be
+  rebuilt as follows:
+
+        notmuch dump > notmuch.dump
+        # Backup, then remove notmuch database ($MAIL/.notmuch)
+        notmuch new
+        notmuch restore notmuch.dump
+
+Support for PGP/MIME
+
+  Both the command-line interface and the emacs-interface have new
+  support for PGP/MIME, detailed below. Thanks to Daniel Kahn Gillmor
+  and Jameson Graef Rollins for making this happen.
+
+New, automatic tags: "signed" and "encrypted"
+
+  These tags will automatically be applied to messages containing
+  multipart/signed and multipart/encrypted parts.
+
+  NOTE: Only messages that are newly indexed with this version of
+  notmuch will receive these tags.
+
+New command-line features
+-------------------------
+
+Add new "notmuch show --verify" option for signature verification
+
+  This option instruct notmuch to verify the signature of
+  PGP/MIME-signed parts.
+
+Add new "notmuch show --decrypt" and "notmuch reply --decrypt" options
+
+  This option instructs notmuch to decrypt PGP/MIME-encrypted parts.
+  Note that this feature currently requires gpg-agent and a passphrase entry
+  tool (e.g. pinentry-gtk or pinentry-curses).
+
+Proper nesting of multipart parts in "notmuch show" output
+
+  MIME parts are now display with proper nesting to reflect original
+  MIME hierarchy of a message. This allows clients to correctly
+  analyze the MIME structure, (such as, for example, determining to
+  which parts a signature part applies).
+
+Add new "notmuch show --part" option
+
+  This is a replacement for the older "notmuch part" command, (which
+  is now deprecated—it should still work as always, but is no longer
+  documented). Putting part output under "notmuch show" allows for all
+  of the "notmuch show" options to be applied when extracting a single
+  part, (such as --format=json for extracting a message part with JSON
+  formatting).
+
+Deprecate "notmuch search-tags" (in favor of "notmuch search --output=tags *")
+
+  The "notmuch search-tags" sub-command has been redundant since the
+  addition of the --output=tags option to "notmuch search". We now
+  make that more clear by deprecating "notmuch search-tags", (dropping
+  it from the documentation). We do continue to support the old syntax
+  by translating it internally to the new call.
+
+Performance improvements
+------------------------
+
+Faster searches (by doing fewer searches to construct threads)
+
+  Whenever a user asks for search results as threads, notmuch first
+  performs a search for messages matching the query, then performs
+  additional searches to find other messages in the resulting threads.
+
+  Removing inefficiencies and redundancies in these secondary searches
+  results in a measured speedups of 1.5x for a typical search.
+
+Faster searches (by doing fewer passes to gather message data)
+
+  Optimizing Xapian data access patterns (using a single pass to get
+  all message-document data rather than a pass for each data type)
+  results in a measured speedup of 1.7x for a typical search.
+
+  The benefits of this optimization combine with the preceding
+  optimization. With both in place, Austin Clements measured a speedup
+  of 2.5x for a search of all messages in his inbox (was 4.5s, now
+  1.8s). Thanks, Austin!
+
+Faster initial indexing
+
+  More efficient indexing of new messages results in a measured
+  speedup of 1.4x for the initial indexing of 3 GB of mail (1h 14m
+  rather than 1h 46m). Thanks to Austin Clements and Michal Sojka.
+
+Make "notmuch new" faster for unchanged directories
+
+  Optimizing to not do any further examinations of sub-directories
+  when the filesystem indicates that a directory is unchanged from the
+  last "notmuch new" results in measured speedups of 8.5 for the "No
+  new mail" case, (was 0.77s, now 0.09s). Thanks to Karel Zak.
+
+New emacs-interface features
+----------------------------
+
+Support for PGP/MIME (GnuPG)
+
+  Automatically indicate validity of signatures for multipart/signed
+  messages.  Automatically display decrypted content for
+  multipart/encrypted messages.  See the emacs variable
+  notmuch-crypto-process-mime for more information. Note that this
+  needs gpg-agent and a pinentry tool just as the command-line tools.
+  Also note there is no support SMIME yet.
+
+Output of pipe command is now displayed if pipe command fails
+
+  This is extremely useful in the common use case of piping a patch to
+  "git am". If git fails to cleanly merge the patch the error messages
+  from the failed merge are now clearly displayed to the user, (where
+  previously they were silently hidden from the user).
+
+User-selectable From address
+
+  A user can choose which configured email addresses should be used as
+  the From address whenever composing a new message. To do so, simply
+  press C-u before the command which will open a new message. Emacs
+  will prompt for the from address to use.
+
+  The user can customize the "Notmuch Identities" setting in the
+  notmuch customize group in order to use addresses other than those in
+  the notmuch configuration file if desired.
+
+  The user can also choose to always be prompted for the from address
+  when composing a new message (without having to use C-u) by setting
+  the "Notmuch Always Prompt For Sender" option in the notmuch
+  customize group.
+
+Hiding of repeated subjects in collapsed thread view
+
+  In notmuch-show mode, if a collapsed message has the same subject as
+  its parent, the subject is not shown.
+
+Automatic detection and hiding of original message in top-posted message
+
+  When a message contains a line looking something like:
+
+        ----- Original Message -----
+
+  emacs hides this and all subsequent lines as an "original message",
+  (allowing the user to click or press enter on the "original message"
+  button to display it again). This makes the handling of top-posted
+  citations work much like conventional citations.
+
+New hooks for running code when tags are modified
+
+  Some users want to perform additional actions whenever a particular
+  tag is added/removed from a message. This could be used to, for
+  example, interface with some external spam-recognition training
+  tool. To facilitate this, two new hooks are added which can be
+  modified in the following settings of the notmuch customize group:
+
+        Notmuch Before Tag Hook
+        Notmuch After Tag Hook
+
+New optional support for hiding some multipart/alternative parts
+
+  Many emails are sent with redundant content within a
+  multipart/alternative group (such as a text/plain part as well as a
+  text/html part). Users can configure the setting:
+
+        Notmuch Show All Multipart/Alternative Parts
+
+  to "off" in the notmuch customize group to have the interface
+  automatically hide some part alternatives (such as text/html
+  parts). This new part hiding is not configured by default yet
+  because there's not yet a simple way to re-display such a hidden
+  part if it is not actually redundant with a displayed part.
+
+Better rendering of text/x-vcalendar parts
+
+  These parts are now displayed in a format suitable for use with the
+  emacs diary.
+
+Avoid getting confused by Subject and Author fields with newline characters
+
+  Replacing all characters with ASCII code less than 32 with a question mark.
+
+Cleaner display of From line in email messages
+
+  Remove double quotes, and drop "name" if it's actually just a repeat of
+  the email address.
+
+Vim interface improvements
+--------------------------
+
+Felipe Contreras provided a number of updates for the vim interface:
+
+  * Using sendmail directly rather than mailx,
+  * Implementing archive in show view
+  * Add support to mark as read in show and search views
+  * Add delete commands
+  * Various cleanups.
+
+Bindings improvements
+---------------------
+
+Ruby bindings are now much more complete
+
+  Including `QUERY.sort`, `QUERY.to_s`, `MESSAGE.maildir_flags_to_tags`,
+  `MESSAGE.tags_to_maildir_flags`, and `MESSAGE.get_filenames`
+
+Python bindings have been updated and extended
+
+  (docs online at https://notmuch.readthedocs.io/)
+
+  New bindings:
+
+  - `Message().get_filenames()`, `Message().tags_to_maildir_flags()`,
+    `Message().maildir_flags_to_tags()`, `list(Threads())` and
+    `list(Messages)` works now
+  - `Message().__cmp__()` and `__hash__()`
+
+  These allow, for example:
+
+        if msg1 == msg2: ...
+
+  As well as set arithmetic on `Messages()`:
+
+        s1, s2 = set(msgs1), set(msgs2)
+        s1.union(s2)
+        s2 -= s1
+
+  Removed:
+
+  - `len(Messages())` as it exhausted the iterator
+
+  Use `len(list(Messages()))` or `Query.count_messages()`
+  to get the length.
+
+Added initial Go bindings in bindings/go
+
+New build-system features
+-------------------------
+
+Added support for building in a directory other than the source directory
+
+  This can be used with the widely-supported idiom of simply running
+  the configure script from some other directory:
+
+        mkdir build
+        cd build
+        ../configure
+        make
+
+Fix to save configure options for future, implicit runs of configure
+
+  When a user updates the source (such as with "git pull") calling
+  "make" may cause an automatic re-run of the configure script. When
+  this happens, the configure script will automatically be called with
+  the same options the user originally passed in the most-recent
+  manual invocation of configure.
+
+New test-suite feature
+----------------------
+
+Binary for bash for running test suite now located via PATH
+
+  The notmuch test suite requires a fairly recent version of bash (>=
+  bash 4). As some systems supply an older version of bash at
+  /bin/bash, the test suite is now updated to search $PATH to locate
+  the bash binary. This allows users of systems with old /bin/bash to
+  simply install bash >= 4 somewhere on $PATH before /bin and then use
+  the test suite.
+
+Support for testing output with a trailing newline
+
+  Previously, some tests would fail to notice a difference in the
+  presence/absence of a trailing newline in a program output, (which
+  has led to bugs in the past). Now, carefully-written tests (using
+  `test_expect_equal_file` rather than `test_expect_equal`) will detect
+  any change in the presence/absence of a trailing newline. Many tests
+  are updated to take advantage of this.
+
+Avoiding accessing user's $HOME while running test suite
+
+  The test suite now carefully creates its own HOME directory. This
+  allows the test suite to be run with no existing HOME directory, (as
+  some build systems apparently do), and avoids test-suite differences
+  due to configuration files in the users HOME directory.
+
+
+General bug fixes
+-----------------
+
+Output *all* files for "notmuch search --output=files"
+
+  For the cases where multiple files have the same Message ID,
+  previous versions of notmuch would output only one such file. This
+  command is now fixed to correctly output all files.
+
+Fixed spurious search results from "overlapped" indexing of addresses
+
+  This fixed a bug where a search for:
+
+        to:user@elsewhere.com
+
+  would incorrectly match a message sent:
+
+        To: user@example,com, someone@elsewhere.com
+
+Fix --output=json when search has no results
+
+  A bug present since notmuch 0.4 had caused searches with no results
+  to produce an invalid json object. This is now fixed to cleanly
+  return a valid json object representing an empty array "[]" as
+  expected.
+
+Fix the automatic detection of the From address for "notmuch reply"
+from the Received headers in some cases
+
+Fix core dump on DragonFlyBSD due to -1 return value from
+`sysconf(_SC_GETPW_R_SIZE_MAX)`
+
+Cleaned up several memory leaks
+
+Eliminated a few, rare segmentation faults and a double-free
+
+Fix libnotmuch library to only export notmuch API functions
+
+  Previous release of the notmuch library also exported some Xapian
+  C++ exception type symbols. These were never part of the library
+  interface and were never intended to be exported.
+
+Emacs-interface bug fixes
+-------------------------
+
+Display any unexpected output or errors from "notmuch search" invocations
+
+  Previously any misformatted output or trailing error messages were
+  silently ignored. This output is now clearly displayed. This fix was
+  very helpful in identifying and fixing the bug described below.
+
+Fix bug where some threads would be missing from large search results
+
+  When a search returned a "large" number of results, the emacs
+  interface was incorrectly dropping one thread every time the output
+  of the "notmuch search" process spanned the emacs read-buffer. This
+  is now fixed.
+
+Avoid re-compression of .gz files (and similar) when saving attachment
+
+  Emacs was being too clever for its own good and trying to
+  re-compress pre-compressed .gz files when saving such attachments
+  (potentially corrupting the attachment). The emacs interface is
+  fixed to avoid this bug.
+
+Fix hiding of a message when a previously-hidden citation is visible
+
+  Previously the citation would remain visible in this case. This is
+  fixed so that hiding a message hides all parts.
+
+Notmuch 0.5 (2010-11-11)
+========================
+
+New, general features
+---------------------
+
+Maildir-flag synchronization
+
+  Notmuch now knows how to synchronize flags in maildir filenames with
+  tags in the notmuch database. The following flag/tag mappings are
+  supported:
+
+        Flag <-> Tag
+        ----     -----
+        'D'      draft
+        'F'      flagged
+        'P'      passed
+        'R'      replied
+        'S'      unread (added when 'S' flag is not present)
+
+  The synchronization occurs in both directions, (for example, adding
+  the 'S' flag to a file will cause the "unread" tag to be added, and
+  adding the "replied" tag to a message will cause the file to be
+  renamed with an 'R' flag).
+
+  This synchronization is enabled by default for users of the
+  command-line interface, (though only files in directories named
+  "cur" or "new" will be renamed). It can be disabled by setting the
+  new `maildir.synchronize_flags` option in the configuration file. For
+  example:
+
+        notmuch config set maildir.synchronize_flags false
+
+  Users upgrading may also want to run "notmuch setup" once (just
+  accept the existing configuration) to get a new, nicely-commented
+  [maildir] section added to the configuration file.
+
+  For users of the notmuch library, the new synchronization
+  functionality is available with the following two new functions:
+
+        notmuch_message_maildir_flags_to_tags
+        notmuch_message_tags_to_maildir_flags
+
+  It is anticipated that future improvements to this support will
+  allow for safe synchronization of the 'T' flag with the "deleted"
+  tag, as well as support for custom flag/tag mappings.
+
+New library features
+--------------------
+
+Support for querying multiple filenames for a single message
+
+  It is common for the mailstore to contain multiple files with the
+  same message ID. Previously, notmuch would always hide these
+  duplicate files, (returning a single, arbitrary filename with
+  `notmuch_message_get_filename`).
+
+  With this release, library users can access all filenames for a
+  message with the new function:
+
+        notmuch_message_get_filenames
+
+  Together with `notmuch_filenames_valid`, `notmuch_filenames_get`,
+  and `notmuch_filenames_move_to_next` it is now possible to iterate
+  over all available filenames for a given message.
+
+New command-line features
+-------------------------
+
+New "notmuch show --format=raw" for getting at original email contents
+
+  This new feature allows for a fully-functional email client to be
+  built on top of the notmuch command-line without needing any direct
+  access to the mail store itself.
+
+  For example, it's now possible to run "emacs -f notmuch" on a local
+  machine with only ssh access to the mail store/notmuch database. To
+  do this, simply set the notmuch-command variable in emacs to the
+  name of a script containing:
+
+        ssh user@host notmuch "$@"
+
+  If the ssh client has enabled connection sharing (ControlMaster
+  option in OpenSSH), the emacs interface can be quite responsive this
+  way.
+
+General bug fixes
+-----------------
+
+Fix "notmuch search" to print nothing when nothing matches
+
+  The 0.4 release had a bug in which:
+
+        notmuch search <expression-with-no-matches>
+
+  would produce a single blank line of output, (where previous
+  versions would produce no output. This fix also causes a change in
+  the --format=json output, (which would previously produce "[]" and
+  now produces nothing).
+
+Emacs interface improvements
+----------------------------
+
+Fix to allow pipe ('|') command to work when using notmuch over ssh
+
+Fix count of lines in hidden signatures
+
+Omit repeated subject lines in (collapsed) thread display
+
+Display current thread subject in a header line
+
+Provide a "c i" binding to copy a thread ID from the search view
+
+Allow for notmuch-fcc-dirs to have a value of nil
+
+  Also, the more complex form of notmuch-fcc-dirs now has a slightly
+  different format. It no longer has a special first-element, fallback
+  string. Instead it's now a list of cons cells where the car of each
+  cell is a regular expression to be matched against the sender
+  address, and the cdr is the name of a folder to use for an FCC. So
+  the old fallback behavior can be achieved by including a final cell
+  of (".*" . "default-fcc-folder").
+
+Vim interface improvements
+--------------------------
+
+Felipe Contreras provided a number of updates for the vim interface
+
+  These include optimizations, support for newer versions of vim, fixed
+  support for sending mail on modern systems, new commands, and
+  various cleanups.
+
+New bindings
+------------
+
+Added initial ruby bindings in bindings/ruby
+
+Notmuch 0.4 (2010-11-01)
+========================
+
+New command-line features
+-------------------------
+
+`notmuch search --output=(summary|threads|messages|tags|files)`
+
+  This new option allows for particular items to be returned from
+  notmuch searches. The "summary" option is the default and behaves
+  just as "notmuch search" has historically behaved.
+
+  The new option values allow for thread IDs, message IDs, lists of
+  tags, and lists of filenames to be returned from searches. It is
+  expected that this new option will be very useful in shell
+  scripts. For example:
+
+        for file in $(notmuch search --output=files <search-terms>); do
+                <operations-on> "$file"
+        done
+
+`notmuch show --format=mbox <search-specification>`
+
+  This new option allows for the messages matching a search
+  specification to be presented as an mbox. Specifically the "mboxrd"
+  format is used which allows for reversible quoting of lines
+  beginning with "From ". A reader should remove a single '>' from the
+  beginning of all lines beginning with one or more '>' characters
+  followed by the 5 characters "From ".
+
+`notmuch config [get|set] <section>.<item> [value ...]`
+
+  The new top-level "config" command allows for any value in the
+  notmuch configuration file to be queried or set to a new value. Both
+  single-valued and multi-valued items are supported, as our any
+  custom items stored in the configuration file.
+
+Avoid setting Bcc header in "notmuch reply"
+
+  We decided that this was a bit heavy-handed as the actual mail
+  user-agent should be responsible for setting any Bcc option. Also,
+  see below for the notmuch/emacs user-agent now setting an Fcc by
+  default rather than Bcc.
+
+New library features
+--------------------
+
+Add `notmuch_query_get_query_string` and `notmuch_query_get_sort`
+
+  These are simply functions for querying properties of a
+  `notmuch_query_t` object.
+
+New emacs features
+------------------
+
+Enable Fcc of all sent messages by default (to "sent" directory)
+
+  All messages sent from the emacs interface will now be saved to the
+  notmuch mail store where they will be incorporated to the database
+  by the next "notmuch new". By default, messages are saved to the
+  "sent" directory at the top-level of the mail store. This directory
+  can be customized by means of the "Notmuch Fcc Dirs" option in the
+  notmuch customize interface.
+
+Ability to all open messages in a thread to a pipe
+
+  Historically, the '|' keybinding allows for piping a single message
+  to an external command. Now, by prefixing this key with a prefix
+  argument, (for example, by pressing "Control-U |"), all open
+  messages in the current thread will be sent to the external command.
+
+Optional support for detecting inline patches
+
+  This hook is disabled by default but can be enabled with a checkbox
+  under "Notmuch Show Insert Text/Plain Hook" in the notmuch customize
+  interface. It allows for inline patches to be detected and treated
+  as if they were attachments, (with context-sensitive highlighting).
+
+Automatically tag messages as "replied" when sending a reply
+
+  Messages replied to within the emacs interface will now be tagged as
+  "replied". This feature can easily be customized to add or remove
+  other tags as well. For example, a user might use a tag of
+  "needs-reply" and can configure this feature to automatically remove
+  that tag when replying. See "Notmuch Message Mark Replied" in the
+  notmuch customize interface.
+
+Allow search-result color specifications to overlay each other
+
+  For example, one tag can specify the background color of matching
+  lines, while another can specify the foreground. With this change,
+  both settings will now be visible simultaneously, (which was not the
+  case in previous releases). See "Notmuch Search Line Faces" in the
+  notmuch customize interface.
+
+Make hidden author names still available for incremental search
+
+  When there is insufficient space to display all authors of a thread
+  in search results, the names of hidden authors are now still made
+  available to emacs' incremental search commands. As the user
+  searches, matching lines will temporarily expand to show the hidden
+  names.
+
+New binding of Control-TAB (works like TAB in reverse)
+
+  Many notmuch nodes already use TAB to navigate forward through
+  various items allowing actions, (message headers, email attachments,
+  etc.). The new Control-TAB binding operates similarly but in the
+  opposite direction.
+
+New build-system features
+-------------------------
+
+Various portability fixes have been applied
+
+  These include fixes for build failures on at least Solaris, FreeBSD,
+  and Fedora systems. We're hopeful that the notmuch code base is now
+  more portable than ever before.
+
+Arrange for libnotmuch to be found automatically after make install
+
+  The notmuch build system is now careful to help the user avoid
+  errors of the form "libnotmuch.so could not be found" immediately
+  after installing. This support takes two forms:
+
+  1. If the library is installed to a system directory,
+     (configured in /etc/ld.so.conf), then "make install" will
+     automatically run ldconfig.
+
+  2. If the library is installed to a non-system directory, the
+     build system adds a `DR_RUNPATH` entry to the final binary
+     pointing to the directory to which the library is installed.
+
+  When this support works, the user should be able to run notmuch
+  immediately after "make install", without any errors trying to find
+  the notmuch library, and without having to manually set environment
+  variables such as `LD_LIBRARY_PATH`.
+
+Check compiler/linker options before using them
+
+  The configure script now carefully checks that any desired
+  compilation options, (whether for enabling compiler warnings, or for
+  embedding rpath, etc.), are supported. Only supported options are
+  used in the resulting Makefile.
+
+New test-suite features
+-----------------------
+
+New modularization of test suite
+
+  Thanks to a gracious relicensing of the test-suite infrastructure
+  from the git project, notmuch now has a modular test suite. This
+  provides the ability to run individual sections of the test suite
+  rather than the whole things. It also provides better summary of
+  test results, with support for tests that are expected to fail
+  (BROKEN and FIXED) in addition to PASS and FAIL. Finally, it makes
+  it easy to run the test suite within valgrind (pass --valgrind to
+  notmuch-test or to any sub-script) which has been very useful.
+
+New testing of emacs interface
+
+  The test suite has been augmented to allow automated testing of the
+  emacs interfaces. So far, this includes basic searches, display of
+  threads, and tag manipulation. This also includes a test that a new
+  message can successfully be sent out through a (dummy) SMTP server
+  and that said message is successfully integrated into the notmuch
+  database via the FCC setting.
+
+General bug fixes
+-----------------
+
+Fix potential corruption of database when "notmuch new" is interrupted
+
+  Previously, an interruption of "notmuch new" would (rarely) result
+  in a corrupt database. The corruption would manifest itself by a
+  persistent error of the form:
+
+        document ID of 1234 has no thread ID
+
+  The message-adding code has been carefully audited and reworked to
+  avoid this sort of corruption regardless of when it is interrupted.
+
+Fix failure with extremely long message ID headers
+
+  Previously, a message with an extremely long message ID, (say, more
+  than 300 characters), would fail to be added to notmuch, (triggering
+  Xapian exceptions). This has now been fixed.
+
+Fix for messages with "charset=unknown-8bit"
+
+  Previously, messages with this charset would cause notmuch to emit a
+  GMime warning, (which would then trip up emacs or other interfaces
+  parsing the notmuch results).
+
+Fix `notmuch_query_search_threads` function to return NULL on any exception
+
+Fix "notmuch search" to return non-zero if `notmuch_query_search_threads`
+fails
+
+  Previously, this command could confusingly report a Xapian
+  exception, yet still return an error code of 0. It now correctly
+  returns a failing error code of 1 in this case.
+
+Emacs bug fixes
+---------------
+
+Fix to handle a message with a subject containing, for example "[1234]"
+
+  Previously, a message subject containing a sequence of digits within
+  square brackets would cause the emacs interface to mis-parse the
+  output of "notmuch search". This would result in the message being
+  mis-displayed and prevent the user from manipulating the message in
+  the emacs interface.
+
+Fix to correctly handle message IDs containing ".."
+
+  The emacs interface now properly quotes message IDs to avoid a
+  Xapian bug in which the ".." within a message ID would be
+  misinterpreted as a numeric range specification.
+
+Python-binding fixes
+--------------------
+
+The python bindings for notmuch have been updated to work with python3.
+
+Debian-specific fixes
+---------------------
+
+Fix emacs initialization so "M-x notmuch" works for users by default
+
+  Now, a new Debian user can immediately run "emacs -f notmuch" after
+  "apt-get install notmuch". Previously, the user would have had to
+  edit the ~/.emacs file to add "(require 'notmuch)" before this would
+  work.
+
+Notmuch 0.3.1 (2010-04-27)
+==========================
+
+General bug fixes
+-----------------
+
+Fix an infinite loop in "notmuch reply"
+
+  This bug could be triggered by replying to a message where the
+  user's primary email address did not appear in the To: header and
+  the user had not configured any secondary email addresses. The bug
+  was a simple re-use of the same iterator variable in nested loops.
+
+Fix a potential SEGV in "notmuch search"
+
+  This bug could be triggered by an author name ending in a ','.
+  Admittedly - that's almost certainly a spam email, but we never
+  want notmuch to crash.
+
+Emacs bug fixes
+---------------
+
+Fix calculations for line wrapping in the primary "notmuch" view
+
+Fix Fcc support to prompt to create a directory if the specified Fcc
+directory does not exist
+
+Build fix
+---------
+
+Fix build on OpenSolaris (at least) due to missing 'extern "C"' block
+
+  Without this, the C++ sources could not find strcasestr and the
+  final linking of notmuch would fail.
+
+Notmuch 0.3 (2010-04-27)
+========================
+
+New command-line features
+-------------------------
+
+User-configurable tags for new messages
+
+  A new "new.tags" option is available in the configuration file to
+  determine which tags are applied to new messages. Run "notmuch
+  setup" to generate new documentation within ~/.notmuch-config on how
+  to specify this value.
+
+Threads search results named based on subjects that match search
+
+  This means that when new mails arrived to a thread you've previously
+  read, and the new mails have a new subject, you will see that
+  subject in the search results rather than the old subject.
+
+Faster operation of "notmuch tag" (avoid unneeded sorting)
+
+  Since the user just wants to tag all matching messages, we can make
+  things perform a bit faster by avoiding the sort.
+
+Even Better guessing of From: header for "notmuch reply"
+
+  Notmuch now looks at a number of headers when trying to figure out
+  the best From: header to use in a reply. This is helpful if you have
+  several configured email addresses, and you also subscribe to various
+  mailing lists with different addresses, (so that mails you are
+  replying to won't always include your subscribed address in the To:
+  header).
+
+Indication of author names that match a search
+
+  When notmuch displays threads as the result of a search, it now
+  lists the authors that match the search before listing the other
+  authors in the thread. It inserts a pipe '|' symbol between the last
+  matching and first non-matching author. This is especially useful in
+  a search that includes tag:unread. Now the authors of the unread
+  messages in the thread are listed first.
+
+New: Python bindings
+--------------------
+
+Sebastian Spaeth has contributed his python bindings for the notmuch
+library to the central repository. These bindings were previously
+known as "cnotmuch" within python but have now been renamed to be
+accessible with a simple, and more official-looking "import notmuch".
+
+The bindings have already proven very useful as people proficient in
+python have been able to easily develop programs to do notmuch-based
+searches for email-address completion, maildir-flag synchronization,
+and other tasks.
+
+These bindings are available within the bindings/python directory, but
+are not yet integrated into the top-level Makefiles, nor the top-level
+package-building scripts. Improvements are welcome.
+
+Emacs interface improvements
+----------------------------
+
+An entirely new initial view for notmuch, (friendly yet powerful)
+
+  Some of us call the new view "notmuch hello" but you can get at it
+  by simply calling "emacs -f notmuch". The new view provides a search
+  bar where new searches can be performed. It also displays a list of
+  recent searches, along with a button to save any of these, giving it
+  a new name as a "saved search". Many people find these "saved
+  searches" one of the most convenient ways of organizing their mail,
+  (providing all of the features of "folders" in other mail clients,
+  but without any of the disadvantages).
+
+  Finally, this view can also optionally display all of the tags that
+  exist in the database, along with a count for each tag, and a custom
+  search of messages with that tag that's simply a click (or keypress)
+  away.
+
+  NOTE: For users that liked the original mode of "emacs -f notmuch"
+  immediately displaying a particular search result, we recommend
+  instead running something like:
+
+        emacs --eval '(notmuch search "tag:inbox" t)'
+
+  The "t" means to sort the messages in an "oldest first" order,
+  (as notmuch would do previously by default). You can also
+  leave that off to have your search results in "newest first"
+  order.
+
+Full-featured "customize" support for configuring notmuch
+
+  Notmuch now plugs in well to the emacs "customize" mode to make it
+  much simpler to find things about the notmuch interface that can be
+  tweaked by the user.
+
+  You can get to this mode by starting at the main "Customize" menu in
+  emacs, then browsing through "Applications", "Mail", and
+  "Notmuch". Or you can go straight to "M-x customize-group"
+  "notmuch".
+
+  Once you're at the customize screen, you'll see a list of documented
+  options that can be manipulated along with checkboxes, drop-down
+  selectors, and text-entry boxes for configuring the various
+  settings.
+
+Support for doing tab-completion of email addresses
+
+  This support currently relies on an external program,
+  (notmuch-addresses), that is not yet shipped with notmuch
+  itself. But multiple, suitable implementations of this program have
+  already been written that generate address completions by doing
+  notmuch searches of your email collection. For example, providing
+  first those addresses that you have composed messages to in the
+  past, etc.
+
+  One such program (implemented in python with the python bindings to
+  notmuch) is available via:
+
+        git clone  http://jkr.acm.jhu.edu/git/notmuch_addresses.git
+
+  Install that program as notmuch-addresses on your PATH, and then
+  hitting TAB on a partial email address or name within the To: or Cc:
+  line of an email message will provide matching completions.
+
+Support for file-based (Fcc) delivery of sent messages to mail store
+
+  This isn't yet enabled by default. To enable this, one will have to
+  set the "Notmuch Fcc Dirs" setting within the notmuch customize
+  screen, (see its documentation there for details). We anticipate
+  making this automatic in a future release.
+
+New 'G' key binding to trigger mail refresh (G == "Get new mail")
+
+  The 'G' key works wherever '=' works. Before refreshing the screen
+  it calls an external program that can be used to poll email servers,
+  run notmuch new and set up specific tags for the new emails. The
+  script to be called should be configured with the "Notmuch Poll
+  Script" setting in the customize interface. This script will
+  typically invoke "notmuch new" and then perhaps several "notmuch
+  tag" commands.
+
+Implement emacs message display with the JSON output from notmuch
+
+  This is much more robust than the previous implementation, (where
+  some HTML mails and mail quoting the notmuch code with the delimiter
+  characters in it would cause the parser to fall over).
+
+Better handling of HTML messages and MIME attachments (inline images!)
+
+  Allow for any MIME parts that emacs can display to be displayed
+  inline. This includes inline viewing of image attachments, (provided
+  the window is large enough to fit the image at its natural size).
+
+  Much more robust handling of HTML messages. Currently both text/plain
+  and text/html alternates will be rendered next to each other. In a
+  future release, users will be able to decide to see only one or the
+  other representation.
+
+  Each attachment now has its own button so that attachments can be
+  saved individually (the 'w' key is still available to save all
+  attachments).
+
+Customizable support for tidying of text/plain message content
+
+  Many new functions are available for tidying up message
+  content. These include options such as wrapping long lines,
+  compressing duplicate blank lines, etc.
+
+  Most of these are disabled by default, but can easily be enabled by
+  clicking the available check boxes under the "Notmuch Show Insert
+  Text/Plain Hook" within the notmuch customize screen.
+
+New support for searchable citations (even when hidden)
+
+  When portions of overly-long citations are hidden, the contents of
+  these citations will still be available for emacs' standard
+  "incremental search" functions. When the search matches any portion
+  of a hidden citation, the citation will become visible temporarily
+  to display the search result.
+
+More flexible handling of header visibility
+
+  As an answer to complaints from many users, the To, Cc, and Date
+  headers of messages are no longer hidden by default. For those users
+  that liked that these were hidden, a new "Notmuch Messages Headers
+  Visible" option in the customize interface can be set to nil. The
+  visibility of headers can still be toggled on a per-message basis
+  with the 'h' keybinding.
+
+  For users that don't want to see some subset of those headers, the
+  new "Notmuch Message Headers" variable can be customized to list
+  only those headers that should be present in the display of a message.
+
+The Return key now toggles message visibility anywhere
+
+  Previously this worked only on the first summary-line of a message.
+
+Customizable formatting of search results
+
+  The user can easily customize the order, width, and formatting of
+  the various fields in a "notmuch search" buffer. See the "Notmuch
+  Search Result Format" section of the customize interface.
+
+Generate nicer names for search buffers when using a saved search
+
+Add a notmuch User-Agent header when sending mail from notmuch/emacs
+
+New keybinding (M-Ret) to open all collapsed messages in a thread
+
+New library feature
+-------------------
+
+Provide a new `NOTMUCH_SORT_UNSORTED` value for queries
+
+  This can be somewhat faster when sorting simply isn't desired. For
+  example when collecting a set of messages that will all be
+  manipulated identically, (adding a tag, removing a tag, deleting the
+  messages), then there's no advantage to sorting the messages by
+  date.
+
+Build fixes
+-----------
+
+Fix to compile against GMime 2.6
+
+  Previously notmuch insisted on being able to find GMime 2.4, (even
+  though GMime 2.6 would have worked all along).
+
+Fix configure script to accept (and ignore) various standard options
+
+  For example, those that the Gentoo build scripts expect configure to
+  accept are now all accepted.
+
+Test suite
+----------
+
+A large number of new tests for the many new features
+
+Better display of output from failed tests
+
+  Now shows failures with diff rather than forcing the user to gaze at
+  complete actual and expected output looking for deviation.
+
+Notmuch 0.2 (2010-04-16)
+========================
+
+This is the second release of the notmuch mail system, with actual
+detailed release notes this time!
+
+This release consists of a number of minor new features that make
+notmuch more pleasant to use, and a few fairly major bug fixes.
+
+We didn't quite hit our release target of "about a week" from the 0.1
+release, (0.2 is happening 11 days after 0.1), but we hope to do
+better for next week. Look forward to some major features coming to
+notmuch in subsequent releases.
+
+-Carl
+
+General features
+----------------
+
+Better guessing of From: header
+
+  Notmuch now tries harder to guess which configured address should be
+  used as the From: line in a "notmuch reply". It will examine the
+  Received: headers if it fails to find any configured address in To:
+  or Cc:. This allows it to often choose the correct address even when
+  replying to a message sent to a mailing list, and not directly to a
+  configured address.
+
+Make "notmuch count" with no arguments count all messages
+
+  Previously, it was hard to construct a search term that was
+  guaranteed to match all messages.
+
+Provide a new special-case search term of "*" to match all messages
+
+  This can be used in any command accepting a search term, such as
+  "notmuch search '*'". Note that you'll want to take care that the
+  shell doesn't expand * against the current files. And note that the
+  support for "*" is a special case. It's only meaningful as a single
+  search term and loses its special meaning when combined with any
+  other search terms.
+
+Automatically detect thread connections even when a parent message is
+missing
+
+  Previously, if two or more message were received with a common
+  parent, but that parent was not received, then these messages would
+  not be recognized as belonging to the same thread. This is now fixed
+  so that such messages are properly connected in a thread.
+
+General bug fixes
+-----------------
+
+Fix potential data loss in "notmuch new" with SIGINT
+
+  One code path in "notmuch new" was not properly handling
+  SIGINT. Previously, this could lead to messages being removed from
+  the database (and their tags being lost) if the user pressed
+  Control-C while "notmuch new" was working.
+
+Fix segfault when a message includes a MIME part that is empty
+
+Fix handling of non-ASCII characters with --format=json
+
+  Previously, characters outside the range of 7-bit ASCII were
+  silently dropped from the JSON output. This led to corrupted display
+  of utf-8 content in the upcoming notmuch web-based frontends.
+
+Fix headers to be properly decoded in "notmuch reply"
+
+  Previously, the user might see:
+
+        Subject: Re: =?iso-8859-2?q?Rozlu=E8ka?=
+
+  rather than:
+
+        Subject: Re: Rozlučka
+
+  The former text is properly encoded to be RFC-compliant SMTP, will
+  be sent correctly, and will be properly decoded by the
+  recipient. But the user trying to edit the reply would likely be
+  unable to read or edit that field in its encoded form.
+
+Emacs client features
+---------------------
+
+Show the last few lines of citations as well as the first few lines
+
+  It's often the case that the last sentence of a citation is what is
+  being replied to directly, so the last few lines are often much more
+  important. The number of lines shown at the beginning and end of any
+  citation can be configured, (notmuch-show-citation-lines-prefix and
+  notmuch-show-citation-lines-suffix).
+
+The '+' and '-' commands in the search view can now add and remove
+tags by region
+
+  Selective bulk tagging is now possible by selecting a region of
+  threads and then using either the '+' or '-' keybindings. Bulk
+  tagging is still available for all threads matching the current
+  search with the '*' binding.
+
+More meaningful buffer names for thread-view buffers
+
+  Notmuch now uses the Subject of the thread as the buffer
+  name. Previously it was using the thread ID, which is a meaningless
+  number to the user.
+
+Provide for customized colors of threads in search view based on tags
+
+  See the documentation of notmuch-search-line-faces, (or us "M-x
+  customize" and browse to the "notmuch" group within "Applications"
+  and "Mail"), for details on how to configure this colorization.
+
+Build-system features
+---------------------
+
+Add support to properly build libnotmuch on Darwin systems (OS X)
+
+Add support to configure for many standard options
+
+  We include actual support for:
+
+        --includedir --mandir --sysconfdir
+
+  And accept and silently ignore several more:
+
+        --build --infodir --libexecdir --localstatedir
+        --disable-maintainer-mode --disable-dependency-tracking
+
+Install emacs client in "make install" rather than requiring a
+separate "make install-emacs"
+
+Automatically compute versions numbers between releases
+
+  This support uses the git-describe notation, so a version such as
+  0.1-144-g43cbbfc indicates a version that is 144 commits since the
+  0.1 release and is available as git commit "43cbbfc".
+
+Add a new "make test" target to run the test suite and actually
+verify its results
+
+Notmuch 0.1 (2010-04-05)
+========================
+
+This is the first release of the notmuch mail system.
+
+It includes the libnotmuch library, the notmuch command-line
+interface, and an emacs-based interface to notmuch.
+
+Note: Notmuch will work best with Xapian 1.0.18 (or later) or Xapian
+1.1.4 (or later). Previous versions of Xapian (whether 1.0 or 1.1) had
+a performance bug that made notmuch very slow when modifying
+tags. This would cause distracting pauses when reading mail while
+notmuch would wait for Xapian when removing the "inbox" and "unread"
+tags from messages in a thread.
+
+
+<!--
+ Local variables:
+ mode: text
+ tab-width: 8
+ indent-tabs-mode: nil
+ End:
+ vi: sw=8 ts=8 et
+-->
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..0aa9a08
--- /dev/null
+++ b/README
@@ -0,0 +1,77 @@
+Notmuch - thread-based email index, search and tagging.
+
+Notmuch is a system for indexing, searching, reading, and tagging
+large collections of email messages in maildir or mh format. It uses
+the Xapian library to provide fast, full-text search with a convenient
+search syntax.
+
+Notmuch is free software, released under the GNU General Public
+License version 3 (or later).
+
+Building notmuch
+----------------
+See the INSTALL file for notes on compiling and installing notmuch.
+
+Running notmuch
+---------------
+After installing notmuch, start by running "notmuch setup" which will
+interactively prompt for configuration information such as your name,
+email address, and the directory which contains your mail archive to
+be indexed. You can change any answers later by running "notmuch
+setup" again or by editing the .notmuch-config file in your home
+directory.
+
+With notmuch configured you should next run "notmuch new" which will
+index all of your existing mail. This can take a long time, (several
+hours) if you have a lot of email, (hundreds of thousands of
+files). When new mail is delivered to your mail archive in the future,
+you will want to run "notmuch new" again. These runs will be much
+faster as they will only index new messages.
+
+Finally, you can prove to yourself that things are working by running
+some command-line searches such as "notmuch search
+from:someone@example.com" or "notmuch search subject:topic". See
+"notmuch help search-terms" for more details on the available search
+syntax.
+
+The command-line search output is not expected to be particularly
+friendly for day-to-day usage. Instead, it is expected that you will
+use an email interface that builds on the notmuch command-line tool or
+the libnotmuch library.
+
+Notmuch installs a full-featured email interface for use within
+emacs. To use this, first add the following line to your .emacs file:
+
+       (autoload 'notmuch "notmuch" "Notmuch mail" t)
+
+Then, either run "emacs -f notmuch" or execute the command "M-x
+notmuch" from within a running emacs.
+
+If you're interested in a non-emacs-based interface to notmuch, then
+please join the notmuch community. Various other interfaces are
+already in progress, (an interface within vim, a curses interface,
+graphical interfaces based on evolution, and various web-based
+interfaces). The authors of these interfaces would love further
+testing or contribution. See contact information below.
+
+Contacting users and developers
+-------------------------------
+The website for Notmuch is:
+
+       https://notmuchmail.org
+
+The mailing list address for the notmuch community is:
+
+       notmuch@notmuchmail.org
+
+We welcome any sort of questions, comments, kudos, or code there.
+
+Subscription is not required, (but if you do subscribe you'll avoid
+any delay due to moderation). See the website for subscription
+information.
+
+There is also an IRC channel dedicated to talk about using and
+developing notmuch:
+
+       IRC server:     irc.freenode.net
+       Channel:        #notmuch
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..7ff3198
--- /dev/null
@@ -0,0 +1,11 @@
+If you're reading this on https://github.com/notmuch/notmuch, this is a
+read-only mirror of the notmuch project.
+
+For more information about the project, see https://notmuchmail.org.
+
+Please don't send us pull requests on github. If you have a feature
+branch that you want us to look at, use ``git send-email`` to send it
+to notmuch@notmuchmail.org.
+
+For more information about contributing to the project, see
+https://notmuchmail.org/contributing/.
diff --git a/bindings/Makefile b/bindings/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/bindings/Makefile.local b/bindings/Makefile.local
new file mode 100644 (file)
index 0000000..18f9583
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- makefile -*-
+
+dir := bindings
+
+# force the shared library to be built
+ruby-bindings: lib/$(LINKER_NAME)
+ifeq ($(HAVE_RUBY_DEV),1)
+       cd $(dir)/ruby && \
+               EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
+               LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
+               NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \
+               $(RUBY) extconf.rb --vendor
+       $(MAKE) -C $(dir)/ruby
+endif
+
+CLEAN += $(patsubst %,$(dir)/ruby/%, \
+       .RUBYARCHDIR.time \
+       Makefile database.o directory.o filenames.o\
+       init.o message.o messages.o mkmf.log notmuch.so query.o \
+       status.o tags.o thread.o threads.o)
+
+CLEAN += bindings/ruby/.vendorarchdir.time
diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore
new file mode 100644 (file)
index 0000000..601acdd
--- /dev/null
@@ -0,0 +1,4 @@
+*.py[co]
+/docs/build
+/docs/html
+/build/
diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in
new file mode 100644 (file)
index 0000000..c83be4b
--- /dev/null
@@ -0,0 +1,2 @@
+include notmuch
+#recursive-include docs/html *
\ No newline at end of file
diff --git a/bindings/python/README b/bindings/python/README
new file mode 100644 (file)
index 0000000..5bf076d
--- /dev/null
@@ -0,0 +1,17 @@
+notmuch -- The python interface to notmuch
+==========================================
+
+This module makes the functionality of the notmuch library
+(`https://notmuchmail.org`_) available to python. Successful import of
+this module depends on a libnotmuch.so|dll being available on the
+user's system.
+
+If you have downloaded the full source tarball, you can create the
+documentation with sphinx installed, go to the docs directory and
+"make html". A static version of the documentation is available at:
+
+  https://notmuch.readthedocs.io/projects/notmuch-python/
+
+To build the python bindings, do
+
+  python setup.py install --prefix=path/to/your/preferred/location
diff --git a/bindings/python/docs/COPYING b/bindings/python/docs/COPYING
new file mode 100644 (file)
index 0000000..e600086
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/bindings/python/docs/Makefile b/bindings/python/docs/Makefile
new file mode 100644 (file)
index 0000000..bd6de38
--- /dev/null
@@ -0,0 +1,88 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html      to make standalone HTML files"
+       @echo "  dirhtml   to make HTML files named index.html in directories"
+       @echo "  pickle    to make pickle files"
+       @echo "  json      to make JSON files"
+       @echo "  htmlhelp  to make HTML files and a HTML help project"
+       @echo "  qthelp    to make HTML files and a qthelp project"
+       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  changes   to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck to check all external links for integrity"
+       @echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       -rm -rf build/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html
+       @echo
+       @echo "Build finished. The HTML pages are in html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in build/dirhtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in build/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in build/qthelp, like this:"
+       @echo "# qcollectiongenerator build/qthelp/pyDNS.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile build/qthelp/pyDNS.qhc"
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in build/latex."
+       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+             "run these through (pdf)latex."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
+       @echo
+       @echo "The overview file is in build/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in build/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in build/doctest/output.txt."
diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py
new file mode 100644 (file)
index 0000000..8b43c5c
--- /dev/null
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+#
+# pyDNS documentation build configuration file, created by
+# sphinx-quickstart on Tue Feb  2 10:00:47 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+from unittest.mock import Mock
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0,os.path.abspath('../..'))
+
+MOCK_MODULES = [
+    'ctypes',
+]
+for mod_name in MOCK_MODULES:
+    sys.modules[mod_name] = Mock()
+
+
+from notmuch import __VERSION__,__AUTHOR__
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo']
+autoclass_content = "both"
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'notmuch'
+copyright = u'2010-2012, ' + __AUTHOR__
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = __VERSION__
+# The full version, including alpha/beta/rc tags.
+release = __VERSION__
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = False
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+html_use_modindex = False
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'notmuchdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'notmuch.tex', u'notmuch Documentation',
+   u'notmuch contributors', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/bindings/python/docs/source/database.rst b/bindings/python/docs/source/database.rst
new file mode 100644 (file)
index 0000000..660de91
--- /dev/null
@@ -0,0 +1,54 @@
+:class:`Database` -- The underlying notmuch database
+====================================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]])
+
+   .. automethod:: create
+
+   .. automethod:: open(path, status=MODE.READ_ONLY)
+
+   .. automethod:: close
+
+   .. automethod:: get_path
+
+   .. automethod:: get_version
+
+   .. automethod:: needs_upgrade
+
+   .. automethod:: upgrade
+
+   .. automethod:: begin_atomic
+
+   .. automethod:: end_atomic
+
+   .. automethod:: get_directory
+
+   .. automethod:: index_file
+
+   .. automethod:: remove_message
+
+   .. automethod:: find_message
+
+   .. automethod:: find_message_by_filename
+
+   .. automethod:: get_all_tags
+
+   .. automethod:: create_query
+
+   .. automethod:: get_config
+
+   .. automethod:: get_configs
+
+   .. automethod:: set_config
+
+   .. attribute:: Database.MODE
+
+     Defines constants that are used as the mode in which to open a database.
+
+     MODE.READ_ONLY
+       Open the database in read-only mode
+
+     MODE.READ_WRITE
+       Open the database in read-write mode
diff --git a/bindings/python/docs/source/filesystem.rst b/bindings/python/docs/source/filesystem.rst
new file mode 100644 (file)
index 0000000..13fe119
--- /dev/null
@@ -0,0 +1,32 @@
+Files and directories
+=====================
+
+.. currentmodule:: notmuch
+
+:class:`Filenames` -- An iterator over filenames
+------------------------------------------------
+
+.. autoclass:: Filenames
+
+   .. method:: Filenames.__len__
+   .. warning::
+      :meth:`__len__` was removed in version 0.22 as it exhausted the
+      iterator and broke list(Filenames()). Use `len(list(names))`
+      instead.
+
+:class:`Directory` -- A directory entry in the database
+-------------------------------------------------------
+
+.. autoclass:: Directory
+
+   .. automethod:: Directory.get_child_files
+
+   .. automethod:: Directory.get_child_directories
+
+   .. automethod:: Directory.get_mtime
+
+   .. automethod:: Directory.set_mtime
+
+   .. autoattribute:: Directory.mtime
+
+   .. autoattribute:: Directory.path
diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst
new file mode 100644 (file)
index 0000000..bef7e60
--- /dev/null
@@ -0,0 +1,36 @@
+Welcome to :mod:`notmuch`'s documentation
+=========================================
+
+.. currentmodule:: notmuch
+
+The :mod:`notmuch` module provides an interface to the `notmuch
+<https://notmuchmail.org>`_ functionality, directly interfacing to a
+shared notmuch library.  Within :mod:`notmuch`, the classes
+:class:`Database`, :class:`Query` provide most of the core
+functionality, returning :class:`Threads`, :class:`Messages` and
+:class:`Tags`.
+
+.. moduleauthor:: Sebastian Spaeth <Sebastian@SSpaeth.de>
+
+:License: This module is covered under the GNU GPL v3 (or later).
+
+.. toctree::
+   :maxdepth: 1
+
+   quickstart
+   notes
+   status_and_errors
+   database
+   query
+   messages
+   message
+   tags
+   threads
+   thread
+   filesystem
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/bindings/python/docs/source/message.rst b/bindings/python/docs/source/message.rst
new file mode 100644 (file)
index 0000000..b003392
--- /dev/null
@@ -0,0 +1,54 @@
+:class:`Message` -- A single message
+====================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Message
+
+   .. automethod:: get_message_id
+
+   .. automethod:: get_thread_id
+
+   .. automethod:: get_replies
+
+   .. automethod:: get_filename
+
+   .. automethod:: get_filenames
+
+   .. attribute:: FLAG
+
+        FLAG.MATCH
+          This flag is automatically set by a
+         Query.search_threads on those messages that match the
+         query. This allows us to distinguish matches from the rest
+         of the messages in that thread.
+
+   .. automethod:: get_flag
+
+   .. automethod:: set_flag
+
+   .. automethod:: get_date
+
+   .. automethod:: get_header
+
+   .. automethod:: get_tags
+
+   .. automethod:: get_property
+
+   .. automethod:: get_properties
+
+   .. automethod:: maildir_flags_to_tags
+
+   .. automethod:: tags_to_maildir_flags
+
+   .. automethod:: remove_tag
+
+   .. automethod:: add_tag
+
+   .. automethod:: remove_all_tags
+
+   .. automethod:: freeze
+
+   .. automethod:: thaw
+
+   .. automethod:: __str__
diff --git a/bindings/python/docs/source/messages.rst b/bindings/python/docs/source/messages.rst
new file mode 100644 (file)
index 0000000..3ccf505
--- /dev/null
@@ -0,0 +1,15 @@
+:class:`Messages` -- A bunch of messages
+========================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Messages
+
+   .. automethod:: collect_tags
+
+   .. method:: __len__()
+
+   .. warning::
+
+      :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
+      list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`.
diff --git a/bindings/python/docs/source/notes.rst b/bindings/python/docs/source/notes.rst
new file mode 100644 (file)
index 0000000..a792748
--- /dev/null
@@ -0,0 +1,6 @@
+Interfacing with notmuch
+========================
+
+.. todo:: move the note about talloc out of the code
+
+.. automodule:: notmuch
diff --git a/bindings/python/docs/source/query.rst b/bindings/python/docs/source/query.rst
new file mode 100644 (file)
index 0000000..785e984
--- /dev/null
@@ -0,0 +1,43 @@
+:class:`Query` -- A search query
+================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Query
+
+   .. automethod:: create
+
+   .. attribute:: Query.SORT
+
+     Defines constants that are used as the mode in which to open a database.
+
+     SORT.OLDEST_FIRST
+       Sort by message date, oldest first.
+
+     SORT.NEWEST_FIRST
+       Sort by message date, newest first.
+
+     SORT.MESSAGE_ID
+       Sort by email message ID.
+
+     SORT.UNSORTED
+       Do not apply a special sort order (returns results in document id
+       order).
+
+   .. automethod:: set_sort
+
+   .. attribute::  sort
+
+      Instance attribute :attr:`sort` contains the sort order (see
+      :attr:`Query.SORT`) if explicitly specified via
+      :meth:`set_sort`. By default it is set to `None`.
+
+   .. automethod:: exclude_tag
+
+   .. automethod:: search_threads
+
+   .. automethod:: search_messages
+
+   .. automethod:: count_messages
+
+   .. automethod:: count_threads
diff --git a/bindings/python/docs/source/quickstart.rst b/bindings/python/docs/source/quickstart.rst
new file mode 100644 (file)
index 0000000..609f42e
--- /dev/null
@@ -0,0 +1,19 @@
+Quickstart and examples
+=======================
+
+.. todo:: write a nice introduction
+.. todo:: improve the examples
+
+Notmuch can be imported as::
+
+    import notmuch
+
+or::
+
+    from notmuch import Query, Database
+
+    db = Database('path', create=True)
+    msgs = Query(db, 'from:myself').search_messages()
+
+    for msg in msgs:
+        print(msg)
diff --git a/bindings/python/docs/source/status_and_errors.rst b/bindings/python/docs/source/status_and_errors.rst
new file mode 100644 (file)
index 0000000..68913f1
--- /dev/null
@@ -0,0 +1,57 @@
+.. currentmodule:: notmuch
+
+Status and Errors
+=================
+
+Some methods return a status, indicating if an operation was successful and what the error was. Most of these status codes are expressed as a specific value, the :class:`notmuch.STATUS`.
+
+.. note::
+
+    Prior to version 0.12 the exception classes and the enumeration
+    :class:`notmuch.STATUS` were defined in `notmuch.globals`. They
+    have since then been moved into `notmuch.errors`.
+
+:class:`STATUS` -- Notmuch operation return value
+--------------------------------------------------
+
+.. autoclass:: notmuch.STATUS
+   :inherited-members:
+
+.. automethod:: notmuch.STATUS.status2str
+
+:exc:`NotmuchError` -- A Notmuch execution error
+------------------------------------------------
+Whenever an error occurs, we throw a special Exception :exc:`NotmuchError`, or a more fine grained Exception which is derived from it. This means it is always safe to check for NotmuchErrors if you want to catch all errors. If you are interested in more fine grained exceptions, you can use those below.
+
+.. autoexception:: NotmuchError
+
+The following exceptions are all directly derived from NotmuchError. Each of them corresponds to a specific :class:`notmuch.STATUS` value. You can either check the :attr:`status` attribute of a NotmuchError to see if a specific error has occurred, or you can directly check for the following Exception types:
+
+.. autoexception:: OutOfMemoryError(message=None)
+   :members:
+.. autoexception:: ReadOnlyDatabaseError(message=None)
+   :members:
+.. autoexception:: XapianError(message=None)
+   :members:
+.. autoexception:: FileError(message=None)
+   :members:
+.. autoexception:: FileNotEmailError(message=None)
+   :members:
+.. autoexception:: DuplicateMessageIdError(message=None)
+   :members:
+.. autoexception:: NullPointerError(message=None)
+   :members:
+.. autoexception:: TagTooLongError(message=None)
+   :members:
+.. autoexception:: UnbalancedFreezeThawError(message=None)
+   :members:
+.. autoexception:: UnbalancedAtomicError(message=None)
+   :members:
+.. autoexception:: UnsupportedOperationError(message=None)
+   :members:
+.. autoexception:: UpgradeRequiredError(message=None)
+   :members:
+.. autoexception:: PathError(message=None)
+   :members:
+.. autoexception:: NotInitializedError(message=None)
+   :members:
diff --git a/bindings/python/docs/source/tags.rst b/bindings/python/docs/source/tags.rst
new file mode 100644 (file)
index 0000000..31527d4
--- /dev/null
@@ -0,0 +1,17 @@
+:class:`Tags` -- Notmuch tags
+-----------------------------
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Tags
+   :members:
+
+   .. method:: __len__
+
+       .. warning::
+
+            :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke
+            list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of
+            tags.
+
+   .. automethod:: __str__
diff --git a/bindings/python/docs/source/thread.rst b/bindings/python/docs/source/thread.rst
new file mode 100644 (file)
index 0000000..4067872
--- /dev/null
@@ -0,0 +1,26 @@
+:class:`Thread` -- A single thread
+==================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Thread
+
+  .. automethod:: get_thread_id
+
+  .. automethod:: get_total_messages
+
+  .. automethod:: get_toplevel_messages
+
+  .. automethod:: get_matched_messages
+
+  .. automethod:: get_authors
+
+  .. automethod:: get_subject
+
+  .. automethod:: get_oldest_date
+
+  .. automethod:: get_newest_date
+
+  .. automethod:: get_tags
+
+  .. automethod:: __str__
diff --git a/bindings/python/docs/source/threads.rst b/bindings/python/docs/source/threads.rst
new file mode 100644 (file)
index 0000000..46ce5be
--- /dev/null
@@ -0,0 +1,14 @@
+:class:`Threads` -- Threads iterator
+====================================
+
+.. currentmodule:: notmuch
+
+.. autoclass:: Threads
+
+   .. method:: __len__
+   .. warning::
+      :meth:`__len__` was removed in version 0.22 as it exhausted the
+      iterator and broke list(Threads()). Use `len(list(msgs))`
+      instead.
+
+   .. automethod:: __str__
diff --git a/bindings/python/notmuch/__init__.py b/bindings/python/notmuch/__init__.py
new file mode 100644 (file)
index 0000000..cf627ff
--- /dev/null
@@ -0,0 +1,84 @@
+"""The :mod:`notmuch` module provides most of the functionality that a user is
+likely to need.
+
+.. note:: The underlying notmuch library is build on a hierarchical
+    memory allocator called talloc. All objects derive from a
+    top-level :class:`Database` object.
+
+    This means that as soon as an object is deleted, all underlying
+    derived objects such as Queries, Messages, Message, and Tags will
+    be freed by the underlying library as well. Accessing these
+    objects will then lead to segfaults and other unexpected behavior.
+
+    We implement reference counting, so that parent objects can be
+    automatically freed when they are not needed anymore. For
+    example::
+
+            db = Database('path',create=True)
+            msgs = Query(db,'from:myself').search_messages()
+
+    This returns :class:`Messages` which internally contains a
+    reference to its parent :class:`Query` object. Otherwise the
+    Query() would be immediately freed, taking our *msgs* down with
+    it.
+
+    In this case, the above Query() object will be automatically freed
+    whenever we delete all derived objects, ie in our case:
+    `del(msgs)` would also delete the parent Query. It would not
+    delete the parent Database() though, as that is still referenced
+    from the variable *db* in which it is stored.
+
+    Pretty much the same is valid for all other objects in the
+    hierarchy, such as :class:`Query`, :class:`Messages`,
+    :class:`Message`, and :class:`Tags`.
+"""
+
+"""
+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/>.
+
+Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from .database import Database
+from .directory import Directory
+from .filenames import Filenames
+from .message import Message
+from .messages import Messages
+from .query import Query
+from .tag import Tags
+from .thread import Thread
+from .threads import Threads
+from .globals import nmlib
+from .errors import (
+    STATUS,
+    NotmuchError,
+    OutOfMemoryError,
+    ReadOnlyDatabaseError,
+    XapianError,
+    FileError,
+    FileNotEmailError,
+    DuplicateMessageIdError,
+    NullPointerError,
+    TagTooLongError,
+    UnbalancedFreezeThawError,
+    UnbalancedAtomicError,
+    NotInitializedError,
+    UnsupportedOperationError,
+    UpgradeRequiredError,
+    PathError,
+)
+from .version import __VERSION__
+__LICENSE__ = "GPL v3+"
+__AUTHOR__ = 'Sebastian Spaeth <Sebastian@SSpaeth.de>'
diff --git a/bindings/python/notmuch/compat.py b/bindings/python/notmuch/compat.py
new file mode 100644 (file)
index 0000000..c931329
--- /dev/null
@@ -0,0 +1,71 @@
+'''
+This file is part of notmuch.
+
+This module handles differences between python2.x and python3.x and
+allows the notmuch bindings to support both version families with one
+source tree.
+
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+Copyright 2012 Justus Winter <4winter@informatik.uni-hamburg.de>
+'''
+
+import sys
+
+if sys.version_info[0] == 2:
+    from ConfigParser import SafeConfigParser
+
+    class Python3StringMixIn(object):
+        def __str__(self):
+            return unicode(self).encode('utf-8')
+
+    def encode_utf8(value):
+        '''
+        Ensure a nicely utf-8 encoded string to pass to wrapped
+        libnotmuch functions.
+
+        C++ code expects strings to be well formatted and unicode
+        strings to have no null bytes.
+        '''
+        if not isinstance(value, basestring):
+            raise TypeError('Expected str or unicode, got %s' % type(value))
+
+        if isinstance(value, unicode):
+            return value.encode('utf-8', 'replace')
+
+        return value
+else:
+    from configparser import SafeConfigParser
+
+    class Python3StringMixIn(object):
+        def __str__(self):
+            return self.__unicode__()
+
+    def encode_utf8(value):
+        '''
+        Ensure a nicely utf-8 encoded string to pass to wrapped
+        libnotmuch functions.
+
+        C++ code expects strings to be well formatted and unicode
+        strings to have no null bytes.
+        '''
+        if not isinstance(value, str):
+            raise TypeError('Expected str, got %s' % type(value))
+
+        return value.encode('utf-8', 'replace')
+
+# We import the SafeConfigParser class on behalf of other code to cope
+# with the differences between Python 2 and 3.
+SafeConfigParser # avoid warning about unused import
diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py
new file mode 100644 (file)
index 0000000..342d665
--- /dev/null
@@ -0,0 +1,786 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+import os
+import codecs
+import warnings
+from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
+from .compat import SafeConfigParser
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    NotmuchConfigListP,
+    NotmuchDatabaseP,
+    NotmuchDirectoryP,
+    NotmuchIndexoptsP,
+    NotmuchMessageP,
+    NotmuchTagsP,
+)
+from .errors import (
+    STATUS,
+    FileError,
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .message import Message
+from .tag import Tags
+from .query import Query
+from .directory import Directory
+
+class Database(object):
+    """The :class:`Database` is the highest-level object that notmuch
+    provides. It references a notmuch database, and can be opened in
+    read-only or read-write mode. A :class:`Query` can be derived from
+    or be applied to a specific database to find messages. Also adding
+    and removing messages to the database happens via this
+    object. Modifications to the database are not atmic by default (see
+    :meth:`begin_atomic`) and once a database has been modified, all
+    other database objects pointing to the same data-base will throw an
+    :exc:`XapianError` as the underlying database has been
+    modified. Close and reopen the database to continue working with it.
+
+    :class:`Database` objects implement the context manager protocol
+    so you can use the :keyword:`with` statement to ensure that the
+    database is properly closed. See :meth:`close` for more
+    information.
+
+    .. note::
+
+        Any function in this class can and will throw an
+        :exc:`NotInitializedError` if the database was not intitialized
+        properly.
+    """
+    _std_db_path = None
+    """Class attribute to cache user's default database"""
+
+    MODE = Enum(['READ_ONLY', 'READ_WRITE'])
+    """Constants: Mode in which to open the database"""
+
+    DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH'])
+    """Constants: policies for decrypting messages during indexing"""
+
+    """notmuch_database_get_directory"""
+    _get_directory = nmlib.notmuch_database_get_directory
+    _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)]
+    _get_directory.restype = c_uint
+
+    """notmuch_database_get_path"""
+    _get_path = nmlib.notmuch_database_get_path
+    _get_path.argtypes = [NotmuchDatabaseP]
+    _get_path.restype = c_char_p
+
+    """notmuch_database_get_version"""
+    _get_version = nmlib.notmuch_database_get_version
+    _get_version.argtypes = [NotmuchDatabaseP]
+    _get_version.restype = c_uint
+
+    """notmuch_database_get_revision"""
+    _get_revision = nmlib.notmuch_database_get_revision
+    _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
+    _get_revision.restype = c_uint
+
+    """notmuch_database_open"""
+    _open = nmlib.notmuch_database_open
+    _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)]
+    _open.restype = c_uint
+
+    """notmuch_database_upgrade"""
+    _upgrade = nmlib.notmuch_database_upgrade
+    _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
+    _upgrade.restype = c_uint
+
+    """ notmuch_database_find_message"""
+    _find_message = nmlib.notmuch_database_find_message
+    _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
+                              POINTER(NotmuchMessageP)]
+    _find_message.restype = c_uint
+
+    """notmuch_database_find_message_by_filename"""
+    _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
+    _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
+                                          POINTER(NotmuchMessageP)]
+    _find_message_by_filename.restype = c_uint
+
+    """notmuch_database_get_all_tags"""
+    _get_all_tags = nmlib.notmuch_database_get_all_tags
+    _get_all_tags.argtypes = [NotmuchDatabaseP]
+    _get_all_tags.restype = NotmuchTagsP
+
+    """notmuch_database_create"""
+    _create = nmlib.notmuch_database_create
+    _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)]
+    _create.restype = c_uint
+
+    def __init__(self, path = None, create = False,
+                 mode = MODE.READ_ONLY):
+        """If *path* is `None`, we will try to read a users notmuch
+        configuration and use his configured database. The location of the
+        configuration file can be specified through the environment variable
+        *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
+
+        If *create* is `True`, the database will always be created in
+        :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
+
+        :param path:   Directory to open/create the database in (see
+                       above for behavior if `None`)
+        :type path:    `str` or `None`
+        :param create: Pass `False` to open an existing, `True` to create a new
+                       database.
+        :type create:  bool
+        :param mode:   Mode to open a database in. Is always
+                       :attr:`MODE`.READ_WRITE when creating a new one.
+        :type mode:    :attr:`MODE`
+        :raises: :exc:`NotmuchError` or derived exception in case of
+            failure.
+        """
+        self._db = None
+        self.mode = mode
+        if path is None:
+            # no path specified. use a user's default database
+            if Database._std_db_path is None:
+                #the following line throws a NotmuchError if it fails
+                Database._std_db_path = self._get_user_default_db()
+            path = Database._std_db_path
+
+        if create == False:
+            self.open(path, mode)
+        else:
+            self.create(path)
+
+    _destroy = nmlib.notmuch_database_destroy
+    _destroy.argtypes = [NotmuchDatabaseP]
+    _destroy.restype = c_uint
+
+    def __del__(self):
+        if self._db:
+            status = self._destroy(self._db)
+            if status != STATUS.SUCCESS:
+                raise NotmuchError(status)
+
+    def _assert_db_is_initialized(self):
+        """Raises :exc:`NotInitializedError` if self._db is `None`"""
+        if not self._db:
+            raise NotInitializedError()
+
+    def create(self, path):
+        """Creates a new notmuch database
+
+        This function is used by __init__() and usually does not need
+        to be called directly. It wraps the underlying
+        *notmuch_database_create* function and creates a new notmuch
+        database at *path*. It will always return a database in :attr:`MODE`
+        .READ_WRITE mode as creating an empty database for
+        reading only does not make a great deal of sense.
+
+        :param path: A directory in which we should create the database.
+        :type path: str
+        :raises: :exc:`NotmuchError` in case of any failure
+                    (possibly after printing an error message on stderr).
+        """
+        if self._db:
+            raise NotmuchError(message="Cannot create db, this Database() "
+                                       "already has an open one.")
+
+        db = NotmuchDatabaseP()
+        status = Database._create(_str(path), byref(db))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        self._db = db
+        return status
+
+    def open(self, path, mode=0):
+        """Opens an existing database
+
+        This function is used by __init__() and usually does not need
+        to be called directly. It wraps the underlying
+        *notmuch_database_open* function.
+
+        :param status: Open the database in read-only or read-write mode
+        :type status:  :attr:`MODE`
+        :raises: Raises :exc:`NotmuchError` in case of any failure
+                    (possibly after printing an error message on stderr).
+        """
+        db = NotmuchDatabaseP()
+        status = Database._open(_str(path), mode, byref(db))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        self._db = db
+        return status
+
+    _close = nmlib.notmuch_database_close
+    _close.argtypes = [NotmuchDatabaseP]
+    _close.restype = c_uint
+
+    def close(self):
+        '''
+        Closes the notmuch database.
+
+        .. warning::
+
+            This function closes the notmuch database. From that point
+            on every method invoked on any object ever derived from
+            the closed database may cease to function and raise a
+            NotmuchError.
+        '''
+        if self._db:
+            status = self._close(self._db)
+            if status != STATUS.SUCCESS:
+                raise NotmuchError(status)
+
+    def __enter__(self):
+        '''
+        Implements the context manager protocol.
+        '''
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        '''
+        Implements the context manager protocol.
+        '''
+        self.close()
+
+    def get_path(self):
+        """Returns the file path of an open database"""
+        self._assert_db_is_initialized()
+        return Database._get_path(self._db).decode('utf-8')
+
+    def get_version(self):
+        """Returns the database format version
+
+        :returns: The database version as positive integer
+        """
+        self._assert_db_is_initialized()
+        return Database._get_version(self._db)
+
+    def get_revision (self):
+        """Returns the committed database revison and UUID
+
+        :returns: (revison, uuid) The database revision as a positive integer
+        and the UUID of the database.
+        """
+        self._assert_db_is_initialized()
+        uuid = c_char_p ()
+        revision = Database._get_revision(self._db, byref (uuid))
+        return (revision, uuid.value.decode ('utf-8'))
+
+    _needs_upgrade = nmlib.notmuch_database_needs_upgrade
+    _needs_upgrade.argtypes = [NotmuchDatabaseP]
+    _needs_upgrade.restype = bool
+
+    def needs_upgrade(self):
+        """Does this database need to be upgraded before writing to it?
+
+        If this function returns `True` then no functions that modify the
+        database (:meth:`index_file`,
+        :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
+        etc.) will work unless :meth:`upgrade` is called successfully first.
+
+        :returns: `True` or `False`
+        """
+        self._assert_db_is_initialized()
+        return self._needs_upgrade(self._db)
+
+    def upgrade(self):
+        """Upgrades the current database
+
+        After opening a database in read-write mode, the client should
+        check if an upgrade is needed (notmuch_database_needs_upgrade) and
+        if so, upgrade with this function before making any modifications.
+
+        NOT IMPLEMENTED: The optional progress_notify callback can be
+        used by the caller to provide progress indication to the
+        user. If non-NULL it will be called periodically with
+        'progress' as a floating-point value in the range of [0.0..1.0]
+        indicating the progress made so far in the upgrade process.
+
+        :TODO: catch exceptions, document return values and etc...
+        """
+        self._assert_db_is_initialized()
+        status = Database._upgrade(self._db, None, None)
+        # TODO: catch exceptions, document return values and etc
+        return status
+
+    _begin_atomic = nmlib.notmuch_database_begin_atomic
+    _begin_atomic.argtypes = [NotmuchDatabaseP]
+    _begin_atomic.restype = c_uint
+
+    def begin_atomic(self):
+        """Begin an atomic database operation
+
+        Any modifications performed between a successful
+        :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
+        the database atomically.  Note that, unlike a typical database
+        transaction, this only ensures atomicity, not durability;
+        neither begin nor end necessarily flush modifications to disk.
+
+        :returns: :attr:`STATUS`.SUCCESS or raises
+        :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
+                    Xapian exception occurred; atomic section not entered.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+        status = self._begin_atomic(self._db)
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return status
+
+    _end_atomic = nmlib.notmuch_database_end_atomic
+    _end_atomic.argtypes = [NotmuchDatabaseP]
+    _end_atomic.restype = c_uint
+
+    def end_atomic(self):
+        """Indicate the end of an atomic database operation
+
+        See :meth:`begin_atomic` for details.
+
+        :returns: :attr:`STATUS`.SUCCESS or raises
+
+        :raises:
+            :exc:`NotmuchError`:
+                :attr:`STATUS`.XAPIAN_EXCEPTION
+                    A Xapian exception occurred; atomic section not
+                    ended.
+                :attr:`STATUS`.UNBALANCED_ATOMIC:
+                    end_atomic has been called more times than begin_atomic.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+        status = self._end_atomic(self._db)
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return status
+
+    def get_directory(self, path):
+        """Returns a :class:`Directory` of path,
+
+        :param path: An unicode string containing the path relative to the path
+              of database (see :meth:`get_path`), or else should be an absolute
+              path with initial components that match the path of 'database'.
+        :returns: :class:`Directory` or raises an exception.
+        :raises: :exc:`FileError` if path is not relative database or absolute
+                 with initial components same as database.
+        """
+        self._assert_db_is_initialized()
+
+        # sanity checking if path is valid, and make path absolute
+        if path and path[0] == os.sep:
+            # we got an absolute path
+            if not path.startswith(self.get_path()):
+                # but its initial components are not equal to the db path
+                raise FileError('Database().get_directory() called '
+                                'with a wrong absolute path')
+            abs_dirpath = path
+        else:
+            #we got a relative path, make it absolute
+            abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
+
+        dir_p = NotmuchDirectoryP()
+        status = Database._get_directory(self._db, _str(path), byref(dir_p))
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        if not dir_p:
+            return None
+
+        # return the Directory, init it with the absolute path
+        return Directory(abs_dirpath, dir_p, self)
+
+    _get_default_indexopts = nmlib.notmuch_database_get_default_indexopts
+    _get_default_indexopts.argtypes = [NotmuchDatabaseP]
+    _get_default_indexopts.restype = NotmuchIndexoptsP
+
+    _indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy
+    _indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint]
+    _indexopts_set_decrypt_policy.restype = None
+
+    _indexopts_destroy = nmlib.notmuch_indexopts_destroy
+    _indexopts_destroy.argtypes = [NotmuchIndexoptsP]
+    _indexopts_destroy.restype = None
+
+    _index_file = nmlib.notmuch_database_index_file
+    _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
+                             c_void_p,
+                             POINTER(NotmuchMessageP)]
+    _index_file.restype = c_uint
+
+    def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None):
+        """Adds a new message to the database
+
+        :param filename: should be a path relative to the path of the
+            open database (see :meth:`get_path`), or else should be an
+            absolute filename with initial components that match the
+            path of the database.
+
+            The file should be a single mail message (not a
+            multi-message mbox) that is expected to remain at its
+            current location, since the notmuch database will reference
+            the filename, and will not copy the entire contents of the
+            file.
+
+        :param sync_maildir_flags: If the message contains Maildir
+            flags, we will -depending on the notmuch configuration- sync
+            those tags to initial notmuch tags, if set to `True`. It is
+            `False` by default to remain consistent with the libnotmuch
+            API. You might want to look into the underlying method
+            :meth:`Message.maildir_flags_to_tags`.
+
+        :param decrypt_policy: If the message contains any encrypted
+            parts, and decrypt_policy is set to
+            :attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to
+            decrypt the message and index the cleartext, stashing any
+            discovered session keys.  If it is set to
+            :attr:`DECRYPTION_POLICY`.FALSE, it will never try to
+            decrypt during indexing.  If it is set to
+            :attr:`DECRYPTION_POLICY`.AUTO, then it will try to use
+            any stashed session keys it knows about, but will not try
+            to access the user's secret keys.
+            :attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as
+            :attr:`DECRYPTION_POLICY`.TRUE except that no session keys
+            are stashed in the database.  If decrypt_policy is set to
+            None (the default), then the database itself will decide
+            whether to decrypt, based on the `index.decrypt`
+            configuration setting (see notmuch-config(1)).
+
+        :returns: On success, we return
+
+           1) a :class:`Message` object that can be used for things
+              such as adding tags to the just-added message.
+           2) one of the following :attr:`STATUS` values:
+
+              :attr:`STATUS`.SUCCESS
+                  Message successfully added to database.
+              :attr:`STATUS`.DUPLICATE_MESSAGE_ID
+                  Message has the same message ID as another message already
+                  in the database. The new filename was successfully added
+                  to the list of the filenames for the existing message.
+
+        :rtype:   2-tuple(:class:`Message`, :attr:`STATUS`)
+
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
+              If such an exception occurs, nothing was added to the database.
+
+              :attr:`STATUS`.FILE_ERROR
+                      An error occurred trying to open the file, (such as
+                      permission denied, or file not found, etc.).
+              :attr:`STATUS`.FILE_NOT_EMAIL
+                      The contents of filename don't look like an email
+                      message.
+              :attr:`STATUS`.READ_ONLY_DATABASE
+                      Database was opened in read-only mode so no message can
+                      be added.
+        """
+        self._assert_db_is_initialized()
+        msg_p = NotmuchMessageP()
+        indexopts = c_void_p(None)
+        if decrypt_policy is not None:
+            indexopts = self._get_default_indexopts(self._db)
+            self._indexopts_set_decrypt_policy(indexopts, decrypt_policy)
+
+        status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p))
+
+        if indexopts:
+            self._indexopts_destroy(indexopts)
+
+        if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
+            raise NotmuchError(status)
+
+        #construct Message() and return
+        msg = Message(msg_p, self)
+        #automatic sync initial tags from Maildir flags
+        if sync_maildir_flags:
+            msg.maildir_flags_to_tags()
+        return (msg, status)
+
+    def add_message(self, filename, sync_maildir_flags=False):
+        """Deprecated alias for :meth:`index_file`
+        """
+        warnings.warn(
+                "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
+
+        return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
+
+    _remove_message = nmlib.notmuch_database_remove_message
+    _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
+    _remove_message.restype = c_uint
+
+    def remove_message(self, filename):
+        """Removes a message (filename) from the given notmuch database
+
+        Note that only this particular filename association is removed from
+        the database. If the same message (as determined by the message ID)
+        is still available via other filenames, then the message will
+        persist in the database for those filenames. When the last filename
+        is removed for a particular message, the database content for that
+        message will be entirely removed.
+
+        :returns: A :attr:`STATUS` value with the following meaning:
+
+             :attr:`STATUS`.SUCCESS
+               The last filename was removed and the message was removed
+               from the database.
+             :attr:`STATUS`.DUPLICATE_MESSAGE_ID
+               This filename was removed but the message persists in the
+               database with at least one other filename.
+
+        :raises: Raises a :exc:`NotmuchError` with the following meaning.
+             If such an exception occurs, nothing was removed from the
+             database.
+
+             :attr:`STATUS`.READ_ONLY_DATABASE
+               Database was opened in read-only mode so no message can be
+               removed.
+        """
+        self._assert_db_is_initialized()
+        status = self._remove_message(self._db, _str(filename))
+        if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
+            raise NotmuchError(status)
+        return status
+
+    def find_message(self, msgid):
+        """Returns a :class:`Message` as identified by its message ID
+
+        Wraps the underlying *notmuch_database_find_message* function.
+
+        :param msgid: The message ID
+        :type msgid: unicode or str
+        :returns: :class:`Message` or `None` if no message is found.
+        :raises:
+            :exc:`OutOfMemoryError`
+                  If an Out-of-memory occurred while constructing the message.
+            :exc:`XapianError`
+                  In case of a Xapian Exception. These exceptions
+                  include "Database modified" situations, e.g. when the
+                  notmuch database has been modified by another program
+                  in the meantime. In this case, you should close and
+                  reopen the database and retry.
+            :exc:`NotInitializedError` if
+                    the database was not intitialized.
+        """
+        self._assert_db_is_initialized()
+        msg_p = NotmuchMessageP()
+        status = Database._find_message(self._db, _str(msgid), byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return msg_p and Message(msg_p, self) or None
+
+    def find_message_by_filename(self, filename):
+        """Find a message with the given filename
+
+        :returns: If the database contains a message with the given
+            filename, then a class:`Message:` is returned.  This
+            function returns None if no message is found with the given
+            filename.
+
+        :raises: :exc:`OutOfMemoryError` if an Out-of-memory occurred while
+                 constructing the message.
+        :raises: :exc:`XapianError` in case of a Xapian Exception.
+                 These exceptions include "Database modified"
+                 situations, e.g. when the notmuch database has been
+                 modified by another program in the meantime. In this
+                 case, you should close and reopen the database and
+                 retry.
+        :raises: :exc:`NotInitializedError` if the database was not
+                 intitialized.
+
+        *Added in notmuch 0.9*"""
+        self._assert_db_is_initialized()
+
+        msg_p = NotmuchMessageP()
+        status = Database._find_message_by_filename(self._db, _str(filename),
+                                                    byref(msg_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return msg_p and Message(msg_p, self) or None
+
+    def get_all_tags(self):
+        """Returns :class:`Tags` with a list of all tags found in the database
+
+        :returns: :class:`Tags`
+        :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
+                    on error
+        """
+        self._assert_db_is_initialized()
+        tags_p = Database._get_all_tags(self._db)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def create_query(self, querystring):
+        """Returns a :class:`Query` derived from this database
+
+        This is a shorthand method for doing::
+
+          # short version
+          # Automatically frees the Database() when 'q' is deleted
+
+          q  = Database(dbpath).create_query('from:"Biene Maja"')
+
+          # long version, which is functionally equivalent but will keep the
+          # Database in the 'db' variable around after we delete 'q':
+
+          db = Database(dbpath)
+          q  = Query(db,'from:"Biene Maja"')
+
+        This function is a python extension and not in the underlying C API.
+        """
+        return Query(self, querystring)
+
+    """notmuch_database_status_string"""
+    _status_string = nmlib.notmuch_database_status_string
+    _status_string.argtypes = [NotmuchDatabaseP]
+    _status_string.restype = c_char_p
+
+    def status_string(self):
+        """Returns the status string of the database
+
+        This is sometimes used for additional error reporting
+        """
+        self._assert_db_is_initialized()
+        s = Database._status_string(self._db)
+        if s:
+            return s.decode('utf-8', 'ignore')
+        return s
+
+    def __repr__(self):
+        return "'Notmuch DB " + self.get_path() + "'"
+
+    def _get_user_default_db(self):
+        """ Reads a user's notmuch config and returns his db location
+
+        Throws a NotmuchError if it cannot find it"""
+        config = SafeConfigParser()
+        conf_f = os.getenv('NOTMUCH_CONFIG',
+                           os.path.expanduser('~/.notmuch-config'))
+        config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
+        if not config.has_option('database', 'path'):
+            raise NotmuchError(message="No DB path specified"
+                                       " and no user default found")
+        return config.get('database', 'path')
+
+    """notmuch_database_get_config"""
+    _get_config = nmlib.notmuch_database_get_config
+    _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
+    _get_config.restype = c_uint
+
+    def get_config(self, key):
+        """Return the value of the given config key.
+
+        Note that only config values that are stored in the database are
+        searched and returned.  The config file is not read.
+
+        :param key: the config key under which a value should be looked up, it
+                    should probably be in the form "section.key"
+        :type key:  str
+        :returns:   the config value or the empty string if no value is present
+                    for that key
+        :rtype:     str
+        :raises:    :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        return_string = c_char_p()
+        status = self._get_config(self._db, _str(key), byref(return_string))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        return return_string.value.decode('utf-8')
+
+    """notmuch_database_get_config_list"""
+    _get_config_list = nmlib.notmuch_database_get_config_list
+    _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
+                                 POINTER(NotmuchConfigListP)]
+    _get_config_list.restype = c_uint
+
+    _config_list_valid = nmlib.notmuch_config_list_valid
+    _config_list_valid.argtypes = [NotmuchConfigListP]
+    _config_list_valid.restype = bool
+
+    _config_list_key = nmlib.notmuch_config_list_key
+    _config_list_key.argtypes = [NotmuchConfigListP]
+    _config_list_key.restype = c_char_p
+
+    _config_list_value = nmlib.notmuch_config_list_value
+    _config_list_value.argtypes = [NotmuchConfigListP]
+    _config_list_value.restype = c_char_p
+
+    _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
+    _config_list_move_to_next.argtypes = [NotmuchConfigListP]
+    _config_list_move_to_next.restype = None
+
+    _config_list_destroy = nmlib.notmuch_config_list_destroy
+    _config_list_destroy.argtypes = [NotmuchConfigListP]
+    _config_list_destroy.restype = None
+
+    def get_configs(self, prefix=''):
+        """Return a generator of key, value pairs where the start of key
+        matches the given prefix
+
+        Note that only config values that are stored in the database are
+        searched and returned.  The config file is not read.  If no `prefix` is
+        given all config values are returned.
+
+        This could be used to get all named queries into a dict for example::
+
+            queries = {k[6:]: v for k, v in db.get_configs('query.')}
+
+        :param prefix: a string by which the keys should be selected
+        :type prefix:  str
+        :yields:       all key-value pairs where `prefix` matches the beginning
+                       of the key
+        :ytype:        pairs of str
+        :raises:      :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        config_list_p = NotmuchConfigListP()
+        status = self._get_config_list(self._db, _str(prefix),
+                                       byref(config_list_p))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+        while self._config_list_valid(config_list_p):
+            key = self._config_list_key(config_list_p).decode('utf-8')
+            value = self._config_list_value(config_list_p).decode('utf-8')
+            yield key, value
+            self._config_list_move_to_next(config_list_p)
+
+    """notmuch_database_set_config"""
+    _set_config = nmlib.notmuch_database_set_config
+    _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
+    _set_config.restype = c_uint
+
+    def set_config(self, key, value):
+        """Set a config value in the notmuch database.
+
+        If an empty string is provided as `value` the `key` is unset!
+
+        :param key:   the key to set
+        :type key:    str
+        :param value: the value to store under `key`
+        :type value:  str
+        :returns:     None
+        :raises:      :exc:`NotmuchError` in case of failure.
+
+        """
+        self._assert_db_is_initialized()
+        status = self._set_config(self._db, _str(key), _str(value))
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py
new file mode 100644 (file)
index 0000000..b30c9e3
--- /dev/null
@@ -0,0 +1,185 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_uint, c_long
+from .globals import (
+    nmlib,
+    NotmuchDirectoryP,
+    NotmuchFilenamesP
+)
+from .errors import (
+    STATUS,
+    NotmuchError,
+    NotInitializedError,
+)
+from .filenames import Filenames
+
+class Directory(object):
+    """Represents a directory entry in the notmuch directory
+
+    Modifying attributes of this object will modify the
+    database, not the real directory attributes.
+
+    The Directory object is usually derived from another object
+    e.g. via :meth:`Database.get_directory`, and will automatically be
+    become invalid whenever that parent is deleted. You should
+    therefore initialized this object handing it a reference to the
+    parent, preventing the parent from automatically being garbage
+    collected.
+    """
+
+    """notmuch_directory_get_mtime"""
+    _get_mtime = nmlib.notmuch_directory_get_mtime
+    _get_mtime.argtypes = [NotmuchDirectoryP]
+    _get_mtime.restype = c_long
+
+    """notmuch_directory_set_mtime"""
+    _set_mtime = nmlib.notmuch_directory_set_mtime
+    _set_mtime.argtypes = [NotmuchDirectoryP, c_long]
+    _set_mtime.restype = c_uint
+
+    """notmuch_directory_get_child_files"""
+    _get_child_files = nmlib.notmuch_directory_get_child_files
+    _get_child_files.argtypes = [NotmuchDirectoryP]
+    _get_child_files.restype = NotmuchFilenamesP
+
+    """notmuch_directory_get_child_directories"""
+    _get_child_directories = nmlib.notmuch_directory_get_child_directories
+    _get_child_directories.argtypes = [NotmuchDirectoryP]
+    _get_child_directories.restype = NotmuchFilenamesP
+
+    def _assert_dir_is_initialized(self):
+        """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
+        if dir_p is None"""
+        if not self._dir_p:
+            raise NotInitializedError()
+
+    def __init__(self, path, dir_p, parent):
+        """
+        :param path:   The absolute path of the directory object.
+        :param dir_p:  The pointer to an internal notmuch_directory_t object.
+        :param parent: The object this Directory is derived from
+                       (usually a :class:`Database`). We do not directly use
+                       this, but store a reference to it as long as
+                       this Directory object lives. This keeps the
+                       parent object alive.
+        """
+        self._path = path
+        self._dir_p = dir_p
+        self._parent = parent
+
+    def set_mtime(self, mtime):
+        """Sets the mtime value of this directory in the database
+
+        The intention is for the caller to use the mtime to allow efficient
+        identification of new messages to be added to the database. The
+        recommended usage is as follows:
+
+        * Read the mtime of a directory from the filesystem
+
+        * Call :meth:`Database.index_file` for all mail files in
+          the directory
+
+        * Call notmuch_directory_set_mtime with the mtime read from the
+          filesystem.  Then, when wanting to check for updates to the
+          directory in the future, the client can call :meth:`get_mtime`
+          and know that it only needs to add files if the mtime of the
+          directory and files are newer than the stored timestamp.
+
+          .. note::
+
+                :meth:`get_mtime` function does not allow the caller to
+                distinguish a timestamp of 0 from a non-existent timestamp. So
+                don't store a timestamp of 0 unless you are comfortable with
+                that.
+
+        :param mtime: A (time_t) timestamp
+        :raises: :exc:`XapianError` a Xapian exception occurred, mtime
+                 not stored
+        :raises: :exc:`ReadOnlyDatabaseError` the database was opened
+                 in read-only mode so directory mtime cannot be modified
+        :raises: :exc:`NotInitializedError` the directory object has not
+                 been initialized
+        """
+        self._assert_dir_is_initialized()
+        status = Directory._set_mtime(self._dir_p, mtime)
+
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+    def get_mtime(self):
+        """Gets the mtime value of this directory in the database
+
+        Retrieves a previously stored mtime for this directory.
+
+        :param mtime: A (time_t) timestamp
+        :raises: :exc:`NotmuchError`:
+
+                        :attr:`STATUS`.NOT_INITIALIZED
+                          The directory has not been initialized
+        """
+        self._assert_dir_is_initialized()
+        return Directory._get_mtime(self._dir_p)
+
+    # Make mtime attribute a property of Directory()
+    mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
+                     and setting of the Directory *mtime* (read-write)
+
+                     See :meth:`get_mtime` and :meth:`set_mtime` for usage and
+                     possible exceptions.""")
+
+    def get_child_files(self):
+        """Gets a Filenames iterator listing all the filenames of
+        messages in the database within the given directory.
+
+        The returned filenames will be the basename-entries only (not
+        complete paths.
+        """
+        self._assert_dir_is_initialized()
+        files_p = Directory._get_child_files(self._dir_p)
+        return Filenames(files_p, self)
+
+    def get_child_directories(self):
+        """Gets a :class:`Filenames` iterator listing all the filenames of
+        sub-directories in the database within the given directory
+
+        The returned filenames will be the basename-entries only (not
+        complete paths.
+        """
+        self._assert_dir_is_initialized()
+        files_p = Directory._get_child_directories(self._dir_p)
+        return Filenames(files_p, self)
+
+    @property
+    def path(self):
+        """Returns the absolute path of this Directory (read-only)"""
+        return self._path
+
+    def __repr__(self):
+        """Object representation"""
+        return "<notmuch Directory object '%s'>" % self._path
+
+    _destroy = nmlib.notmuch_directory_destroy
+    _destroy.argtypes = [NotmuchDirectoryP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the Directory"""
+        if self._dir_p:
+            self._destroy(self._dir_p)
diff --git a/bindings/python/notmuch/errors.py b/bindings/python/notmuch/errors.py
new file mode 100644 (file)
index 0000000..b7684ef
--- /dev/null
@@ -0,0 +1,204 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_int
+
+from .globals import (
+    nmlib,
+    Enum,
+    Python3StringMixIn,
+)
+
+class Status(Enum):
+    """Enum with a string representation of a notmuch_status_t value."""
+    _status2str = nmlib.notmuch_status_to_string
+    _status2str.restype = c_char_p
+    _status2str.argtypes = [c_int]
+
+    def __init__(self, statuslist):
+        """It is initialized with a list of strings that are available as
+        Status().string1 - Status().stringn attributes.
+        """
+        super(Status, self).__init__(statuslist)
+
+    @classmethod
+    def status2str(self, status):
+        """Get a (unicode) string representation of a notmuch_status_t value."""
+        # define strings for custom error messages
+        if status == STATUS.NOT_INITIALIZED:
+            return "Operation on uninitialized object impossible."
+        return unicode(Status._status2str(status))
+
+STATUS = Status(['SUCCESS',
+  'OUT_OF_MEMORY',
+  'READ_ONLY_DATABASE',
+  'XAPIAN_EXCEPTION',
+  'FILE_ERROR',
+  'FILE_NOT_EMAIL',
+  'DUPLICATE_MESSAGE_ID',
+  'NULL_POINTER',
+  'TAG_TOO_LONG',
+  'UNBALANCED_FREEZE_THAW',
+  'UNBALANCED_ATOMIC',
+  'UNSUPPORTED_OPERATION',
+  'UPGRADE_REQUIRED',
+  'PATH_ERROR',
+  'NOT_INITIALIZED'])
+"""STATUS is a class, whose attributes provide constants that serve as return
+indicators for notmuch functions. Currently the following ones are defined. For
+possible return values and specific meaning for each method, see the method
+description.
+
+  * SUCCESS
+  * OUT_OF_MEMORY
+  * READ_ONLY_DATABASE
+  * XAPIAN_EXCEPTION
+  * FILE_ERROR
+  * FILE_NOT_EMAIL
+  * DUPLICATE_MESSAGE_ID
+  * NULL_POINTER
+  * TAG_TOO_LONG
+  * UNBALANCED_FREEZE_THAW
+  * UNBALANCED_ATOMIC
+  * UNSUPPORTED_OPERATION
+  * UPGRADE_REQUIRED
+  * PATH_ERROR
+  * NOT_INITIALIZED
+
+Invoke the class method `notmuch.STATUS.status2str` with a status value as
+argument to receive a human readable string"""
+STATUS.__name__ = 'STATUS'
+
+
+class NotmuchError(Exception, Python3StringMixIn):
+    """Is initiated with a (notmuch.STATUS[, message=None]). It will not
+    return an instance of the class NotmuchError, but a derived instance
+    of a more specific Error Message, e.g. OutOfMemoryError. Each status
+    but SUCCESS has a corresponding subclassed Exception."""
+
+    @classmethod
+    def get_exc_subclass(cls, status):
+        """Returns a fine grained Exception() type,
+        detailing the error status"""
+        subclasses = {
+            STATUS.OUT_OF_MEMORY: OutOfMemoryError,
+            STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
+            STATUS.XAPIAN_EXCEPTION: XapianError,
+            STATUS.FILE_ERROR: FileError,
+            STATUS.FILE_NOT_EMAIL: FileNotEmailError,
+            STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
+            STATUS.NULL_POINTER: NullPointerError,
+            STATUS.TAG_TOO_LONG: TagTooLongError,
+            STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
+            STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
+            STATUS.UNSUPPORTED_OPERATION: UnsupportedOperationError,
+            STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
+            STATUS.PATH_ERROR: PathError,
+            STATUS.NOT_INITIALIZED: NotInitializedError,
+        }
+        assert 0 < status <= len(subclasses)
+        return subclasses[status]
+
+    def __new__(cls, *args, **kwargs):
+        """Return a correct subclass of NotmuchError if needed
+
+        We return a NotmuchError instance if status is None (or 0) and a
+        subclass that inherits from NotmuchError depending on the
+        'status' parameter otherwise."""
+        # get 'status'. Passed in as arg or kwarg?
+        status = args[0] if len(args) else kwargs.get('status', None)
+        # no 'status' or cls is subclass already, return 'cls' instance
+        if not status or cls != NotmuchError:
+            return super(NotmuchError, cls).__new__(cls)
+        subclass = cls.get_exc_subclass(status)  # which class to use?
+        return subclass.__new__(subclass, *args, **kwargs)
+
+    def __init__(self, status=None, message=None):
+        self.status = status
+        self.message = message
+
+    def __unicode__(self):
+        if self.message is not None:
+            return self.message
+        elif self.status is not None:
+            return STATUS.status2str(self.status)
+        else:
+            return 'Unknown error'
+
+
+# List of Subclassed exceptions that correspond to STATUS values and are
+# subclasses of NotmuchError.
+class OutOfMemoryError(NotmuchError):
+    status = STATUS.OUT_OF_MEMORY
+
+
+class ReadOnlyDatabaseError(NotmuchError):
+    status = STATUS.READ_ONLY_DATABASE
+
+
+class XapianError(NotmuchError):
+    status = STATUS.XAPIAN_EXCEPTION
+
+
+class FileError(NotmuchError):
+    status = STATUS.FILE_ERROR
+
+
+class FileNotEmailError(NotmuchError):
+    status = STATUS.FILE_NOT_EMAIL
+
+
+class DuplicateMessageIdError(NotmuchError):
+    status = STATUS.DUPLICATE_MESSAGE_ID
+
+
+class NullPointerError(NotmuchError):
+    status = STATUS.NULL_POINTER
+
+
+class TagTooLongError(NotmuchError):
+    status = STATUS.TAG_TOO_LONG
+
+
+class UnbalancedFreezeThawError(NotmuchError):
+    status = STATUS.UNBALANCED_FREEZE_THAW
+
+
+class UnbalancedAtomicError(NotmuchError):
+    status = STATUS.UNBALANCED_ATOMIC
+
+
+class UnsupportedOperationError(NotmuchError):
+    status = STATUS.UNSUPPORTED_OPERATION
+
+
+class UpgradeRequiredError(NotmuchError):
+    status = STATUS.UPGRADE_REQUIRED
+
+
+class PathError(NotmuchError):
+    status = STATUS.PATH_ERROR
+
+
+class NotInitializedError(NotmuchError):
+    """Derived from NotmuchError, this occurs if the underlying data
+    structure (e.g. database is not initialized (yet) or an iterator has
+    been exhausted. You can test for NotmuchError with .status =
+    STATUS.NOT_INITIALIZED"""
+    status = STATUS.NOT_INITIALIZED
diff --git a/bindings/python/notmuch/filenames.py b/bindings/python/notmuch/filenames.py
new file mode 100644 (file)
index 0000000..3bbc22b
--- /dev/null
@@ -0,0 +1,131 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from ctypes import c_char_p
+from .globals import (
+    nmlib,
+    NotmuchFilenamesP,
+    Python3StringMixIn,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+
+
+class Filenames(Python3StringMixIn):
+    """Represents a list of filenames as returned by notmuch
+
+    Objects of this class implement the iterator protocol.
+
+    .. note::
+
+        The underlying library only provides a one-time iterator (it
+        cannot reset the iterator to the start). Thus iterating over
+        the function will "exhaust" the list of tags, and a subsequent
+        iteration attempt will raise a
+        :exc:`NotInitializedError`. Also note, that any function that
+        uses iteration (nearly all) will also exhaust the tags. So
+        both::
+
+           for name in filenames: print name
+
+        as well as::
+
+           list_of_names = list(names)
+
+        and even a simple::
+
+           #str() iterates over all tags to construct a space separated list
+           print(str(filenames))
+
+        will "exhaust" the Filenames. However, you can use
+        :meth:`Message.get_filenames` repeatedly to get fresh
+        Filenames objects to perform various actions on filenames.
+    """
+
+    #notmuch_filenames_get
+    _get = nmlib.notmuch_filenames_get
+    _get.argtypes = [NotmuchFilenamesP]
+    _get.restype = c_char_p
+
+    def __init__(self, files_p, parent):
+        """
+        :param files_p: A pointer to an underlying *notmuch_tags_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Tags` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
+             valid, we will raise an :exc:`NullPointerError`
+             if it is `None`.
+        :type files_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object (ie :class:`Message` these
+             filenames are derived from, and saves a
+             reference to it, so we can automatically delete the db object
+             once all derived objects are dead.
+        """
+        if not files_p:
+            raise NullPointerError()
+
+        self._files_p = files_p
+        #save reference to parent object so we keep it alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Filenames an iterator """
+        return self
+
+    _valid = nmlib.notmuch_filenames_valid
+    _valid.argtypes = [NotmuchFilenamesP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_filenames_move_to_next
+    _move_to_next.argtypes = [NotmuchFilenamesP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._files_p:
+            raise NotInitializedError()
+
+        if not self._valid(self._files_p):
+            self._files_p = None
+            raise StopIteration
+
+        file_ = Filenames._get(self._files_p)
+        self._move_to_next(self._files_p)
+        return file_.decode('utf-8', 'ignore')
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __unicode__(self):
+        """Represent Filenames() as newline-separated list of full paths
+
+        .. note::
+
+            This method exhausts the iterator object, so you will not be able to
+            iterate over them again.
+        """
+        return "\n".join(self)
+
+    _destroy = nmlib.notmuch_filenames_destroy
+    _destroy.argtypes = [NotmuchFilenamesP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch filenames"""
+        if self._files_p:
+            self._destroy(self._files_p)
diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py
new file mode 100644 (file)
index 0000000..11e328b
--- /dev/null
@@ -0,0 +1,105 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import CDLL, Structure, POINTER
+from notmuch.version import SOVERSION
+
+#-----------------------------------------------------------------------------
+#package-global instance of the notmuch library
+try:
+    from os import uname
+    if uname()[0] == 'Darwin':
+        nmlib = CDLL("libnotmuch.{0:s}.dylib".format(SOVERSION))
+    else:
+        nmlib = CDLL("libnotmuch.so.{0:s}".format(SOVERSION))
+except:
+    raise ImportError("Could not find shared 'notmuch' library.")
+
+from .compat import Python3StringMixIn, encode_utf8 as _str
+
+# We import these on behalf of other modules.  Silence warning about
+# these symbols not being used.
+Python3StringMixIn
+_str
+
+class Enum(object):
+    """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc..."""
+    def __init__(self, names):
+        for number, name in enumerate(names):
+            setattr(self, name, number)
+
+
+class NotmuchDatabaseS(Structure):
+    pass
+NotmuchDatabaseP = POINTER(NotmuchDatabaseS)
+
+
+class NotmuchQueryS(Structure):
+    pass
+NotmuchQueryP = POINTER(NotmuchQueryS)
+
+
+class NotmuchThreadsS(Structure):
+    pass
+NotmuchThreadsP = POINTER(NotmuchThreadsS)
+
+
+class NotmuchThreadS(Structure):
+    pass
+NotmuchThreadP = POINTER(NotmuchThreadS)
+
+
+class NotmuchMessagesS(Structure):
+    pass
+NotmuchMessagesP = POINTER(NotmuchMessagesS)
+
+
+class NotmuchMessageS(Structure):
+    pass
+NotmuchMessageP = POINTER(NotmuchMessageS)
+
+
+class NotmuchMessagePropertiesS(Structure):
+    pass
+NotmuchMessagePropertiesP = POINTER(NotmuchMessagePropertiesS)
+
+
+class NotmuchTagsS(Structure):
+    pass
+NotmuchTagsP = POINTER(NotmuchTagsS)
+
+
+class NotmuchDirectoryS(Structure):
+    pass
+NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
+
+
+class NotmuchFilenamesS(Structure):
+    pass
+NotmuchFilenamesP = POINTER(NotmuchFilenamesS)
+
+
+class NotmuchConfigListS(Structure):
+    pass
+NotmuchConfigListP = POINTER(NotmuchConfigListS)
+
+
+class NotmuchIndexoptsS(Structure):
+    pass
+NotmuchIndexoptsP = POINTER(NotmuchIndexoptsS)
diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py
new file mode 100644 (file)
index 0000000..de0fb41
--- /dev/null
@@ -0,0 +1,719 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+               Jesse Rosenthal <jrosenthal@jhu.edu>
+"""
+
+
+from ctypes import c_char_p, c_long, c_uint, c_int, POINTER, byref
+from datetime import date
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    Python3StringMixIn,
+    NotmuchTagsP,
+    NotmuchMessageP,
+    NotmuchMessagesP,
+    NotmuchMessagePropertiesP,
+    NotmuchFilenamesP,
+)
+from .errors import (
+    STATUS,
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .tag import Tags
+from .filenames import Filenames
+
+import email
+import sys
+
+
+class Message(Python3StringMixIn):
+    """Represents a single Email message
+
+    Technically, this wraps the underlying *notmuch_message_t*
+    structure. A user will usually not create these objects themselves
+    but get them as search results.
+
+    As it implements :meth:`__cmp__`, it is possible to compare two
+    :class:`Message`\s using `if msg1 == msg2: ...`.
+    """
+
+    """notmuch_message_get_filename (notmuch_message_t *message)"""
+    _get_filename = nmlib.notmuch_message_get_filename
+    _get_filename.argtypes = [NotmuchMessageP]
+    _get_filename.restype = c_char_p
+
+    """return all filenames for a message"""
+    _get_filenames = nmlib.notmuch_message_get_filenames
+    _get_filenames.argtypes = [NotmuchMessageP]
+    _get_filenames.restype = NotmuchFilenamesP
+
+    """notmuch_message_get_flag"""
+    _get_flag = nmlib.notmuch_message_get_flag
+    _get_flag.argtypes = [NotmuchMessageP, c_uint]
+    _get_flag.restype = bool
+
+    """notmuch_message_set_flag"""
+    _set_flag = nmlib.notmuch_message_set_flag
+    _set_flag.argtypes = [NotmuchMessageP, c_uint, c_int]
+    _set_flag.restype = None
+
+    """notmuch_message_get_message_id (notmuch_message_t *message)"""
+    _get_message_id = nmlib.notmuch_message_get_message_id
+    _get_message_id.argtypes = [NotmuchMessageP]
+    _get_message_id.restype = c_char_p
+
+    """notmuch_message_get_thread_id"""
+    _get_thread_id = nmlib.notmuch_message_get_thread_id
+    _get_thread_id.argtypes = [NotmuchMessageP]
+    _get_thread_id.restype = c_char_p
+
+    """notmuch_message_get_replies"""
+    _get_replies = nmlib.notmuch_message_get_replies
+    _get_replies.argtypes = [NotmuchMessageP]
+    _get_replies.restype = NotmuchMessagesP
+
+    """notmuch_message_get_tags (notmuch_message_t *message)"""
+    _get_tags = nmlib.notmuch_message_get_tags
+    _get_tags.argtypes = [NotmuchMessageP]
+    _get_tags.restype = NotmuchTagsP
+
+    _get_date = nmlib.notmuch_message_get_date
+    _get_date.argtypes = [NotmuchMessageP]
+    _get_date.restype = c_long
+
+    _get_header = nmlib.notmuch_message_get_header
+    _get_header.argtypes = [NotmuchMessageP, c_char_p]
+    _get_header.restype = c_char_p
+
+    """notmuch_status_t ..._maildir_flags_to_tags (notmuch_message_t *)"""
+    _tags_to_maildir_flags = nmlib.notmuch_message_tags_to_maildir_flags
+    _tags_to_maildir_flags.argtypes = [NotmuchMessageP]
+    _tags_to_maildir_flags.restype = c_int
+
+    """notmuch_status_t ..._tags_to_maildir_flags (notmuch_message_t *)"""
+    _maildir_flags_to_tags = nmlib.notmuch_message_maildir_flags_to_tags
+    _maildir_flags_to_tags.argtypes = [NotmuchMessageP]
+    _maildir_flags_to_tags.restype = c_int
+
+    """notmuch_message_get_property"""
+    _get_property = nmlib.notmuch_message_get_property
+    _get_property.argtypes = [NotmuchMessageP, c_char_p, POINTER(c_char_p)]
+    _get_property.restype = c_int
+
+    """notmuch_message_get_properties"""
+    _get_properties = nmlib.notmuch_message_get_properties
+    _get_properties.argtypes = [NotmuchMessageP, c_char_p, c_int]
+    _get_properties.restype = NotmuchMessagePropertiesP
+
+    """notmuch_message_properties_valid"""
+    _properties_valid = nmlib.notmuch_message_properties_valid
+    _properties_valid.argtypes = [NotmuchMessagePropertiesP]
+    _properties_valid.restype = bool
+
+    """notmuch_message_properties_value"""
+    _properties_value = nmlib.notmuch_message_properties_value
+    _properties_value.argtypes = [NotmuchMessagePropertiesP]
+    _properties_value.restype = c_char_p
+
+    """notmuch_message_properties_key"""
+    _properties_key = nmlib.notmuch_message_properties_key
+    _properties_key.argtypes = [NotmuchMessagePropertiesP]
+    _properties_key.restype = c_char_p
+
+    """notmuch_message_properties_move_to_next"""
+    _properties_move_to_next = nmlib.notmuch_message_properties_move_to_next
+    _properties_move_to_next.argtypes = [NotmuchMessagePropertiesP]
+    _properties_move_to_next.restype = None
+
+    #Constants: Flags that can be set/get with set_flag
+    FLAG = Enum(['MATCH'])
+
+    def __init__(self, msg_p, parent=None):
+        """
+        :param msg_p: A pointer to an internal notmuch_message_t
+            Structure.  If it is `None`, we will raise an
+            :exc:`NullPointerError`.
+
+        :param parent: A 'parent' object is passed which this message is
+              derived from. We save a reference to it, so we can
+              automatically delete the parent object once all derived
+              objects are dead.
+        """
+        if not msg_p:
+            raise NullPointerError()
+        self._msg = msg_p
+        #keep reference to parent, so we keep it alive
+        self._parent = parent
+
+    def get_message_id(self):
+        """Returns the message ID
+
+        :returns: String with a message ID
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_message_id(self._msg).decode('utf-8', 'ignore')
+
+    def get_thread_id(self):
+        """Returns the thread ID
+
+        The returned string belongs to 'message' will only be valid for as
+        long as the message is valid.
+
+        This function will not return `None` since Notmuch ensures that every
+        message belongs to a single thread.
+
+        :returns: String with a thread ID
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        return Message._get_thread_id(self._msg).decode('utf-8', 'ignore')
+
+    def get_replies(self):
+        """Gets all direct replies to this message as :class:`Messages`
+        iterator
+
+        .. note::
+
+            This call only makes sense if 'message' was ultimately obtained from
+            a :class:`Thread` object, (such as by coming directly from the
+            result of calling :meth:`Thread.get_toplevel_messages` or by any
+            number of subsequent calls to :meth:`get_replies`). If this message
+            was obtained through some non-thread means, (such as by a call to
+            :meth:`Query.search_messages`), then this function will return
+            an empty Messages iterator.
+
+        :returns: :class:`Messages`.
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        msgs_p = Message._get_replies(self._msg)
+
+        from .messages import Messages, EmptyMessagesResult
+
+        if not msgs_p:
+            return EmptyMessagesResult(self)
+
+        return Messages(msgs_p, self)
+
+    def get_date(self):
+        """Returns time_t of the message date
+
+        For the original textual representation of the Date header from the
+        message call notmuch_message_get_header() with a header value of
+        "date".
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_date(self._msg)
+
+    def get_header(self, header):
+        """Get the value of the specified header.
+
+        The value will be read from the actual message file, not from
+        the notmuch database. The header name is case insensitive.
+
+        Returns an empty string ("") if the message does not contain a
+        header line matching 'header'.
+
+        :param header: The name of the header to be retrieved.
+                       It is not case-sensitive.
+        :type header: str
+        :returns: The header value as string
+        :raises: :exc:`NotInitializedError` if the message is not
+                 initialized
+        :raises: :exc:`NullPointerError` if any error occurred
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        #Returns NULL if any error occurs.
+        header = Message._get_header(self._msg, _str(header))
+        if header == None:
+            raise NullPointerError()
+        return header.decode('UTF-8', 'ignore')
+
+    def get_filename(self):
+        """Returns the file path of the message file
+
+        :returns: Absolute file path & name of the message file
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_filename(self._msg).decode('utf-8', 'ignore')
+
+    def get_filenames(self):
+        """Get all filenames for the email corresponding to 'message'
+
+        Returns a Filenames() generator with all absolute filepaths for
+        messages recorded to have the same Message-ID. These files must
+        not necessarily have identical content."""
+        if not self._msg:
+            raise NotInitializedError()
+
+        files_p = Message._get_filenames(self._msg)
+
+        return Filenames(files_p, self)
+
+    def get_flag(self, flag):
+        """Checks whether a specific flag is set for this message
+
+        The method :meth:`Query.search_threads` sets
+        *Message.FLAG.MATCH* for those messages that match the
+        query. This method allows us to get the value of this flag.
+
+        :param flag: One of the :attr:`Message.FLAG` values (currently only
+                     *Message.FLAG.MATCH*
+        :returns: An unsigned int (0/1), indicating whether the flag is set.
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._get_flag(self._msg, flag)
+
+    def set_flag(self, flag, value):
+        """Sets/Unsets a specific flag for this message
+
+        :param flag: One of the :attr:`Message.FLAG` values (currently only
+                     *Message.FLAG.MATCH*
+        :param value: A bool indicating whether to set or unset the flag.
+
+        :raises: :exc:`NotInitializedError` if the message
+              is not initialized.
+        """
+        if not self._msg:
+            raise NotInitializedError()
+        self._set_flag(self._msg, flag, value)
+
+    def get_tags(self):
+        """Returns the message tags
+
+        :returns: A :class:`Tags` iterator.
+        :raises: :exc:`NotInitializedError` if the message is not
+                 initialized
+        :raises: :exc:`NullPointerError` if any error occurred
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        tags_p = Message._get_tags(self._msg)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    _add_tag = nmlib.notmuch_message_add_tag
+    _add_tag.argtypes = [NotmuchMessageP, c_char_p]
+    _add_tag.restype = c_uint
+
+    def add_tag(self, tag, sync_maildir_flags=False):
+        """Adds a tag to the given message
+
+        Adds a tag to the current message. The maximal tag length is defined in
+        the notmuch library and is currently 200 bytes.
+
+        :param tag: String with a 'tag' to be added.
+
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            underlying method :meth:`tags_to_maildir_flags`. Use False
+            if you want to add/remove many tags on a message without
+            having to physically rename the file every time. Do note,
+            that this will do nothing when a message is frozen, as tag
+            changes will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tag was successfully added.
+                  Raises an exception otherwise.
+        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
+        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
+                 Message.NOTMUCH_TAG_MAX)
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._add_tag(self._msg, _str(tag))
+
+        # bail out on failure
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _remove_tag = nmlib.notmuch_message_remove_tag
+    _remove_tag.argtypes = [NotmuchMessageP, c_char_p]
+    _remove_tag.restype = c_uint
+
+    def remove_tag(self, tag, sync_maildir_flags=False):
+        """Removes a tag from the given message
+
+        If the message has no such tag, this is a non-operation and
+        will report success anyway.
+
+        :param tag: String with a 'tag' to be removed.
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            underlying method :meth:`tags_to_maildir_flags`. Use False
+            if you want to add/remove many tags on a message without
+            having to physically rename the file every time. Do note,
+            that this will do nothing when a message is frozen, as tag
+            changes will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tag was successfully removed or if
+                  the message had no such tag.
+                  Raises an exception otherwise.
+        :raises: :exc:`NullPointerError` if the `tag` argument is NULL
+        :raises: :exc:`TagTooLongError` if the length of `tag` exceeds
+                 Message.NOTMUCH_TAG_MAX)
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._remove_tag(self._msg, _str(tag))
+        # bail out on error
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _remove_all_tags = nmlib.notmuch_message_remove_all_tags
+    _remove_all_tags.argtypes = [NotmuchMessageP]
+    _remove_all_tags.restype = c_uint
+
+    def remove_all_tags(self, sync_maildir_flags=False):
+        """Removes all tags from the given message.
+
+        See :meth:`freeze` for an example showing how to safely
+        replace tag values.
+
+
+        :param sync_maildir_flags: If notmuch configuration is set to do
+            this, add maildir flags corresponding to notmuch tags. See
+            :meth:`tags_to_maildir_flags`. Use False if you want to
+            add/remove many tags on a message without having to
+            physically rename the file every time. Do note, that this
+            will do nothing when a message is frozen, as tag changes
+            will not be committed to the database yet.
+
+        :returns: STATUS.SUCCESS if the tags were successfully removed.
+                  Raises an exception otherwise.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._remove_all_tags(self._msg)
+
+        # bail out on error
+        if status != STATUS.SUCCESS:
+            raise NotmuchError(status)
+
+        if sync_maildir_flags:
+            self.tags_to_maildir_flags()
+        return STATUS.SUCCESS
+
+    _freeze = nmlib.notmuch_message_freeze
+    _freeze.argtypes = [NotmuchMessageP]
+    _freeze.restype = c_uint
+
+    def get_property(self, prop):
+        """ Retrieve the value for a single property key
+
+        :param prop: The name of the property to get.
+        :returns: String with the property value or None if there is no such
+                  key. In the case of multiple values for the given key, the
+                  first one is retrieved.
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        value = c_char_p()
+        status = Message._get_property(self._msg, _str(prop), byref(value))
+        if status != 0:
+            raise NotmuchError(status)
+
+        return value.value.decode('utf-8') if value is not None else None
+
+    def get_properties(self, prop="", exact=False):
+        """ Get the properties of the message, returning a generator of
+        name, value pairs.
+
+        The generator will yield once per value. There might be more than one
+        value on each name, so the generator might yield the same name several
+        times.
+
+        :param prop: The name of the property to get. Otherwise it will return
+                     the full list of properties of the message.
+        :param exact: if True, require exact match with key. Otherwise
+                      treat as prefix.
+        :yields:  Each property values as a pair of `name, value`
+        :ytype:   pairs of str
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        properties = Message._get_properties(self._msg, _str(prop), exact)
+        while Message._properties_valid(properties):
+            key = Message._properties_key(properties)
+            value = Message._properties_value(properties)
+            yield key.decode("utf-8"), value.decode("utf-8")
+            Message._properties_move_to_next(properties)
+
+    def freeze(self):
+        """Freezes the current state of 'message' within the database
+
+        This means that changes to the message state, (via :meth:`add_tag`,
+        :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
+        committed to the database until the message is :meth:`thaw` ed.
+
+        Multiple calls to freeze/thaw are valid and these calls will
+        "stack". That is there must be as many calls to thaw as to freeze
+        before a message is actually thawed.
+
+        The ability to do freeze/thaw allows for safe transactions to
+        change tag values. For example, explicitly setting a message to
+        have a given set of tags might look like this::
+
+          msg.freeze()
+          msg.remove_all_tags(False)
+          for tag in new_tags:
+              msg.add_tag(tag, False)
+          msg.thaw()
+          msg.tags_to_maildir_flags()
+
+        With freeze/thaw used like this, the message in the database is
+        guaranteed to have either the full set of original tag values, or
+        the full set of new tag values, but nothing in between.
+
+        Imagine the example above without freeze/thaw and the operation
+        somehow getting interrupted. This could result in the message being
+        left with no tags if the interruption happened after
+        :meth:`remove_all_tags` but before :meth:`add_tag`.
+
+        :returns: STATUS.SUCCESS if the message was successfully frozen.
+                  Raises an exception otherwise.
+        :raises: :exc:`ReadOnlyDatabaseError` if the database was opened
+                 in read-only mode so message cannot be modified
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._freeze(self._msg)
+
+        if STATUS.SUCCESS == status:
+            # return on success
+            return status
+
+        raise NotmuchError(status)
+
+    _thaw = nmlib.notmuch_message_thaw
+    _thaw.argtypes = [NotmuchMessageP]
+    _thaw.restype = c_uint
+
+    def thaw(self):
+        """Thaws the current 'message'
+
+        Thaw the current 'message', synchronizing any changes that may have
+        occurred while 'message' was frozen into the notmuch database.
+
+        See :meth:`freeze` for an example of how to use this
+        function to safely provide tag changes.
+
+        Multiple calls to freeze/thaw are valid and these calls with
+        "stack". That is there must be as many calls to thaw as to freeze
+        before a message is actually thawed.
+
+        :returns: STATUS.SUCCESS if the message was successfully frozen.
+                  Raises an exception otherwise.
+        :raises: :exc:`UnbalancedFreezeThawError` if an attempt was made
+                 to thaw an unfrozen message. That is, there have been
+                 an unbalanced number of calls to :meth:`freeze` and
+                 :meth:`thaw`.
+        :raises: :exc:`NotInitializedError` if message has not been
+                 initialized
+        """
+        if not self._msg:
+            raise NotInitializedError()
+
+        status = self._thaw(self._msg)
+
+        if STATUS.SUCCESS == status:
+            # return on success
+            return status
+
+        raise NotmuchError(status)
+
+    def is_match(self):
+        """(Not implemented)"""
+        return self.get_flag(Message.FLAG.MATCH)
+
+    def tags_to_maildir_flags(self):
+        """Synchronize notmuch tags to file Maildir flags
+
+              'D' if the message has the "draft" tag
+              'F' if the message has the "flagged" tag
+              'P' if the message has the "passed" tag
+              'R' if the message has the "replied" tag
+              'S' if the message does not have the "unread" tag
+
+        Any existing flags unmentioned in the list above will be
+        preserved in the renaming.
+
+        Also, if this filename is in a directory named "new", rename it
+        to be within the neighboring directory named "cur".
+
+        Do note that calling this method while a message is frozen might
+        not work yet, as the modified tags have not been committed yet
+        to the database.
+
+        :returns: a :class:`STATUS` value. In short, you want to see
+            notmuch.STATUS.SUCCESS here. See there for details."""
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._tags_to_maildir_flags(self._msg)
+
+    def maildir_flags_to_tags(self):
+        """Synchronize file Maildir flags to notmuch tags
+
+            Flag    Action if present
+            ----    -----------------
+            'D'     Adds the "draft" tag to the message
+            'F'     Adds the "flagged" tag to the message
+            'P'     Adds the "passed" tag to the message
+            'R'     Adds the "replied" tag to the message
+            'S'     Removes the "unread" tag from the message
+
+        For each flag that is not present, the opposite action
+        (add/remove) is performed for the corresponding tags.  If there
+        are multiple filenames associated with this message, the flag is
+        considered present if it appears in one or more filenames. (That
+        is, the flags from the multiple filenames are combined with the
+        logical OR operator.)
+
+        As a convenience, you can set the sync_maildir_flags parameter in
+        :meth:`Database.index_file` to implicitly call this.
+
+        :returns: a :class:`STATUS`. In short, you want to see
+            notmuch.STATUS.SUCCESS here. See there for details."""
+        if not self._msg:
+            raise NotInitializedError()
+        return Message._maildir_flags_to_tags(self._msg)
+
+    def __repr__(self):
+        """Represent a Message() object by str()"""
+        return self.__str__()
+
+    def __unicode__(self):
+        format = "%s (%s) (%s)"
+        return format % (self.get_header('from'),
+                         self.get_tags(),
+                         date.fromtimestamp(self.get_date()),
+                        )
+
+    def get_message_parts(self):
+        """Output like notmuch show"""
+        fp = open(self.get_filename(), 'rb')
+        if sys.version_info[0] < 3:
+            email_msg = email.message_from_file(fp)
+        else:
+            email_msg = email.message_from_binary_file(fp)
+        fp.close()
+
+        out = []
+        for msg in email_msg.walk():
+            if not msg.is_multipart():
+                out.append(msg)
+        return out
+
+    def get_part(self, num):
+        """Returns the nth message body part"""
+        parts = self.get_message_parts()
+        if (num <= 0 or num > len(parts)):
+            return ""
+        else:
+            out_part = parts[(num - 1)]
+            return out_part.get_payload(decode=True)
+
+    def __hash__(self):
+        """Implement hash(), so we can use Message() sets"""
+        file = self.get_filename()
+        if not file:
+            return None
+        return hash(file)
+
+    def __cmp__(self, other):
+        """Implement cmp(), so we can compare Message()s
+
+        2 messages are considered equal if they point to the same
+        Message-Id and if they point to the same file names. If 2
+        Messages derive from different queries where some files have
+        been added or removed, the same messages would not be considered
+        equal (as they do not point to the same set of files
+        any more)."""
+        res = cmp(self.get_message_id(), other.get_message_id())
+        if res:
+            res = cmp(list(self.get_filenames()), list(other.get_filenames()))
+        return res
+
+    _destroy = nmlib.notmuch_message_destroy
+    _destroy.argtypes = [NotmuchMessageP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Message"""
+        if self._msg:
+            self._destroy(self._msg)
diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py
new file mode 100644 (file)
index 0000000..cae5da5
--- /dev/null
@@ -0,0 +1,199 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+               Jesse Rosenthal <jrosenthal@jhu.edu>
+"""
+
+from .globals import (
+    nmlib,
+    NotmuchTagsP,
+    NotmuchMessageP,
+    NotmuchMessagesP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .tag import Tags
+from .message import Message
+
+class Messages(object):
+    """Represents a list of notmuch messages
+
+    This object provides an iterator over a list of notmuch messages
+    (Technically, it provides a wrapper for the underlying
+    *notmuch_messages_t* structure). Do note that the underlying library
+    only provides a one-time iterator (it cannot reset the iterator to
+    the start). Thus iterating over the function will "exhaust" the list
+    of messages, and a subsequent iteration attempt will raise a
+    :exc:`NotInitializedError`. If you need to
+    re-iterate over a list of messages you will need to retrieve a new
+    :class:`Messages` object or cache your :class:`Message`\s in a list
+    via::
+
+       msglist = list(msgs)
+
+    You can store and reuse the single :class:`Message` objects as often
+    as you want as long as you keep the parent :class:`Messages` object
+    around. (Due to hierarchical memory allocation, all derived
+    :class:`Message` objects will be invalid when we delete the parent
+    :class:`Messages` object, even if it was already exhausted.) So
+    this works::
+
+      db   = Database()
+      msgs = Query(db,'').search_messages() #get a Messages() object
+      msglist = list(msgs)
+
+      # msgs is "exhausted" now and msgs.next() will raise an exception.
+      # However it will be kept alive until all retrieved Message()
+      # objects are also deleted. If you do e.g. an explicit del(msgs)
+      # here, the following lines would fail.
+
+      # You can reiterate over *msglist* however as often as you want.
+      # It is simply a list with :class:`Message`s.
+
+      print (msglist[0].get_filename())
+      print (msglist[1].get_filename())
+      print (msglist[0].get_message_id())
+
+
+    As :class:`Message` implements both __hash__() and __cmp__(), it is
+    possible to make sets out of :class:`Messages` and use set
+    arithmetic (this happens in python and will of course be *much*
+    slower than redoing a proper query with the appropriate filters::
+
+        s1, s2 = set(msgs1), set(msgs2)
+        s.union(s2)
+        s1 -= s2
+        ...
+
+    Be careful when using set arithmetic between message sets derived
+    from different Databases (ie the same database reopened after
+    messages have changed). If messages have added or removed associated
+    files in the meantime, it is possible that the same message would be
+    considered as a different object (as it points to a different file).
+    """
+
+    #notmuch_messages_get
+    _get = nmlib.notmuch_messages_get
+    _get.argtypes = [NotmuchMessagesP]
+    _get.restype = NotmuchMessageP
+
+    _collect_tags = nmlib.notmuch_messages_collect_tags
+    _collect_tags.argtypes = [NotmuchMessagesP]
+    _collect_tags.restype = NotmuchTagsP
+
+    def __init__(self, msgs_p, parent=None):
+        """
+        :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Messages` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type msgs_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object
+             (ie :class:`Query`) these tags are derived from. It saves
+             a reference to it, so we can automatically delete the db
+             object once all derived objects are dead.
+        :TODO: Make the iterator work more than once and cache the tags in
+               the Python object.(?)
+        """
+        if not msgs_p:
+            raise NullPointerError()
+
+        self._msgs = msgs_p
+        #store parent, so we keep them alive as long as self  is alive
+        self._parent = parent
+
+    def collect_tags(self):
+        """Return the unique :class:`Tags` in the contained messages
+
+        :returns: :class:`Tags`
+        :exceptions: :exc:`NotInitializedError` if not init'ed
+
+        .. note::
+
+            :meth:`collect_tags` will iterate over the messages and therefore
+            will not allow further iterations.
+        """
+        if not self._msgs:
+            raise NotInitializedError()
+
+        # collect all tags (returns NULL on error)
+        tags_p = Messages._collect_tags(self._msgs)
+        #reset _msgs as we iterated over it and can do so only once
+        self._msgs = None
+
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def __iter__(self):
+        """ Make Messages an iterator """
+        return self
+
+    _valid = nmlib.notmuch_messages_valid
+    _valid.argtypes = [NotmuchMessagesP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_messages_move_to_next
+    _move_to_next.argtypes = [NotmuchMessagesP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._msgs:
+            raise NotInitializedError()
+
+        if not self._valid(self._msgs):
+            self._msgs = None
+            raise StopIteration
+
+        msg = Message(Messages._get(self._msgs), self)
+        self._move_to_next(self._msgs)
+        return msg
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._msgs and self._valid(self._msgs)
+
+    _destroy = nmlib.notmuch_messages_destroy
+    _destroy.argtypes = [NotmuchMessagesP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Messages"""
+        if self._msgs:
+            self._destroy(self._msgs)
+
+class EmptyMessagesResult(Messages):
+    def __init__(self, parent):
+        self._msgs = None
+        self._parent = parent
+
+    def __next__(self):
+        raise StopIteration()
+    next = __next__
diff --git a/bindings/python/notmuch/query.py b/bindings/python/notmuch/query.py
new file mode 100644 (file)
index 0000000..06c7b11
--- /dev/null
@@ -0,0 +1,237 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_uint, POINTER, byref
+from .globals import (
+    nmlib,
+    Enum,
+    _str,
+    NotmuchQueryP,
+    NotmuchThreadsP,
+    NotmuchDatabaseP,
+    NotmuchMessagesP,
+)
+from .errors import (
+    NotmuchError,
+    NullPointerError,
+    NotInitializedError,
+)
+from .threads import Threads
+from .messages import Messages
+
+
+class Query(object):
+    """Represents a search query on an opened :class:`Database`.
+
+    A query selects and filters a subset of messages from the notmuch
+    database we derive from.
+
+    :class:`Query` provides an instance attribute :attr:`sort`, which
+    contains the sort order (if specified via :meth:`set_sort`) or
+    `None`.
+
+    Any function in this class may throw an :exc:`NotInitializedError`
+    in case the underlying query object was not set up correctly.
+
+    .. note:: Do remember that as soon as we tear down this object,
+           all underlying derived objects such as threads,
+           messages, tags etc will be freed by the underlying library
+           as well. Accessing these objects will lead to segfaults and
+           other unexpected behavior. See above for more details.
+    """
+    # constants
+    SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
+    """Constants: Sort order in which to return results"""
+
+    def __init__(self, db, querystr):
+        """
+        :param db: An open database which we derive the Query from.
+        :type db: :class:`Database`
+        :param querystr: The query string for the message.
+        :type querystr: utf-8 encoded str or unicode
+        """
+        self._db = None
+        self._query = None
+        self.sort = None
+        self.create(db, querystr)
+
+    def _assert_query_is_initialized(self):
+        """Raises :exc:`NotInitializedError` if self._query is `None`"""
+        if not self._query:
+            raise NotInitializedError()
+
+    """notmuch_query_create"""
+    _create = nmlib.notmuch_query_create
+    _create.argtypes = [NotmuchDatabaseP, c_char_p]
+    _create.restype = NotmuchQueryP
+
+    def create(self, db, querystr):
+        """Creates a new query derived from a Database
+
+        This function is utilized by __init__() and usually does not need to
+        be called directly.
+
+        :param db: Database to create the query from.
+        :type db: :class:`Database`
+        :param querystr: The query string
+        :type querystr: utf-8 encoded str or unicode
+        :raises:
+            :exc:`NullPointerError` if the query creation failed
+                (e.g. too little memory).
+            :exc:`NotInitializedError` if the underlying db was not
+                intitialized.
+        """
+        db._assert_db_is_initialized()
+        # create reference to parent db to keep it alive
+        self._db = db
+        # create query, return None if too little mem available
+        query_p = Query._create(db._db, _str(querystr))
+        if not query_p:
+            raise NullPointerError
+        self._query = query_p
+
+    _set_sort = nmlib.notmuch_query_set_sort
+    _set_sort.argtypes = [NotmuchQueryP, c_uint]
+    _set_sort.argtypes = None
+
+    def set_sort(self, sort):
+        """Set the sort order future results will be delivered in
+
+        :param sort: Sort order (see :attr:`Query.SORT`)
+        """
+        self._assert_query_is_initialized()
+        self.sort = sort
+        self._set_sort(self._query, sort)
+
+    _exclude_tag = nmlib.notmuch_query_add_tag_exclude
+    _exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
+    _exclude_tag.resttype = None
+
+    def exclude_tag(self, tagname):
+        """Add a tag that will be excluded from the query results by default.
+
+        This exclusion will be overridden if this tag appears explicitly in the
+        query.
+
+        :param tagname: Name of the tag to be excluded
+        """
+        self._assert_query_is_initialized()
+        self._exclude_tag(self._query, _str(tagname))
+
+    """notmuch_query_search_threads"""
+    _search_threads = nmlib.notmuch_query_search_threads
+    _search_threads.argtypes = [NotmuchQueryP, POINTER(NotmuchThreadsP)]
+    _search_threads.restype = c_uint
+
+    def search_threads(self):
+        """Execute a query for threads
+
+        Execute a query for threads, returning a :class:`Threads` iterator.
+        The returned threads are owned by the query and as such, will only be
+        valid until the Query is deleted.
+
+        The method sets :attr:`Message.FLAG`\.MATCH for those messages that
+        match the query. The method :meth:`Message.get_flag` allows us
+        to get the value of this flag.
+
+        :returns: :class:`Threads`
+        :raises: :exc:`NullPointerError` if search_threads failed
+        """
+        self._assert_query_is_initialized()
+        threads_p = NotmuchThreadsP() # == NULL
+        status = Query._search_threads(self._query, byref(threads_p))
+        if status != 0:
+            raise NotmuchError(status)
+
+        if not threads_p:
+            raise NullPointerError
+        return Threads(threads_p, self)
+
+    """notmuch_query_search_messages_st"""
+    _search_messages = nmlib.notmuch_query_search_messages
+    _search_messages.argtypes = [NotmuchQueryP, POINTER(NotmuchMessagesP)]
+    _search_messages.restype = c_uint
+
+    def search_messages(self):
+        """Filter messages according to the query and return
+        :class:`Messages` in the defined sort order
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        self._assert_query_is_initialized()
+        msgs_p = NotmuchMessagesP() # == NULL
+        status = Query._search_messages(self._query, byref(msgs_p))
+        if status != 0:
+            raise NotmuchError(status)
+
+        if not msgs_p:
+            raise NullPointerError
+        return Messages(msgs_p, self)
+
+    _count_messages = nmlib.notmuch_query_count_messages
+    _count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
+    _count_messages.restype = c_uint
+
+    def count_messages(self):
+        '''
+        This function performs a search and returns Xapian's best
+        guess as to the number of matching messages.
+
+        :returns: the estimated number of messages matching this query
+        :rtype:   int
+        '''
+        self._assert_query_is_initialized()
+        count = c_uint(0)
+        status = Query._count_messages(self._query, byref(count))
+        if status != 0:
+            raise NotmuchError(status)
+        return count.value
+
+    _count_threads = nmlib.notmuch_query_count_threads
+    _count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
+    _count_threads.restype = c_uint
+
+    def count_threads(self):
+        '''
+        This function performs a search and returns the number of
+        unique thread IDs in the matching messages. This is the same
+        as number of threads matching a search.
+
+        Note that this is a significantly heavier operation than
+        meth:`Query.count_messages`.
+
+        :returns: the number of threads returned by this query
+        :rtype:   int
+        '''
+        self._assert_query_is_initialized()
+        count = c_uint(0)
+        status = Query._count_threads(self._query, byref(count))
+        if status != 0:
+            raise NotmuchError(status)
+        return count.value
+
+    _destroy = nmlib.notmuch_query_destroy
+    _destroy.argtypes = [NotmuchQueryP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the Query"""
+        if self._query:
+            self._destroy(self._query)
diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py
new file mode 100644 (file)
index 0000000..fbb18ce
--- /dev/null
@@ -0,0 +1,141 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+from ctypes import c_char_p
+from .globals import (
+    nmlib,
+    Python3StringMixIn,
+    NotmuchTagsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+
+
+class Tags(Python3StringMixIn):
+    """Represents a list of notmuch tags
+
+    This object provides an iterator over a list of notmuch tags (which
+    are unicode instances).
+
+    Do note that the underlying library only provides a one-time
+    iterator (it cannot reset the iterator to the start). Thus iterating
+    over the function will "exhaust" the list of tags, and a subsequent
+    iteration attempt will raise a :exc:`NotInitializedError`.
+    Also note, that any function that uses iteration (nearly all) will
+    also exhaust the tags. So both::
+
+      for tag in tags: print tag
+
+    as well as::
+
+       number_of_tags = len(tags)
+
+    and even a simple::
+
+       #str() iterates over all tags to construct a space separated list
+       print(str(tags))
+
+    will "exhaust" the Tags. If you need to re-iterate over a list of
+    tags you will need to retrieve a new :class:`Tags` object.
+    """
+
+    #notmuch_tags_get
+    _get = nmlib.notmuch_tags_get
+    _get.argtypes = [NotmuchTagsP]
+    _get.restype = c_char_p
+
+    def __init__(self, tags_p, parent=None):
+        """
+        :param tags_p: A pointer to an underlying *notmuch_tags_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Tags` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type tags_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object (ie :class:`Database` or
+             :class:`Message` these tags are derived from, and saves a
+             reference to it, so we can automatically delete the db object
+             once all derived objects are dead.
+        :TODO: Make the iterator optionally work more than once by
+               cache the tags in the Python object(?)
+        """
+        if not tags_p:
+            raise NullPointerError()
+
+        self._tags = tags_p
+        #save reference to parent object so we keep it alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Tags an iterator """
+        return self
+
+    _valid = nmlib.notmuch_tags_valid
+    _valid.argtypes = [NotmuchTagsP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_tags_move_to_next
+    _move_to_next.argtypes = [NotmuchTagsP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._tags:
+            raise NotInitializedError()
+        if not self._valid(self._tags):
+            self._tags = None
+            raise StopIteration
+        tag = Tags._get(self._tags).decode('UTF-8')
+        self._move_to_next(self._tags)
+        return tag
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._tags and self._valid(self._tags)
+
+    def __unicode__(self):
+        """string representation of :class:`Tags`: a space separated list of tags
+
+        .. note::
+
+            As this iterates over the tags, we will not be able to iterate over
+            them again (as in retrieve them)! If the tags have been exhausted
+            already, this will raise a :exc:`NotInitializedError`on subsequent
+            attempts.
+        """
+        return " ".join(self)
+
+    _destroy = nmlib.notmuch_tags_destroy
+    _destroy.argtypes = [NotmuchTagsP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch tags"""
+        if self._tags:
+            self._destroy(self._tags)
diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py
new file mode 100644 (file)
index 0000000..9aa0e08
--- /dev/null
@@ -0,0 +1,281 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from ctypes import c_char_p, c_long, c_int
+from .globals import (
+    nmlib,
+    NotmuchThreadP,
+    NotmuchMessagesP,
+    NotmuchTagsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .messages import Messages
+from .tag import Tags
+from datetime import date
+
+class Thread(object):
+    """Represents a single message thread."""
+
+    """notmuch_thread_get_thread_id"""
+    _get_thread_id = nmlib.notmuch_thread_get_thread_id
+    _get_thread_id.argtypes = [NotmuchThreadP]
+    _get_thread_id.restype = c_char_p
+
+    """notmuch_thread_get_authors"""
+    _get_authors = nmlib.notmuch_thread_get_authors
+    _get_authors.argtypes = [NotmuchThreadP]
+    _get_authors.restype = c_char_p
+
+    """notmuch_thread_get_subject"""
+    _get_subject = nmlib.notmuch_thread_get_subject
+    _get_subject.argtypes = [NotmuchThreadP]
+    _get_subject.restype = c_char_p
+
+    """notmuch_thread_get_toplevel_messages"""
+    _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages
+    _get_toplevel_messages.argtypes = [NotmuchThreadP]
+    _get_toplevel_messages.restype = NotmuchMessagesP
+
+    _get_newest_date = nmlib.notmuch_thread_get_newest_date
+    _get_newest_date.argtypes = [NotmuchThreadP]
+    _get_newest_date.restype = c_long
+
+    _get_oldest_date = nmlib.notmuch_thread_get_oldest_date
+    _get_oldest_date.argtypes = [NotmuchThreadP]
+    _get_oldest_date.restype = c_long
+
+    """notmuch_thread_get_tags"""
+    _get_tags = nmlib.notmuch_thread_get_tags
+    _get_tags.argtypes = [NotmuchThreadP]
+    _get_tags.restype = NotmuchTagsP
+
+    def __init__(self, thread_p, parent=None):
+        """
+        :param thread_p: A pointer to an internal notmuch_thread_t
+            Structure.  These are not publicly exposed, so a user
+            will almost never instantiate a :class:`Thread` object
+            herself. They are usually handed back as a result,
+            e.g. when iterating through :class:`Threads`. *thread_p*
+            must be valid, we will raise an :exc:`NullPointerError`
+            if it is `None`.
+
+        :param parent: A 'parent' object is passed which this message is
+              derived from. We save a reference to it, so we can
+              automatically delete the parent object once all derived
+              objects are dead.
+        """
+        if not thread_p:
+            raise NullPointerError()
+        self._thread = thread_p
+        #keep reference to parent, so we keep it alive
+        self._parent = parent
+
+    def get_thread_id(self):
+        """Get the thread ID of 'thread'
+
+        The returned string belongs to 'thread' and will only be valid
+        for as long as the thread is valid.
+
+        :returns: String with a message ID
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore')
+
+    _get_total_messages = nmlib.notmuch_thread_get_total_messages
+    _get_total_messages.argtypes = [NotmuchThreadP]
+    _get_total_messages.restype = c_int
+
+    def get_total_messages(self):
+        """Get the total number of messages in 'thread'
+
+        :returns: The number of all messages in the database
+                  belonging to this thread. Contrast with
+                  :meth:`get_matched_messages`.
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return self._get_total_messages(self._thread)
+
+    def get_toplevel_messages(self):
+        """Returns a :class:`Messages` iterator for the top-level messages in
+           'thread'
+
+           This iterator will not necessarily iterate over all of the messages
+           in the thread. It will only iterate over the messages in the thread
+           which are not replies to other messages in the thread.
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        msgs_p = Thread._get_toplevel_messages(self._thread)
+
+        if not msgs_p:
+            raise NullPointerError()
+
+        return Messages(msgs_p, self)
+
+    """notmuch_thread_get_messages"""
+    _get_messages = nmlib.notmuch_thread_get_messages
+    _get_messages.argtypes = [NotmuchThreadP]
+    _get_messages.restype = NotmuchMessagesP
+
+    def get_messages(self):
+        """Returns a :class:`Messages` iterator for all messages in 'thread'
+
+        :returns: :class:`Messages`
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if get_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        msgs_p = Thread._get_messages(self._thread)
+
+        if not msgs_p:
+            raise NullPointerError()
+
+        return Messages(msgs_p, self)
+
+    _get_matched_messages = nmlib.notmuch_thread_get_matched_messages
+    _get_matched_messages.argtypes = [NotmuchThreadP]
+    _get_matched_messages.restype = c_int
+
+    def get_matched_messages(self):
+        """Returns the number of messages in 'thread' that matched the query
+
+        :returns: The number of all messages belonging to this thread that
+                  matched the :class:`Query`from which this thread was created.
+                  Contrast with :meth:`get_total_messages`.
+        :raises: :exc:`NotInitializedError` if the thread
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return self._get_matched_messages(self._thread)
+
+    def get_authors(self):
+        """Returns the authors of 'thread'
+
+        The returned string is a comma-separated list of the names of the
+        authors of mail messages in the query results that belong to this
+        thread.
+
+        The returned string belongs to 'thread' and will only be valid for
+        as long as this Thread() is not deleted.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        authors = Thread._get_authors(self._thread)
+        if not authors:
+            return None
+        return authors.decode('UTF-8', 'ignore')
+
+    def get_subject(self):
+        """Returns the Subject of 'thread'
+
+        The returned string belongs to 'thread' and will only be valid for
+        as long as this Thread() is not deleted.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        subject = Thread._get_subject(self._thread)
+        if not subject:
+            return None
+        return subject.decode('UTF-8', 'ignore')
+
+    def get_newest_date(self):
+        """Returns time_t of the newest message date
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_newest_date(self._thread)
+
+    def get_oldest_date(self):
+        """Returns time_t of the oldest message date
+
+        :returns: A time_t timestamp.
+        :rtype: c_unit64
+        :raises: :exc:`NotInitializedError` if the message
+                    is not initialized.
+        """
+        if not self._thread:
+            raise NotInitializedError()
+        return Thread._get_oldest_date(self._thread)
+
+    def get_tags(self):
+        """ Returns the message tags
+
+        In the Notmuch database, tags are stored on individual
+        messages, not on threads. So the tags returned here will be all
+        tags of the messages which matched the search and which belong to
+        this thread.
+
+        The :class:`Tags` object is owned by the thread and as such, will only
+        be valid for as long as this :class:`Thread` is valid (e.g. until the
+        query from which it derived is explicitly deleted).
+
+        :returns: A :class:`Tags` iterator.
+        :raises: :exc:`NotInitializedError` if query is not initialized
+        :raises: :exc:`NullPointerError` if search_messages failed
+        """
+        if not self._thread:
+            raise NotInitializedError()
+
+        tags_p = Thread._get_tags(self._thread)
+        if not tags_p:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def __unicode__(self):
+        frm = "thread:%s %12s [%d/%d] %s; %s (%s)"
+
+        return frm % (self.get_thread_id(),
+                      date.fromtimestamp(self.get_newest_date()),
+                      self.get_matched_messages(),
+                      self.get_total_messages(),
+                      self.get_authors(),
+                      self.get_subject(),
+                      self.get_tags(),
+                     )
+
+    _destroy = nmlib.notmuch_thread_destroy
+    _destroy.argtypes = [NotmuchThreadP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Thread"""
+        if self._thread:
+            self._destroy(self._thread)
diff --git a/bindings/python/notmuch/threads.py b/bindings/python/notmuch/threads.py
new file mode 100644 (file)
index 0000000..0c382d5
--- /dev/null
@@ -0,0 +1,152 @@
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+from .globals import (
+    nmlib,
+    Python3StringMixIn,
+    NotmuchThreadP,
+    NotmuchThreadsP,
+)
+from .errors import (
+    NullPointerError,
+    NotInitializedError,
+)
+from .thread import Thread
+
+class Threads(Python3StringMixIn):
+    """Represents a list of notmuch threads
+
+    This object provides an iterator over a list of notmuch threads
+    (Technically, it provides a wrapper for the underlying
+    *notmuch_threads_t* structure). Do note that the underlying
+    library only provides a one-time iterator (it cannot reset the
+    iterator to the start). Thus iterating over the function will
+    "exhaust" the list of threads, and a subsequent iteration attempt
+    will raise a :exc:`NotInitializedError`. Also
+    note, that any function that uses iteration will also
+    exhaust the messages. So both::
+
+      for thread in threads: print thread
+
+    as well as::
+
+       list_of_threads = list(threads)
+
+    will "exhaust" the threads. If you need to re-iterate over a list of
+    messages you will need to retrieve a new :class:`Threads` object.
+
+    Things are not as bad as it seems though, you can store and reuse
+    the single Thread objects as often as you want as long as you
+    keep the parent Threads object around. (Recall that due to
+    hierarchical memory allocation, all derived Threads objects will
+    be invalid when we delete the parent Threads() object, even if it
+    was already "exhausted".) So this works::
+
+      db   = Database()
+      threads = Query(db,'').search_threads() #get a Threads() object
+      threadlist = []
+      for thread in threads:
+         threadlist.append(thread)
+
+      # threads is "exhausted" now.
+      # However it will be kept around until all retrieved Thread() objects are
+      # also deleted. If you did e.g. an explicit del(threads) here, the
+      # following lines would fail.
+
+      # You can reiterate over *threadlist* however as often as you want.
+      # It is simply a list with Thread objects.
+
+      print (threadlist[0].get_thread_id())
+      print (threadlist[1].get_thread_id())
+      print (threadlist[0].get_total_messages())
+    """
+
+    #notmuch_threads_get
+    _get = nmlib.notmuch_threads_get
+    _get.argtypes = [NotmuchThreadsP]
+    _get.restype = NotmuchThreadP
+
+    def __init__(self, threads_p, parent=None):
+        """
+        :param threads_p:  A pointer to an underlying *notmuch_threads_t*
+             structure. These are not publicly exposed, so a user
+             will almost never instantiate a :class:`Threads` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Query.search_threads`.  *threads_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type threads_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object
+             (ie :class:`Query`) these tags are derived from. It saves
+             a reference to it, so we can automatically delete the db
+             object once all derived objects are dead.
+        :TODO: Make the iterator work more than once and cache the tags in
+               the Python object.(?)
+        """
+        if not threads_p:
+            raise NullPointerError()
+
+        self._threads = threads_p
+        #store parent, so we keep them alive as long as self  is alive
+        self._parent = parent
+
+    def __iter__(self):
+        """ Make Threads an iterator """
+        return self
+
+    _valid = nmlib.notmuch_threads_valid
+    _valid.argtypes = [NotmuchThreadsP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_threads_move_to_next
+    _move_to_next.argtypes = [NotmuchThreadsP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._threads:
+            raise NotInitializedError()
+
+        if not self._valid(self._threads):
+            self._threads = None
+            raise StopIteration
+
+        thread = Thread(Threads._get(self._threads), self)
+        self._move_to_next(self._threads)
+        return thread
+    next = __next__ # python2.x iterator protocol compatibility
+
+    def __nonzero__(self):
+        '''
+        Implement truth value testing. If __nonzero__ is not
+        implemented, the python runtime would fall back to `len(..) >
+        0` thus exhausting the iterator.
+
+        :returns: True if the wrapped iterator has at least one more object
+                  left.
+        '''
+        return self._threads and self._valid(self._threads)
+
+    _destroy = nmlib.notmuch_threads_destroy
+    _destroy.argtypes = [NotmuchThreadsP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Threads"""
+        if self._threads:
+            self._destroy(self._threads)
diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py
new file mode 100644 (file)
index 0000000..f376f6e
--- /dev/null
@@ -0,0 +1,3 @@
+# this file should be kept in sync with ../../../version
+__VERSION__ = '0.28.2'
+SOVERSION = '5'
diff --git a/bindings/python/setup.py b/bindings/python/setup.py
new file mode 100644 (file)
index 0000000..d986f0c
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+"""
+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/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
+"""
+
+import os
+from distutils.core import setup
+
+# get the notmuch version number without importing the notmuch module
+version_file = os.path.join(os.path.dirname(__file__),
+                            'notmuch', 'version.py')
+exec(compile(open(version_file).read(), version_file, 'exec'))
+assert '__VERSION__' in globals(), \
+    'Failed to read the notmuch binding version number'
+
+setup(name='notmuch',
+      version=__VERSION__,
+      description='Python binding of the notmuch mail search and indexing library.',
+      author='Sebastian Spaeth',
+      author_email='Sebastian@SSpaeth.de',
+      url='https://notmuchmail.org/',
+      download_url='https://notmuchmail.org/releases/notmuch-%s.tar.gz' % __VERSION__,
+      packages=['notmuch'],
+      keywords=['library', 'email'],
+      long_description='''Overview
+========
+
+The notmuch module provides an interface to the `notmuch
+<https://notmuchmail.org>`_ functionality, directly interfacing with a
+shared notmuch library. Notmuch provides a maildatabase that allows
+for extremely quick searching and filtering of your email according to
+various criteria.
+
+The documentation for the latest notmuch release can be `viewed
+online <https://notmuch.readthedocs.io/>`_.
+
+Requirements
+------------
+
+You need to have notmuch installed (or rather libnotmuch.so.1). Also,
+notmuch makes use of the ctypes library, and has only been tested with
+python >= 2.5. It will not work on earlier python versions.
+''',
+      classifiers=['Development Status :: 3 - Alpha',
+                   'Intended Audience :: Developers',
+                   'License :: OSI Approved :: GNU General Public License (GPL)',
+                   'Programming Language :: Python :: 2',
+                   'Programming Language :: Python :: 3',
+                   'Topic :: Communications :: Email',
+                   'Topic :: Software Development :: Libraries'
+                   ],
+      platforms='',
+      license='https://www.gnu.org/licenses/gpl-3.0.txt',
+     )
diff --git a/bindings/ruby/.gitignore b/bindings/ruby/.gitignore
new file mode 100644 (file)
index 0000000..c57ae63
--- /dev/null
@@ -0,0 +1,7 @@
+# .gitignore for bindings/ruby
+
+# Generated files
+/Makefile
+/mkmf.log
+/notmuch.so
+*.o
diff --git a/bindings/ruby/README b/bindings/ruby/README
new file mode 100644 (file)
index 0000000..a2946b6
--- /dev/null
@@ -0,0 +1,7 @@
+To build the the notmuch ruby extension, run the following commands
+from the *top level* notmuch source directory:
+
+% ./configure
+% make ruby-bindings
+
+The generic documentation about building notmuch also applies.
diff --git a/bindings/ruby/database.c b/bindings/ruby/database.c
new file mode 100644 (file)
index 0000000..416eb70
--- /dev/null
@@ -0,0 +1,423 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+VALUE
+notmuch_rb_database_alloc (VALUE klass)
+{
+    return Data_Wrap_Struct (klass, NULL, NULL, NULL);
+}
+
+/*
+ * call-seq: Notmuch::Database.new(path [, {:create => false, :mode => Notmuch::MODE_READ_ONLY}]) => DB
+ *
+ * Create or open a notmuch database using the given path.
+ *
+ * If :create is +true+, create the database instead of opening.
+ *
+ * The argument :mode specifies the open mode of the database.
+ */
+VALUE
+notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE self)
+{
+    const char *path;
+    int create, mode;
+    VALUE pathv, hashv;
+    VALUE modev;
+    notmuch_database_t *database;
+    notmuch_status_t ret;
+
+    /* Check arguments */
+    rb_scan_args (argc, argv, "11", &pathv, &hashv);
+
+    SafeStringValue (pathv);
+    path = RSTRING_PTR (pathv);
+
+    if (!NIL_P (hashv)) {
+       Check_Type (hashv, T_HASH);
+       create = RTEST (rb_hash_aref (hashv, ID2SYM (ID_db_create)));
+       modev = rb_hash_aref (hashv, ID2SYM (ID_db_mode));
+       if (NIL_P (modev))
+           mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+       else if (!FIXNUM_P (modev))
+           rb_raise (rb_eTypeError, ":mode isn't a Fixnum");
+       else {
+           mode = FIX2INT (modev);
+           switch (mode) {
+           case NOTMUCH_DATABASE_MODE_READ_ONLY:
+           case NOTMUCH_DATABASE_MODE_READ_WRITE:
+               break;
+           default:
+               rb_raise ( rb_eTypeError, "Invalid mode");
+           }
+       }
+    } else {
+       create = 0;
+       mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+    }
+
+    Check_Type (self, T_DATA);
+    if (create)
+       ret = notmuch_database_create (path, &database);
+    else
+       ret = notmuch_database_open (path, mode, &database);
+    notmuch_rb_status_raise (ret);
+
+    DATA_PTR (self) = database;
+
+    return self;
+}
+
+/*
+ * call-seq: Notmuch::Database.open(path [, ahash]) {|db| ...}
+ *
+ * Identical to new, except that when it is called with a block, it yields with
+ * the new instance and closes it, and returns the result which is returned from
+ * the block.
+ */
+VALUE
+notmuch_rb_database_open (int argc, VALUE *argv, VALUE klass)
+{
+    VALUE obj;
+
+    obj = rb_class_new_instance (argc, argv, klass);
+    if (!rb_block_given_p ())
+       return obj;
+
+    return rb_ensure (rb_yield, obj, notmuch_rb_database_close, obj);
+}
+
+/*
+ * call-seq: DB.close => nil
+ *
+ * Close the notmuch database.
+ */
+VALUE
+notmuch_rb_database_close (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+    ret = notmuch_database_destroy (db);
+    DATA_PTR (self) = NULL;
+    notmuch_rb_status_raise (ret);
+
+    return Qnil;
+}
+
+/*
+ * call-seq: DB.path => String
+ *
+ * Return the path of the database
+ */
+VALUE
+notmuch_rb_database_path (VALUE self)
+{
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    return rb_str_new2 (notmuch_database_get_path (db));
+}
+
+/*
+ * call-seq: DB.version => Fixnum
+ *
+ * Return the version of the database
+ */
+VALUE
+notmuch_rb_database_version (VALUE self)
+{
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    return INT2FIX (notmuch_database_get_version (db));
+}
+
+/*
+ * call-seq: DB.needs_upgrade? => true or false
+ *
+ * Return the +true+ if the database needs upgrading, +false+ otherwise
+ */
+VALUE
+notmuch_rb_database_needs_upgrade (VALUE self)
+{
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    return notmuch_database_needs_upgrade (db) ? Qtrue : Qfalse;
+}
+
+static void
+notmuch_rb_upgrade_notify (void *closure, double progress)
+{
+    VALUE *block = (VALUE *) closure;
+    rb_funcall (*block, ID_call, 1, rb_float_new (progress));
+}
+
+/*
+ * call-seq: DB.upgrade! [{|progress| block }] => nil
+ *
+ * Upgrade the database.
+ *
+ * If a block is given the block is called with a progress indicator as a
+ * floating point value in the range of [0.0..1.0].
+ */
+VALUE
+notmuch_rb_database_upgrade (VALUE self)
+{
+    notmuch_status_t ret;
+    void (*pnotify) (void *closure, double progress);
+    notmuch_database_t *db;
+    VALUE block;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    if (rb_block_given_p ()) {
+       pnotify = notmuch_rb_upgrade_notify;
+       block = rb_block_proc ();
+    }
+    else
+       pnotify = NULL;
+
+    ret = notmuch_database_upgrade (db, pnotify, pnotify ? &block : NULL);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: DB.begin_atomic => nil
+ *
+ * Begin an atomic database operation.
+ */
+VALUE
+notmuch_rb_database_begin_atomic (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    ret = notmuch_database_begin_atomic (db);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: DB.end_atomic => nil
+ *
+ * Indicate the end of an atomic database operation.
+ */
+VALUE
+notmuch_rb_database_end_atomic (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    ret = notmuch_database_end_atomic (db);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: DB.get_directory(path) => DIR
+ *
+ * Retrieve a directory object from the database for 'path'
+ */
+VALUE
+notmuch_rb_database_get_directory (VALUE self, VALUE pathv)
+{
+    const char *path;
+    notmuch_status_t ret;
+    notmuch_directory_t *dir;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (pathv);
+    path = RSTRING_PTR (pathv);
+
+    ret = notmuch_database_get_directory (db, path, &dir);
+    notmuch_rb_status_raise (ret);
+    if (dir)
+       return Data_Wrap_Struct (notmuch_rb_cDirectory, NULL, NULL, dir);
+    return Qnil;
+}
+
+/*
+ * call-seq: DB.add_message(path) => MESSAGE, isdup
+ *
+ * Add a message to the database and return it.
+ *
+ * +isdup+ is a boolean that specifies whether the added message was a
+ * duplicate.
+ */
+VALUE
+notmuch_rb_database_add_message (VALUE self, VALUE pathv)
+{
+    const char *path;
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (pathv);
+    path = RSTRING_PTR (pathv);
+
+    ret = notmuch_database_index_file (db, path, NULL, &message);
+    notmuch_rb_status_raise (ret);
+    return rb_assoc_new (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message),
+        (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse);
+}
+
+/*
+ * call-seq: DB.remove_message (path) => isdup
+ *
+ * Remove a message from the database.
+ *
+ * +isdup+ is a boolean that specifies whether the removed message was a
+ * duplicate.
+ */
+VALUE
+notmuch_rb_database_remove_message (VALUE self, VALUE pathv)
+{
+    const char *path;
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (pathv);
+    path = RSTRING_PTR (pathv);
+
+    ret = notmuch_database_remove_message (db, path);
+    notmuch_rb_status_raise (ret);
+    return (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: DB.find_message(id) => MESSAGE or nil
+ *
+ * Find a message by message id.
+ */
+VALUE
+notmuch_rb_database_find_message (VALUE self, VALUE idv)
+{
+    const char *id;
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (idv);
+    id = RSTRING_PTR (idv);
+
+    ret = notmuch_database_find_message (db, id, &message);
+    notmuch_rb_status_raise (ret);
+
+    if (message)
+        return Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message);
+    return Qnil;
+}
+
+/*
+ * call-seq: DB.find_message_by_filename(path) => MESSAGE or nil
+ *
+ * Find a message by filename.
+ */
+VALUE
+notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv)
+{
+    const char *path;
+    notmuch_status_t ret;
+    notmuch_database_t *db;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (pathv);
+    path = RSTRING_PTR (pathv);
+
+    ret = notmuch_database_find_message_by_filename (db, path, &message);
+    notmuch_rb_status_raise (ret);
+
+    if (message)
+        return Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message);
+    return Qnil;
+}
+
+/*
+ * call-seq: DB.get_all_tags() => TAGS
+ *
+ * Returns a list of all tags found in the database.
+ */
+VALUE
+notmuch_rb_database_get_all_tags (VALUE self)
+{
+    notmuch_database_t *db;
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    tags = notmuch_database_get_all_tags (db);
+    if (!tags) {
+       const char *msg = notmuch_database_status_string (db);
+       if (!msg)
+           msg = "Unknown notmuch error";
+
+       rb_raise (notmuch_rb_eBaseError, "%s", msg);
+    }
+    return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
+}
+
+/*
+ * call-seq: DB.query(query) => QUERY
+ *
+ * Retrieve a query object for the query string 'query'
+ */
+VALUE
+notmuch_rb_database_query_create (VALUE self, VALUE qstrv)
+{
+    const char *qstr;
+    notmuch_query_t *query;
+    notmuch_database_t *db;
+
+    Data_Get_Notmuch_Database (self, db);
+
+    SafeStringValue (qstrv);
+    qstr = RSTRING_PTR (qstrv);
+
+    query = notmuch_query_create (db, qstr);
+    if (!query)
+        rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cQuery, NULL, NULL, query);
+}
diff --git a/bindings/ruby/defs.h b/bindings/ruby/defs.h
new file mode 100644 (file)
index 0000000..48544ca
--- /dev/null
@@ -0,0 +1,357 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011, 2012 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#ifndef DEFS_H
+#define DEFS_H
+
+#include <notmuch.h>
+#include <ruby.h>
+
+extern VALUE notmuch_rb_cDatabase;
+extern VALUE notmuch_rb_cDirectory;
+extern VALUE notmuch_rb_cFileNames;
+extern VALUE notmuch_rb_cQuery;
+extern VALUE notmuch_rb_cThreads;
+extern VALUE notmuch_rb_cThread;
+extern VALUE notmuch_rb_cMessages;
+extern VALUE notmuch_rb_cMessage;
+extern VALUE notmuch_rb_cTags;
+
+extern VALUE notmuch_rb_eBaseError;
+extern VALUE notmuch_rb_eDatabaseError;
+extern VALUE notmuch_rb_eMemoryError;
+extern VALUE notmuch_rb_eReadOnlyError;
+extern VALUE notmuch_rb_eXapianError;
+extern VALUE notmuch_rb_eFileError;
+extern VALUE notmuch_rb_eFileNotEmailError;
+extern VALUE notmuch_rb_eNullPointerError;
+extern VALUE notmuch_rb_eTagTooLongError;
+extern VALUE notmuch_rb_eUnbalancedFreezeThawError;
+extern VALUE notmuch_rb_eUnbalancedAtomicError;
+
+extern ID ID_call;
+extern ID ID_db_create;
+extern ID ID_db_mode;
+
+/* RSTRING_PTR() is new in ruby-1.9 */
+#if !defined(RSTRING_PTR)
+# define RSTRING_PTR(v) (RSTRING((v))->ptr)
+#endif /* !defined (RSTRING_PTR) */
+
+#define Data_Get_Notmuch_Database(obj, ptr)                    \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "database closed");         \
+       Data_Get_Struct ((obj), notmuch_database_t, (ptr));     \
+    } while (0)
+
+#define Data_Get_Notmuch_Directory(obj, ptr)                   \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "directory destroyed");     \
+       Data_Get_Struct ((obj), notmuch_directory_t, (ptr));    \
+    } while (0)
+
+#define Data_Get_Notmuch_FileNames(obj, ptr)                   \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "filenames destroyed");     \
+       Data_Get_Struct ((obj), notmuch_filenames_t, (ptr));    \
+    } while (0)
+
+#define Data_Get_Notmuch_Query(obj, ptr)                       \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "query destroyed");         \
+       Data_Get_Struct ((obj), notmuch_query_t, (ptr));        \
+    } while (0)
+
+#define Data_Get_Notmuch_Threads(obj, ptr)                     \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "threads destroyed");       \
+       Data_Get_Struct ((obj), notmuch_threads_t, (ptr));      \
+    } while (0)
+
+#define Data_Get_Notmuch_Messages(obj, ptr)                    \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "messages destroyed");      \
+       Data_Get_Struct ((obj), notmuch_messages_t, (ptr));     \
+    } while (0)
+
+#define Data_Get_Notmuch_Thread(obj, ptr)                      \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "thread destroyed");        \
+       Data_Get_Struct ((obj), notmuch_thread_t, (ptr));       \
+    } while (0)
+
+#define Data_Get_Notmuch_Message(obj, ptr)                     \
+    do {                                                       \
+       Check_Type ((obj), T_DATA);                             \
+       if (DATA_PTR ((obj)) == NULL)                           \
+       rb_raise (rb_eRuntimeError, "message destroyed");       \
+       Data_Get_Struct ((obj), notmuch_message_t, (ptr));      \
+    } while (0)
+
+#define Data_Get_Notmuch_Tags(obj, ptr)                        \
+    do {                                               \
+       Check_Type ((obj), T_DATA);                     \
+       if (DATA_PTR ((obj)) == NULL)                   \
+       rb_raise (rb_eRuntimeError, "tags destroyed");  \
+       Data_Get_Struct ((obj), notmuch_tags_t, (ptr)); \
+    } while (0)
+
+/* status.c */
+void
+notmuch_rb_status_raise (notmuch_status_t status);
+
+/* database.c */
+VALUE
+notmuch_rb_database_alloc (VALUE klass);
+
+VALUE
+notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE klass);
+
+VALUE
+notmuch_rb_database_open (int argc, VALUE *argv, VALUE klass);
+
+VALUE
+notmuch_rb_database_close (VALUE self);
+
+VALUE
+notmuch_rb_database_path (VALUE self);
+
+VALUE
+notmuch_rb_database_version (VALUE self);
+
+VALUE
+notmuch_rb_database_needs_upgrade (VALUE self);
+
+VALUE
+notmuch_rb_database_upgrade (VALUE self);
+
+VALUE
+notmuch_rb_database_begin_atomic (VALUE self);
+
+VALUE
+notmuch_rb_database_end_atomic (VALUE self);
+
+VALUE
+notmuch_rb_database_get_directory (VALUE self, VALUE pathv);
+
+VALUE
+notmuch_rb_database_add_message (VALUE self, VALUE pathv);
+
+VALUE
+notmuch_rb_database_remove_message (VALUE self, VALUE pathv);
+
+VALUE
+notmuch_rb_database_find_message (VALUE self, VALUE idv);
+
+VALUE
+notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv);
+
+VALUE
+notmuch_rb_database_get_all_tags (VALUE self);
+
+VALUE
+notmuch_rb_database_query_create (VALUE self, VALUE qstrv);
+
+/* directory.c */
+VALUE
+notmuch_rb_directory_destroy (VALUE self);
+
+VALUE
+notmuch_rb_directory_get_mtime (VALUE self);
+
+VALUE
+notmuch_rb_directory_set_mtime (VALUE self, VALUE mtimev);
+
+VALUE
+notmuch_rb_directory_get_child_files (VALUE self);
+
+VALUE
+notmuch_rb_directory_get_child_directories (VALUE self);
+
+/* filenames.c */
+VALUE
+notmuch_rb_filenames_destroy (VALUE self);
+
+VALUE
+notmuch_rb_filenames_each (VALUE self);
+
+/* query.c */
+VALUE
+notmuch_rb_query_destroy (VALUE self);
+
+VALUE
+notmuch_rb_query_get_sort (VALUE self);
+
+VALUE
+notmuch_rb_query_set_sort (VALUE self, VALUE sortv);
+
+VALUE
+notmuch_rb_query_get_string (VALUE self);
+
+VALUE
+notmuch_rb_query_add_tag_exclude (VALUE self, VALUE tagv);
+
+VALUE
+notmuch_rb_query_set_omit_excluded (VALUE self, VALUE omitv);
+
+VALUE
+notmuch_rb_query_search_threads (VALUE self);
+
+VALUE
+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);
+
+VALUE
+notmuch_rb_threads_each (VALUE self);
+
+/* messages.c */
+VALUE
+notmuch_rb_messages_destroy (VALUE self);
+
+VALUE
+notmuch_rb_messages_each (VALUE self);
+
+VALUE
+notmuch_rb_messages_collect_tags (VALUE self);
+
+/* thread.c */
+VALUE
+notmuch_rb_thread_destroy (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_thread_id (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_total_messages (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_toplevel_messages (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_messages (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_matched_messages (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_authors (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_subject (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_oldest_date (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_newest_date (VALUE self);
+
+VALUE
+notmuch_rb_thread_get_tags (VALUE self);
+
+/* message.c */
+VALUE
+notmuch_rb_message_destroy (VALUE self);
+
+VALUE
+notmuch_rb_message_get_message_id (VALUE self);
+
+VALUE
+notmuch_rb_message_get_thread_id (VALUE self);
+
+VALUE
+notmuch_rb_message_get_replies (VALUE self);
+
+VALUE
+notmuch_rb_message_get_filename (VALUE self);
+
+VALUE
+notmuch_rb_message_get_filenames (VALUE self);
+
+VALUE
+notmuch_rb_message_get_flag (VALUE self, VALUE flagv);
+
+VALUE
+notmuch_rb_message_set_flag (VALUE self, VALUE flagv, VALUE valuev);
+
+VALUE
+notmuch_rb_message_get_date (VALUE self);
+
+VALUE
+notmuch_rb_message_get_header (VALUE self, VALUE headerv);
+
+VALUE
+notmuch_rb_message_get_tags (VALUE self);
+
+VALUE
+notmuch_rb_message_add_tag (VALUE self, VALUE tagv);
+
+VALUE
+notmuch_rb_message_remove_tag (VALUE self, VALUE tagv);
+
+VALUE
+notmuch_rb_message_remove_all_tags (VALUE self);
+
+VALUE
+notmuch_rb_message_maildir_flags_to_tags (VALUE self);
+
+VALUE
+notmuch_rb_message_tags_to_maildir_flags (VALUE self);
+
+VALUE
+notmuch_rb_message_freeze (VALUE self);
+
+VALUE
+notmuch_rb_message_thaw (VALUE self);
+
+/* tags.c */
+VALUE
+notmuch_rb_tags_destroy (VALUE self);
+
+VALUE
+notmuch_rb_tags_each (VALUE self);
+
+/* init.c */
+void
+Init_notmuch (void);
+
+#endif
diff --git a/bindings/ruby/directory.c b/bindings/ruby/directory.c
new file mode 100644 (file)
index 0000000..0f37b39
--- /dev/null
@@ -0,0 +1,115 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: DIR.destroy! => nil
+ *
+ * Destroys the directory, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_directory_destroy (VALUE self)
+{
+    notmuch_directory_t *dir;
+
+    Data_Get_Struct (self, notmuch_directory_t, dir);
+
+    notmuch_directory_destroy (dir);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: DIR.mtime => fixnum
+ *
+ * Returns the mtime of the directory or +0+ if no mtime has been previously
+ * stored.
+ */
+VALUE
+notmuch_rb_directory_get_mtime (VALUE self)
+{
+    notmuch_directory_t *dir;
+
+    Data_Get_Notmuch_Directory (self, dir);
+
+    return UINT2NUM (notmuch_directory_get_mtime (dir));
+}
+
+/*
+ * call-seq: DIR.mtime=(fixnum) => nil
+ *
+ * Store an mtime within the database for the directory object.
+ */
+VALUE
+notmuch_rb_directory_set_mtime (VALUE self, VALUE mtimev)
+{
+    notmuch_status_t ret;
+    notmuch_directory_t *dir;
+
+    Data_Get_Notmuch_Directory (self, dir);
+
+    if (!FIXNUM_P (mtimev))
+       rb_raise (rb_eTypeError, "First argument not a fixnum");
+
+    ret = notmuch_directory_set_mtime (dir, FIX2UINT (mtimev));
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: DIR.child_files => FILENAMES
+ *
+ * Return a Notmuch::FileNames object, which is an +Enumerable+ listing all the
+ * filenames of messages in the database within the given directory.
+ */
+VALUE
+notmuch_rb_directory_get_child_files (VALUE self)
+{
+    notmuch_directory_t *dir;
+    notmuch_filenames_t *fnames;
+
+    Data_Get_Notmuch_Directory (self, dir);
+
+    fnames = notmuch_directory_get_child_files (dir);
+
+    return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames);
+}
+
+/*
+ * call-seq: DIR.child_directories => FILENAMES
+ *
+ * Return a Notmuch::FileNames object, which is an +Enumerable+ listing all the
+ * directories in the database within the given directory.
+ */
+VALUE
+notmuch_rb_directory_get_child_directories (VALUE self)
+{
+    notmuch_directory_t *dir;
+    notmuch_filenames_t *fnames;
+
+    Data_Get_Notmuch_Directory (self, dir);
+
+    fnames = notmuch_directory_get_child_directories (dir);
+
+    return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames);
+}
diff --git a/bindings/ruby/extconf.rb b/bindings/ruby/extconf.rb
new file mode 100644 (file)
index 0000000..161de5a
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+# Copyright 2010, 2011, 2012 Ali Polatel <alip@exherbo.org>
+# Distributed under the terms of the GNU General Public License v3
+
+require 'mkmf'
+
+dir = File.join(ENV['NOTMUCH_SRCDIR'], 'lib')
+
+# includes
+$INCFLAGS = "-I#{dir} #{$INCFLAGS}"
+
+if ENV['EXTRA_LDFLAGS']
+  $LDFLAGS += " " + ENV['EXTRA_LDFLAGS']
+end
+
+if not ENV['LIBNOTMUCH']
+  exit 1
+end
+
+$LOCAL_LIBS += ENV['LIBNOTMUCH']
+
+# Create Makefile
+dir_config('notmuch')
+create_makefile('notmuch')
diff --git a/bindings/ruby/filenames.c b/bindings/ruby/filenames.c
new file mode 100644 (file)
index 0000000..656c58e
--- /dev/null
@@ -0,0 +1,58 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: FILENAMES.destroy! => nil
+ *
+ * Destroys the filenames, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_filenames_destroy (VALUE self)
+{
+    notmuch_filenames_t *fnames;
+
+    Data_Get_Notmuch_FileNames (self, fnames);
+
+    notmuch_filenames_destroy (fnames);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: FILENAMES.each {|item| block } => FILENAMES
+ *
+ * Calls +block+ once for each element in +self+, passing that element as a
+ * parameter.
+ */
+VALUE
+notmuch_rb_filenames_each (VALUE self)
+{
+    notmuch_filenames_t *fnames;
+
+    Data_Get_Notmuch_FileNames (self, fnames);
+
+    for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames))
+       rb_yield (rb_str_new2 (notmuch_filenames_get (fnames)));
+
+    return self;
+}
diff --git a/bindings/ruby/init.c b/bindings/ruby/init.c
new file mode 100644 (file)
index 0000000..5556b43
--- /dev/null
@@ -0,0 +1,357 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011, 2012 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+VALUE notmuch_rb_cDatabase;
+VALUE notmuch_rb_cDirectory;
+VALUE notmuch_rb_cFileNames;
+VALUE notmuch_rb_cQuery;
+VALUE notmuch_rb_cThreads;
+VALUE notmuch_rb_cThread;
+VALUE notmuch_rb_cMessages;
+VALUE notmuch_rb_cMessage;
+VALUE notmuch_rb_cTags;
+
+VALUE notmuch_rb_eBaseError;
+VALUE notmuch_rb_eDatabaseError;
+VALUE notmuch_rb_eMemoryError;
+VALUE notmuch_rb_eReadOnlyError;
+VALUE notmuch_rb_eXapianError;
+VALUE notmuch_rb_eFileError;
+VALUE notmuch_rb_eFileNotEmailError;
+VALUE notmuch_rb_eNullPointerError;
+VALUE notmuch_rb_eTagTooLongError;
+VALUE notmuch_rb_eUnbalancedFreezeThawError;
+VALUE notmuch_rb_eUnbalancedAtomicError;
+
+ID ID_call;
+ID ID_db_create;
+ID ID_db_mode;
+
+/*
+ * Document-module: Notmuch
+ *
+ * == Summary
+ *
+ * Ruby extension to the <tt>notmuch</tt> mail library.
+ *
+ * == Classes
+ *
+ * Following are the classes that are most likely to be of interest to
+ * the user:
+ *
+ * - Notmuch::Database
+ * - Notmuch::FileNames
+ * - Notmuch::Query
+ * - Notmuch::Threads
+ * - Notmuch::Messages
+ * - Notmuch::Thread
+ * - Notmuch::Message
+ * - Notmuch::Tags
+ */
+
+void
+Init_notmuch (void)
+{
+    VALUE mod;
+
+    ID_call = rb_intern ("call");
+    ID_db_create = rb_intern ("create");
+    ID_db_mode = rb_intern ("mode");
+
+    mod = rb_define_module ("Notmuch");
+
+    /*
+     * Document-const: Notmuch::MODE_READ_ONLY
+     *
+     * Open the database in read only mode
+     */
+    rb_define_const (mod, "MODE_READ_ONLY", INT2FIX (NOTMUCH_DATABASE_MODE_READ_ONLY));
+    /*
+     * Document-const: Notmuch::MODE_READ_WRITE
+     *
+     * Open the database in read write mode
+     */
+    rb_define_const (mod, "MODE_READ_WRITE", INT2FIX (NOTMUCH_DATABASE_MODE_READ_WRITE));
+    /*
+     * Document-const: Notmuch::SORT_OLDEST_FIRST
+     *
+     * Sort query results by oldest first
+     */
+    rb_define_const (mod, "SORT_OLDEST_FIRST", INT2FIX (NOTMUCH_SORT_OLDEST_FIRST));
+    /*
+     * Document-const: Notmuch::SORT_NEWEST_FIRST
+     *
+     * Sort query results by newest first
+     */
+    rb_define_const (mod, "SORT_NEWEST_FIRST", INT2FIX (NOTMUCH_SORT_NEWEST_FIRST));
+    /*
+     * Document-const: Notmuch::SORT_MESSAGE_ID
+     *
+     * Sort query results by message id
+     */
+    rb_define_const (mod, "SORT_MESSAGE_ID", INT2FIX (NOTMUCH_SORT_MESSAGE_ID));
+    /*
+     * Document-const: Notmuch::SORT_UNSORTED
+     *
+     * Do not sort query results
+     */
+    rb_define_const (mod, "SORT_UNSORTED", INT2FIX (NOTMUCH_SORT_UNSORTED));
+    /*
+     * Document-const: Notmuch::MESSAGE_FLAG_MATCH
+     *
+     * Message flag "match"
+     */
+    rb_define_const (mod, "MESSAGE_FLAG_MATCH", INT2FIX (NOTMUCH_MESSAGE_FLAG_MATCH));
+    /*
+     * Document-const: Notmuch::MESSAGE_FLAG_EXCLUDED
+     *
+     * Message flag "excluded"
+     */
+    rb_define_const (mod, "MESSAGE_FLAG_EXCLUDED", INT2FIX (NOTMUCH_MESSAGE_FLAG_EXCLUDED));
+    /*
+     * Document-const: Notmuch::TAG_MAX
+     *
+     * Maximum allowed length of a tag
+     */
+    rb_define_const (mod, "TAG_MAX", INT2FIX (NOTMUCH_TAG_MAX));
+
+    /*
+     * Document-class: Notmuch::BaseError
+     *
+     * Base class for all notmuch exceptions
+     */
+    notmuch_rb_eBaseError = rb_define_class_under (mod, "BaseError", rb_eStandardError);
+    /*
+     * Document-class: Notmuch::DatabaseError
+     *
+     * Raised when the database can't be created or opened.
+     */
+    notmuch_rb_eDatabaseError = rb_define_class_under (mod, "DatabaseError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::MemoryError
+     *
+     * Raised when notmuch is out of memory
+     */
+    notmuch_rb_eMemoryError = rb_define_class_under (mod, "MemoryError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::ReadOnlyError
+     *
+     * Raised when an attempt was made to write to a database opened in read-only
+     * mode.
+     */
+    notmuch_rb_eReadOnlyError = rb_define_class_under (mod, "ReadOnlyError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::XapianError
+     *
+     * Raised when a Xapian exception occurs
+     */
+    notmuch_rb_eXapianError = rb_define_class_under (mod, "XapianError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::FileError
+     *
+     * Raised when an error occurs trying to read or write to a file.
+     */
+    notmuch_rb_eFileError = rb_define_class_under (mod, "FileError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::FileNotEmailError
+     *
+     * Raised when a file is presented that doesn't appear to be an email message.
+     */
+    notmuch_rb_eFileNotEmailError = rb_define_class_under (mod, "FileNotEmailError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::NullPointerError
+     *
+     * Raised when the user erroneously passes a +NULL+ pointer to a notmuch
+     * function.
+     */
+    notmuch_rb_eNullPointerError = rb_define_class_under (mod, "NullPointerError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::TagTooLongError
+     *
+     * Raised when a tag value is too long (exceeds Notmuch::TAG_MAX)
+     */
+    notmuch_rb_eTagTooLongError = rb_define_class_under (mod, "TagTooLongError", notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::UnbalancedFreezeThawError
+     *
+     * Raised when the notmuch_message_thaw function has been called more times
+     * than notmuch_message_freeze.
+     */
+    notmuch_rb_eUnbalancedFreezeThawError = rb_define_class_under (mod, "UnbalancedFreezeThawError",
+                                                                  notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::UnbalancedAtomicError
+     *
+     * Raised when notmuch_database_end_atomic has been called more times than
+     * notmuch_database_begin_atomic
+     */
+    notmuch_rb_eUnbalancedAtomicError = rb_define_class_under (mod, "UnbalancedAtomicError",
+                                                              notmuch_rb_eBaseError);
+    /*
+     * Document-class: Notmuch::Database
+     *
+     * Notmuch database interaction
+     */
+    notmuch_rb_cDatabase = rb_define_class_under (mod, "Database", rb_cData);
+    rb_define_alloc_func (notmuch_rb_cDatabase, notmuch_rb_database_alloc);
+    rb_define_singleton_method (notmuch_rb_cDatabase, "open", notmuch_rb_database_open, -1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "initialize", notmuch_rb_database_initialize, -1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "close", notmuch_rb_database_close, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "path", notmuch_rb_database_path, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "version", notmuch_rb_database_version, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "needs_upgrade?", notmuch_rb_database_needs_upgrade, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "upgrade!", notmuch_rb_database_upgrade, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "begin_atomic", notmuch_rb_database_begin_atomic, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "end_atomic", notmuch_rb_database_end_atomic, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "get_directory", notmuch_rb_database_get_directory, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "add_message", notmuch_rb_database_add_message, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "remove_message", notmuch_rb_database_remove_message, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "find_message",
+                     notmuch_rb_database_find_message, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "find_message_by_filename",
+                     notmuch_rb_database_find_message_by_filename, 1); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "all_tags", notmuch_rb_database_get_all_tags, 0); /* in database.c */
+    rb_define_method (notmuch_rb_cDatabase, "query", notmuch_rb_database_query_create, 1); /* in database.c */
+
+    /*
+     * Document-class: Notmuch::Directory
+     *
+     * Notmuch directory
+     */
+    notmuch_rb_cDirectory = rb_define_class_under (mod, "Directory", rb_cData);
+    rb_undef_method (notmuch_rb_cDirectory, "initialize");
+    rb_define_method (notmuch_rb_cDirectory, "destroy!", notmuch_rb_directory_destroy, 0); /* in directory.c */
+    rb_define_method (notmuch_rb_cDirectory, "mtime", notmuch_rb_directory_get_mtime, 0); /* in directory.c */
+    rb_define_method (notmuch_rb_cDirectory, "mtime=", notmuch_rb_directory_set_mtime, 1); /* in directory.c */
+    rb_define_method (notmuch_rb_cDirectory, "child_files", notmuch_rb_directory_get_child_files, 0); /* in directory.c */
+    rb_define_method (notmuch_rb_cDirectory, "child_directories", notmuch_rb_directory_get_child_directories, 0); /* in directory.c */
+
+    /*
+     * Document-class: Notmuch::FileNames
+     *
+     * Notmuch file names
+     */
+    notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cData);
+    rb_undef_method (notmuch_rb_cFileNames, "initialize");
+    rb_define_method (notmuch_rb_cFileNames, "destroy!", notmuch_rb_filenames_destroy, 0); /* in filenames.c */
+    rb_define_method (notmuch_rb_cFileNames, "each", notmuch_rb_filenames_each, 0); /* in filenames.c */
+    rb_include_module (notmuch_rb_cFileNames, rb_mEnumerable);
+
+    /*
+     * Document-class: Notmuch::Query
+     *
+     * Notmuch query
+     */
+    notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cData);
+    rb_undef_method (notmuch_rb_cQuery, "initialize");
+    rb_define_method (notmuch_rb_cQuery, "destroy!", notmuch_rb_query_destroy, 0); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "sort", notmuch_rb_query_get_sort, 0); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "sort=", notmuch_rb_query_set_sort, 1); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "to_s", notmuch_rb_query_get_string, 0); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "add_tag_exclude", notmuch_rb_query_add_tag_exclude, 1); /* in query.c */
+    rb_define_method (notmuch_rb_cQuery, "omit_excluded=", notmuch_rb_query_set_omit_excluded, 1); /* in query.c */
+    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
+     *
+     * Notmuch threads
+     */
+    notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cData);
+    rb_undef_method (notmuch_rb_cThreads, "initialize");
+    rb_define_method (notmuch_rb_cThreads, "destroy!", notmuch_rb_threads_destroy, 0); /* in threads.c */
+    rb_define_method (notmuch_rb_cThreads, "each", notmuch_rb_threads_each, 0); /* in threads.c */
+    rb_include_module (notmuch_rb_cThreads, rb_mEnumerable);
+
+    /*
+     * Document-class: Notmuch::Messages
+     *
+     * Notmuch messages
+     */
+    notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cData);
+    rb_undef_method (notmuch_rb_cMessages, "initialize");
+    rb_define_method (notmuch_rb_cMessages, "destroy!", notmuch_rb_messages_destroy, 0); /* in messages.c */
+    rb_define_method (notmuch_rb_cMessages, "each", notmuch_rb_messages_each, 0); /* in messages.c */
+    rb_define_method (notmuch_rb_cMessages, "tags", notmuch_rb_messages_collect_tags, 0); /* in messages.c */
+    rb_include_module (notmuch_rb_cMessages, rb_mEnumerable);
+
+    /*
+     * Document-class: Notmuch::Thread
+     *
+     * Notmuch thread
+     */
+    notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cData);
+    rb_undef_method (notmuch_rb_cThread, "initialize");
+    rb_define_method (notmuch_rb_cThread, "destroy!", notmuch_rb_thread_destroy, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "total_messages", notmuch_rb_thread_get_total_messages, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "toplevel_messages", notmuch_rb_thread_get_toplevel_messages, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "messages", notmuch_rb_thread_get_messages, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "matched_messages", notmuch_rb_thread_get_matched_messages, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "authors", notmuch_rb_thread_get_authors, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "subject", notmuch_rb_thread_get_subject, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "oldest_date", notmuch_rb_thread_get_oldest_date, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "newest_date", notmuch_rb_thread_get_newest_date, 0); /* in thread.c */
+    rb_define_method (notmuch_rb_cThread, "tags", notmuch_rb_thread_get_tags, 0); /* in thread.c */
+
+    /*
+     * Document-class: Notmuch::Message
+     *
+     * Notmuch message
+     */
+    notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cData);
+    rb_undef_method (notmuch_rb_cMessage, "initialize");
+    rb_define_method (notmuch_rb_cMessage, "destroy!", notmuch_rb_message_destroy, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "message_id", notmuch_rb_message_get_message_id, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "thread_id", notmuch_rb_message_get_thread_id, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "replies", notmuch_rb_message_get_replies, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "filename", notmuch_rb_message_get_filename, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "filenames", notmuch_rb_message_get_filenames, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "get_flag", notmuch_rb_message_get_flag, 1); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "set_flag", notmuch_rb_message_set_flag, 2); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "date", notmuch_rb_message_get_date, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "header", notmuch_rb_message_get_header, 1); /* in message.c */
+    rb_define_alias (notmuch_rb_cMessage, "[]", "header");
+    rb_define_method (notmuch_rb_cMessage, "tags", notmuch_rb_message_get_tags, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "add_tag", notmuch_rb_message_add_tag, 1); /* in message.c */
+    rb_define_alias (notmuch_rb_cMessage, "<<", "add_tag");
+    rb_define_method (notmuch_rb_cMessage, "remove_tag", notmuch_rb_message_remove_tag, 1); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "remove_all_tags", notmuch_rb_message_remove_all_tags, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "maildir_flags_to_tags", notmuch_rb_message_maildir_flags_to_tags, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "tags_to_maildir_flags", notmuch_rb_message_tags_to_maildir_flags, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */
+    rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 0); /* in message.c */
+
+    /*
+     * Document-class: Notmuch::Tags
+     *
+     * Notmuch tags
+     */
+    notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cData);
+    rb_undef_method (notmuch_rb_cTags, "initialize");
+    rb_define_method (notmuch_rb_cTags, "destroy!", notmuch_rb_tags_destroy, 0); /* in tags.c */
+    rb_define_method (notmuch_rb_cTags, "each", notmuch_rb_tags_each, 0); /* in tags.c */
+    rb_include_module (notmuch_rb_cTags, rb_mEnumerable);
+}
diff --git a/bindings/ruby/message.c b/bindings/ruby/message.c
new file mode 100644 (file)
index 0000000..c55cf6e
--- /dev/null
@@ -0,0 +1,366 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: MESSAGE.destroy! => nil
+ *
+ * Destroys the message, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_message_destroy (VALUE self)
+{
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    notmuch_message_destroy (message);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: MESSAGE.message_id => String
+ *
+ * Get the message ID of 'message'.
+ */
+VALUE
+notmuch_rb_message_get_message_id (VALUE self)
+{
+    const char *msgid;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    msgid = notmuch_message_get_message_id (message);
+
+    return rb_str_new2 (msgid);
+}
+
+/*
+ * call-seq: MESSAGE.thread_id => String
+ *
+ * Get the thread ID of 'message'.
+ */
+VALUE
+notmuch_rb_message_get_thread_id (VALUE self)
+{
+    const char *tid;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    tid = notmuch_message_get_thread_id (message);
+
+    return rb_str_new2 (tid);
+}
+
+/*
+ * call-seq: MESSAGE.replies => MESSAGES
+ *
+ * Get a Notmuch::Messages enumerable for all of the replies to 'message'.
+ */
+VALUE
+notmuch_rb_message_get_replies (VALUE self)
+{
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    messages = notmuch_message_get_replies (message);
+
+    return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
+}
+
+/*
+ * call-seq: MESSAGE.filename => String
+ *
+ * Get a filename for the email corresponding to 'message'
+ */
+VALUE
+notmuch_rb_message_get_filename (VALUE self)
+{
+    const char *fname;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    fname = notmuch_message_get_filename (message);
+
+    return rb_str_new2 (fname);
+}
+
+/*
+ * call-seq: MESSAGE.filenames => FILENAMES
+ *
+ * Get all filenames for the email corresponding to MESSAGE.
+ */
+VALUE
+notmuch_rb_message_get_filenames (VALUE self)
+{
+    notmuch_filenames_t *fnames;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    fnames = notmuch_message_get_filenames (message);
+
+    return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames);
+}
+
+/*
+ * call-seq: MESSAGE.get_flag (flag) => true or false
+ *
+ * Get a value of a flag for the email corresponding to 'message'
+ */
+VALUE
+notmuch_rb_message_get_flag (VALUE self, VALUE flagv)
+{
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    if (!FIXNUM_P (flagv))
+       rb_raise (rb_eTypeError, "Flag not a Fixnum");
+
+    return notmuch_message_get_flag (message, FIX2INT (flagv)) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: MESSAGE.set_flag (flag, value) => nil
+ *
+ * Set a value of a flag for the email corresponding to 'message'
+ */
+VALUE
+notmuch_rb_message_set_flag (VALUE self, VALUE flagv, VALUE valuev)
+{
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    if (!FIXNUM_P (flagv))
+       rb_raise (rb_eTypeError, "Flag not a Fixnum");
+
+    notmuch_message_set_flag (message, FIX2INT (flagv), RTEST (valuev));
+
+    return Qnil;
+}
+
+/*
+ * call-seq: MESSAGE.date => Fixnum
+ *
+ * Get the date of 'message'
+ */
+VALUE
+notmuch_rb_message_get_date (VALUE self)
+{
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    return UINT2NUM (notmuch_message_get_date (message));
+}
+
+/*
+ * call-seq: MESSAGE.header (name) => String
+ *
+ * Get the value of the specified header from 'message'
+ */
+VALUE
+notmuch_rb_message_get_header (VALUE self, VALUE headerv)
+{
+    const char *header, *value;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    SafeStringValue (headerv);
+    header = RSTRING_PTR (headerv);
+
+    value = notmuch_message_get_header (message, header);
+    if (!value)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return rb_str_new2 (value);
+}
+
+/*
+ * call-seq: MESSAGE.tags => TAGS
+ *
+ * Get a Notmuch::Tags enumerable for all of the tags of 'message'.
+ */
+VALUE
+notmuch_rb_message_get_tags (VALUE self)
+{
+    notmuch_message_t *message;
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    tags = notmuch_message_get_tags (message);
+    if (!tags)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
+}
+
+/*
+ * call-seq: MESSAGE.add_tag (tag) => true
+ *
+ * Add a tag to the 'message'
+ */
+VALUE
+notmuch_rb_message_add_tag (VALUE self, VALUE tagv)
+{
+    const char *tag;
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    SafeStringValue (tagv);
+    tag = RSTRING_PTR (tagv);
+
+    ret = notmuch_message_add_tag (message, tag);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.remove_tag (tag) => true
+ *
+ * Remove a tag from the 'message'
+ */
+VALUE
+notmuch_rb_message_remove_tag (VALUE self, VALUE tagv)
+{
+    const char *tag;
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    SafeStringValue (tagv);
+    tag = RSTRING_PTR (tagv);
+
+    ret = notmuch_message_remove_tag (message, tag);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.remove_all_tags => true
+ *
+ * Remove all tags of the 'message'
+ */
+VALUE
+notmuch_rb_message_remove_all_tags (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    ret = notmuch_message_remove_all_tags (message);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.maildir_flags_to_tags => true
+ *
+ * Add/remove tags according to maildir flags in the message filename (s)
+ */
+VALUE
+notmuch_rb_message_maildir_flags_to_tags (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    ret = notmuch_message_maildir_flags_to_tags (message);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.tags_to_maildir_flags => true
+ *
+ * Rename message filename (s) to encode tags as maildir flags
+ */
+VALUE
+notmuch_rb_message_tags_to_maildir_flags (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    ret = notmuch_message_tags_to_maildir_flags (message);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.freeze => true
+ *
+ * Freeze the 'message'
+ */
+VALUE
+notmuch_rb_message_freeze (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    ret = notmuch_message_freeze (message);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
+
+/*
+ * call-seq: MESSAGE.thaw => true
+ *
+ * Thaw a 'message'
+ */
+VALUE
+notmuch_rb_message_thaw (VALUE self)
+{
+    notmuch_status_t ret;
+    notmuch_message_t *message;
+
+    Data_Get_Notmuch_Message (self, message);
+
+    ret = notmuch_message_thaw (message);
+    notmuch_rb_status_raise (ret);
+
+    return Qtrue;
+}
diff --git a/bindings/ruby/messages.c b/bindings/ruby/messages.c
new file mode 100644 (file)
index 0000000..a337fee
--- /dev/null
@@ -0,0 +1,80 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: MESSAGES.destroy! => nil
+ *
+ * Destroys the messages, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_messages_destroy (VALUE self)
+{
+    notmuch_messages_t *messages;
+
+    Data_Get_Notmuch_Messages (self, messages);
+
+    notmuch_messages_destroy (messages);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/* call-seq: MESSAGES.each {|item| block } => MESSAGES
+ *
+ * Calls +block+ once for each message in +self+, passing that element as a
+ * parameter.
+ */
+VALUE
+notmuch_rb_messages_each (VALUE self)
+{
+    notmuch_message_t *message;
+    notmuch_messages_t *messages;
+
+    Data_Get_Notmuch_Messages (self, messages);
+
+    for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+       rb_yield (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message));
+    }
+
+    return self;
+}
+
+/*
+ * call-seq: MESSAGES.tags => TAGS
+ *
+ * Collect tags from the messages
+ */
+VALUE
+notmuch_rb_messages_collect_tags (VALUE self)
+{
+    notmuch_tags_t *tags;
+    notmuch_messages_t *messages;
+
+    Data_Get_Notmuch_Messages (self, messages);
+
+    tags = notmuch_messages_collect_tags (messages);
+    if (!tags)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
+}
diff --git a/bindings/ruby/query.c b/bindings/ruby/query.c
new file mode 100644 (file)
index 0000000..8b46d70
--- /dev/null
@@ -0,0 +1,209 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011, 2012 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: QUERY.destroy! => nil
+ *
+ * Destroys the query, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_query_destroy (VALUE self)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    notmuch_query_destroy (query);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: QUERY.sort => fixnum
+ *
+ * Get sort type of the +QUERY+
+ */
+VALUE
+notmuch_rb_query_get_sort (VALUE self)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    return FIX2INT (notmuch_query_get_sort (query));
+}
+
+/*
+ * call-seq: QUERY.sort=(fixnum) => nil
+ *
+ * Set sort type of the +QUERY+
+ */
+VALUE
+notmuch_rb_query_set_sort (VALUE self, VALUE sortv)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    if (!FIXNUM_P (sortv))
+       rb_raise (rb_eTypeError, "Not a Fixnum");
+
+    notmuch_query_set_sort (query, FIX2UINT (sortv));
+
+    return Qnil;
+}
+
+/*
+ * call-seq: QUERY.to_s => string
+ *
+ * Get query string of the +QUERY+
+ */
+VALUE
+notmuch_rb_query_get_string (VALUE self)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    return rb_str_new2 (notmuch_query_get_query_string (query));
+}
+
+/*
+ * call-seq: QUERY.add_tag_exclude(tag) => nil
+ *
+ * Add a tag that will be excluded from the query results by default.
+ */
+VALUE
+notmuch_rb_query_add_tag_exclude (VALUE self, VALUE tagv)
+{
+    notmuch_query_t *query;
+    const char *tag;
+
+    Data_Get_Notmuch_Query (self, query);
+    tag = RSTRING_PTR(tagv);
+
+    notmuch_query_add_tag_exclude(query, tag);
+    return Qnil;
+}
+
+/*
+ * call-seq: QUERY.omit_excluded=(boolean) => nil
+ *
+ * Specify whether to omit excluded results or simply flag them.
+ * By default, this is set to +true+.
+ */
+VALUE
+notmuch_rb_query_set_omit_excluded (VALUE self, VALUE omitv)
+{
+    notmuch_query_t *query;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    notmuch_query_set_omit_excluded (query, RTEST (omitv));
+
+    return Qnil;
+}
+
+/*
+ * call-seq: QUERY.search_threads => THREADS
+ *
+ * Search for threads
+ */
+VALUE
+notmuch_rb_query_search_threads (VALUE self)
+{
+    notmuch_query_t *query;
+    notmuch_threads_t *threads;
+    notmuch_status_t status;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    status = notmuch_query_search_threads (query, &threads);
+    if (status)
+       notmuch_rb_status_raise (status);
+
+    return Data_Wrap_Struct (notmuch_rb_cThreads, NULL, NULL, threads);
+}
+
+/*
+ * call-seq: QUERY.search_messages => MESSAGES
+ *
+ * Search for messages
+ */
+VALUE
+notmuch_rb_query_search_messages (VALUE self)
+{
+    notmuch_query_t *query;
+    notmuch_messages_t *messages;
+    notmuch_status_t status;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (status)
+       notmuch_rb_status_raise (status);
+
+    return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
+}
+
+/*
+ * call-seq: QUERY.count_messages => Fixnum
+ *
+ * Return an estimate of the number of messages matching a search
+ */
+VALUE
+notmuch_rb_query_count_messages (VALUE self)
+{
+    notmuch_query_t *query;
+    notmuch_status_t status;
+    unsigned int count;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    status = notmuch_query_count_messages (query, &count);
+    if (status)
+       notmuch_rb_status_raise (status);
+
+    return UINT2NUM(count);
+}
+
+/*
+ * 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;
+    notmuch_status_t status;
+    unsigned int count;
+
+    Data_Get_Notmuch_Query (self, query);
+
+    status = notmuch_query_count_threads (query, &count);
+    if (status)
+       notmuch_rb_status_raise (status);
+
+    return UINT2NUM(count);
+}
diff --git a/bindings/ruby/rdoc.sh b/bindings/ruby/rdoc.sh
new file mode 100755 (executable)
index 0000000..1e867ff
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+if test -z "$RDOC"; then
+    RDOC=rdoc
+    if which rdoc19 >/dev/null 2>&1; then
+        RDOC=rdoc19
+    fi
+fi
+
+set -e
+set -x
+
+$RDOC --main 'Notmuch' --title 'Notmuch Ruby API' --op ruby *.c
+
+if test "$1" = "--upload"; then
+    rsync -avze ssh --delete --partial --progress ruby bach.exherbo.org:public_html/notmuch/
+fi
diff --git a/bindings/ruby/status.c b/bindings/ruby/status.c
new file mode 100644 (file)
index 0000000..a0f8863
--- /dev/null
@@ -0,0 +1,51 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+void
+notmuch_rb_status_raise (notmuch_status_t status)
+{
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       break;
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+       rb_raise (notmuch_rb_eMemoryError, "out of memory");
+    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
+       rb_raise (notmuch_rb_eReadOnlyError, "read-only database");
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+       rb_raise (notmuch_rb_eXapianError, "xapian exception");
+    case NOTMUCH_STATUS_FILE_ERROR:
+       rb_raise (notmuch_rb_eFileError, "failed to read/write file");
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+       rb_raise (notmuch_rb_eFileNotEmailError, "file not email");
+    case NOTMUCH_STATUS_NULL_POINTER:
+       rb_raise (notmuch_rb_eNullPointerError, "null pointer");
+    case NOTMUCH_STATUS_TAG_TOO_LONG:
+       rb_raise (notmuch_rb_eTagTooLongError, "tag too long");
+    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+       rb_raise (notmuch_rb_eUnbalancedFreezeThawError, "unbalanced freeze/thaw");
+    case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
+       rb_raise (notmuch_rb_eUnbalancedAtomicError, "unbalanced atomic");
+    default:
+       rb_raise (notmuch_rb_eBaseError, "unknown notmuch error");
+    }
+}
diff --git a/bindings/ruby/tags.c b/bindings/ruby/tags.c
new file mode 100644 (file)
index 0000000..db8b4cf
--- /dev/null
@@ -0,0 +1,61 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: TAGS.destroy! => nil
+ *
+ * Destroys the tags, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_tags_destroy (VALUE self)
+{
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Tags (self, tags);
+
+    notmuch_tags_destroy (tags);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: TAGS.each {|item| block } => TAGS
+ *
+ * Calls +block+ once for each element in +self+, passing that element as a
+ * parameter.
+ */
+VALUE
+notmuch_rb_tags_each (VALUE self)
+{
+    const char *tag;
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Tags (self, tags);
+
+    for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
+       tag = notmuch_tags_get (tags);
+       rb_yield (rb_str_new2 (tag));
+    }
+
+    return self;
+}
diff --git a/bindings/ruby/thread.c b/bindings/ruby/thread.c
new file mode 100644 (file)
index 0000000..9b29598
--- /dev/null
@@ -0,0 +1,213 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: THREAD.destroy! => nil
+ *
+ * Destroys the thread, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_thread_destroy (VALUE self)
+{
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    notmuch_thread_destroy (thread);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/*
+ * call-seq: THREAD.thread_id => String
+ *
+ * Returns the thread id
+ */
+VALUE
+notmuch_rb_thread_get_thread_id (VALUE self)
+{
+    const char *tid;
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    tid = notmuch_thread_get_thread_id (thread);
+
+    return rb_str_new2 (tid);
+}
+
+/*
+ * call-seq: THREAD.total_messages => fixnum
+ *
+ * Returns the number of total messages
+ */
+VALUE
+notmuch_rb_thread_get_total_messages (VALUE self)
+{
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    return INT2FIX (notmuch_thread_get_total_messages (thread));
+}
+
+/*
+ * call-seq: THREAD.toplevel_messages => MESSAGES
+ *
+ * Get a Notmuch::Messages iterator for the top level messages in thread.
+ */
+VALUE
+notmuch_rb_thread_get_toplevel_messages (VALUE self)
+{
+    notmuch_messages_t *messages;
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    messages = notmuch_thread_get_toplevel_messages (thread);
+    if (!messages)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
+}
+
+/*
+ * call-seq: THREAD.messages => MESSAGES
+ *
+ * Get a Notmuch::Messages iterator for the all messages in thread.
+ */
+VALUE
+notmuch_rb_thread_get_messages (VALUE self)
+{
+    notmuch_messages_t *messages;
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    messages = notmuch_thread_get_messages (thread);
+    if (!messages)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages);
+}
+
+/*
+ * call-seq: THREAD.matched_messages => fixnum
+ *
+ * Get the number of messages in thread that matched the search
+ */
+VALUE
+notmuch_rb_thread_get_matched_messages (VALUE self)
+{
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    return INT2FIX (notmuch_thread_get_matched_messages (thread));
+}
+
+/*
+ * call-seq: THREAD.authors => String
+ *
+ * Get a comma-separated list of the names of the authors.
+ */
+VALUE
+notmuch_rb_thread_get_authors (VALUE self)
+{
+    const char *authors;
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    authors = notmuch_thread_get_authors (thread);
+
+    return rb_str_new2 (authors);
+}
+
+/*
+ * call-seq: THREAD.subject => String
+ *
+ * Returns the subject of the thread
+ */
+VALUE
+notmuch_rb_thread_get_subject (VALUE self)
+{
+    const char *subject;
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    subject = notmuch_thread_get_subject (thread);
+
+    return rb_str_new2 (subject);
+}
+
+/*
+ * call-seq: THREAD.oldest_date => Fixnum
+ *
+ * Get the date of the oldest message in thread.
+ */
+VALUE
+notmuch_rb_thread_get_oldest_date (VALUE self)
+{
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    return UINT2NUM (notmuch_thread_get_oldest_date (thread));
+}
+
+/*
+ * call-seq: THREAD.newest_date => fixnum
+ *
+ * Get the date of the newest message in thread.
+ */
+VALUE
+notmuch_rb_thread_get_newest_date (VALUE self)
+{
+    notmuch_thread_t *thread;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    return UINT2NUM (notmuch_thread_get_newest_date (thread));
+}
+
+/*
+ * call-seq: THREAD.tags => TAGS
+ *
+ * Get a Notmuch::Tags iterator for the tags of the thread
+ */
+VALUE
+notmuch_rb_thread_get_tags (VALUE self)
+{
+    notmuch_thread_t *thread;
+    notmuch_tags_t *tags;
+
+    Data_Get_Notmuch_Thread (self, thread);
+
+    tags = notmuch_thread_get_tags (thread);
+    if (!tags)
+       rb_raise (notmuch_rb_eMemoryError, "Out of memory");
+
+    return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
+}
diff --git a/bindings/ruby/threads.c b/bindings/ruby/threads.c
new file mode 100644 (file)
index 0000000..ed403a8
--- /dev/null
@@ -0,0 +1,60 @@
+/* The Ruby interface to the notmuch mail library
+ *
+ * Copyright © 2010, 2011 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Ali Polatel <alip@exherbo.org>
+ */
+
+#include "defs.h"
+
+/*
+ * call-seq: THREADS.destroy! => nil
+ *
+ * Destroys the threads, freeing all resources allocated for it.
+ */
+VALUE
+notmuch_rb_threads_destroy (VALUE self)
+{
+    notmuch_threads_t *threads;
+
+    Data_Get_Struct (self, notmuch_threads_t, threads);
+
+    notmuch_threads_destroy (threads);
+    DATA_PTR (self) = NULL;
+
+    return Qnil;
+}
+
+/* call-seq: THREADS.each {|item| block } => THREADS
+ *
+ * Calls +block+ once for each thread in +self+, passing that element as a
+ * parameter.
+ */
+VALUE
+notmuch_rb_threads_each (VALUE self)
+{
+    notmuch_thread_t *thread;
+    notmuch_threads_t *threads;
+
+    Data_Get_Notmuch_Threads (self, threads);
+
+    for (; notmuch_threads_valid (threads); notmuch_threads_move_to_next (threads)) {
+       thread = notmuch_threads_get (threads);
+       rb_yield (Data_Wrap_Struct (notmuch_rb_cThread, NULL, NULL, thread));
+    }
+
+    return self;
+}
diff --git a/changelog b/changelog
deleted file mode 100644 (file)
index 9ed2547..0000000
--- a/changelog
+++ /dev/null
@@ -1,1294 +0,0 @@
-notmuch (0.28.2-1) unstable; urgency=medium
-
-  * [notmuch-emacs] Invoke gpg from with --batch and --no-tty
-
- -- David Bremner <bremner@debian.org>  Sun, 17 Feb 2019 07:30:33 -0400
-
-notmuch (0.28.1-1) unstable; urgency=medium
-
-  * New upstream bug fix release
-  * Bug fix: "muttprint/evince fails to show "print", thanks to
-    Joerg Jaspert (Closes: #920856).
-
- -- David Bremner <bremner@debian.org>  Fri, 01 Feb 2019 08:05:05 -0400
-
-notmuch (0.28-2) unstable; urgency=medium
-
-  * Override location of bash, because /usr/bin/bash might exist
-    thanks to usrmerge.
-
- -- David Bremner <bremner@debian.org>  Fri, 12 Oct 2018 20:54:00 -0300
-
-notmuch (0.28-1) unstable; urgency=medium
-
-  * New upstream releases.
-  * Includes threading fixes, support for relative database paths, and
-    rewritten zsh completion.
-
- -- David Bremner <bremner@debian.org>  Fri, 12 Oct 2018 20:17:27 -0300
-
-notmuch (0.28~rc0-1) experimental; urgency=medium
-
-  * New upstream release candidate.
-
- -- David Bremner <bremner@debian.org>  Wed, 03 Oct 2018 20:36:57 -0300
-
-notmuch (0.27-3) unstable; urgency=medium
-
-  * Update Vcs-Git to use https and specify correct branch
-  * Update Build-depends for unversioned emacs packages.
-
- -- David Bremner <bremner@debian.org>  Sat, 08 Sep 2018 18:20:10 -0300
-
-notmuch (0.27-2) unstable; urgency=medium
-
-  * Add texinfo as a build-dep, build info version of documentation.
-
- -- David Bremner <bremner@debian.org>  Thu, 28 Jun 2018 21:01:29 -0300
-
-notmuch (0.27-1) unstable; urgency=medium
-
-  * New upstream feature release
-    - thread subqueries (match terms in different messages of same thread)
-    - notmuch new --full-scan (ignore mtimes)
-    - notmuch show --decrypt=stash (decrypt and stash on first read)
-
- -- David Bremner <bremner@debian.org>  Tue, 12 Jun 2018 22:39:33 -0300
-
-notmuch (0.27~rc1-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Thu, 31 May 2018 08:19:00 -0300
-
-notmuch (0.27~rc0-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Sat, 26 May 2018 09:12:37 -0700
-
-notmuch (0.26.2-2) unstable; urgency=medium
-
-  * Mark gdb and dtach as <!nocheck>, meaning they are only needed for
-    the test suite.
-  * Re-enable gdb based tests on s390x, ppc64el, armel, mipsel, they
-    pass on porterbox. Leave disabled on mipsel64 (gdb tests still
-    failing), and mips (many tests fail on porterbox).
-
- -- David Bremner <bremner@debian.org>  Sun, 06 May 2018 08:36:52 -0300
-
-notmuch (0.26.2-1) unstable; urgency=medium
-
-  * Upstream bugfix release
-    - Break reference loops when indexing, fixes INTERNAL_ERROR in "notmuch show"
-    - Don't call get_mset(0,0,X), fixes crash on some gcc8 using distros
-
- -- David Bremner <bremner@debian.org>  Sat, 28 Apr 2018 08:10:24 -0300
-
-notmuch (0.26.1-1) unstable; urgency=medium
-
-  * Bump LIBRARY_MINOR_VERSION to 1, for new functions in 0.26
-
- -- David Bremner <bremner@debian.org>  Mon, 02 Apr 2018 08:08:01 -0300
-
-notmuch (0.26-1) unstable; urgency=medium
-
-  [ Daniel Kahn Gillmor ]
-  * build against python3-sphinx instead of python-sphinx
-  * d/changelog: strip trailing whitespace
-  * move to debhelper 10
-  * Standards-Version: bump to 4.1.3 (drop priority: extra
-    from transitional packages)
-
-  [ David Bremner ]
-  * New upstream release (see /usr/share/doc/notmuch/NEWS.gz)
-    - new command 'notmuch reindex'
-    - optional indexing of encrypted emails.
-    - indexing of files with duplicate message-id
-  * update symbols
-
- -- David Bremner <bremner@debian.org>  Tue, 09 Jan 2018 07:13:21 -0400
-
-notmuch (0.26~rc2-1) experimental; urgency=medium
-
-  * Third upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Tue, 09 Jan 2018 07:13:11 -0400
-
-notmuch (0.26~rc1-1) experimental; urgency=medium
-
-  * Second upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Mon, 01 Jan 2018 21:17:39 -0400
-
-notmuch (0.26~rc0-1) experimental; urgency=medium
-
-  * Upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Thu, 28 Dec 2017 10:21:08 -0400
-
-notmuch (0.25.3-1) unstable; urgency=medium
-
-  * Upstream bugfix release. Fix for OpenPGP UID validity reporting,
-    and build failure with GMime 3.0.3+.
-  * Bug fix: "notmuch FTBFS on Alpha due to broken gdb", thanks to
-    Michael Cree (Closes: #881028).
-
- -- David Bremner <bremner@debian.org>  Fri, 08 Dec 2017 21:08:00 -0400
-
-notmuch (0.25.2-1) unstable; urgency=medium
-
-  * New upstream bugfix release: fix for segfault when compiled
-    against gmime-2.6
-
- -- David Bremner <bremner@debian.org>  Sun, 05 Nov 2017 20:05:49 -0400
-
-notmuch (0.25.1-1) unstable; urgency=medium
-
-  * new upstream bugfix release: mitigation for emacs bug 28350
-  * remove obsolete lintian override
-  * reformat debian/NEWS
-
- -- David Bremner <bremner@debian.org>  Mon, 11 Sep 2017 22:20:48 -0300
-
-notmuch (0.25-6) unstable; urgency=medium
-
-  * Bug fix: "deletes shipped file on reinstall:
-    /etc/emacs/site-start.d/50notmuch.el", thanks to Andreas Beckmann
-    (Closes: #872197).
-
- -- David Bremner <bremner@debian.org>  Tue, 15 Aug 2017 07:52:21 -0300
-
-notmuch (0.25-5) unstable; urgency=medium
-
-  * Bug fix: "dependency on elpa-emacs doesn't seem right", thanks
-    to Jiri Palecek (Closes: #871642).
-
- -- David Bremner <bremner@debian.org>  Thu, 10 Aug 2017 06:42:50 -0400
-
-notmuch (0.25-4) unstable; urgency=medium
-
-  * Recommend elpa-emacs instead emacs-notmuch
-
- -- David Bremner <bremner@debian.org>  Fri, 04 Aug 2017 18:11:35 -0400
-
-notmuch (0.25-3) unstable; urgency=medium
-
-  * Remove old startup file /etc/emacs/site-start.d/50notmuch.el
-
- -- David Bremner <bremner@debian.org>  Thu, 03 Aug 2017 09:26:00 -0400
-
-notmuch (0.25-2) unstable; urgency=medium
-
-  * Drop build-dep on libgmime-2.4-dev, long unsupported upstream
-  * Bug fix: "please transition to gmime 3.0", thanks to Daniel Kahn
-    Gillmor (Closes: #867353).
-
- -- David Bremner <bremner@debian.org>  Wed, 26 Jul 2017 10:59:14 -0400
-
-notmuch (0.25-1) unstable; urgency=medium
-
-  * New upstream release
-    - regexp search for mid, paths, tags
-    - drop inline images when indexing html
-
- -- David Bremner <bremner@debian.org>  Tue, 25 Jul 2017 08:28:20 -0300
-
-notmuch (0.25~rc1-2) unstable; urgency=low
-
-  * upload to unstable
-
- -- David Bremner <bremner@debian.org>  Tue, 18 Jul 2017 19:47:45 -0300
-
-notmuch (0.25~rc1-1) experimental; urgency=medium
-
-  * Bump standards version to 4.0.0 (no changes needed)
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Tue, 18 Jul 2017 07:04:14 -0300
-
-notmuch (0.25~rc0-2) experimental; urgency=medium
-
-  * Fix compilation on 32 bit architectures (time_t vs. gint64)
-
- -- David Bremner <bremner@debian.org>  Mon, 17 Jul 2017 08:49:32 -0300
-
-notmuch (0.25~rc0-1) experimental; urgency=medium
-
-  * New upstream release candidate
-  * Drop notmuch-dbg, use notmuch-dbgsym if debug symbols are needed.
-  * [lib] Bump SONAME to libnotmuch.so.5
-  * Bug fix: "doesn't check gpg/pgp signatures by default", thanks
-    to Vagrant Cascadian (Closes: #755544).
-  * Bug fix: "allow separation of command-line options and their
-    values: --option foo", thanks to Paul Wise (Closes: #825886).
-
- -- David Bremner <bremner@debian.org>  Sun, 16 Jul 2017 08:48:59 -0300
-
-notmuch (0.24.2-2) unstable; urgency=medium
-
-  * rebuild for unstable
-
- -- David Bremner <bremner@debian.org>  Sun, 02 Jul 2017 12:48:46 -0300
-
-notmuch (0.24.2-1) experimental; urgency=medium
-
-  * Fix dump output to not include tags when not asked for
-  * Fix file name stashing in emacs tree view.
-
- -- David Bremner <bremner@debian.org>  Thu, 01 Jun 2017 07:24:55 -0300
-
-notmuch (0.24.1-1) experimental; urgency=medium
-
-  * Restore Xapian wildcard queries to from: and subject:
-  * Handle empty queries for from: and subject:
-  * Memory leaks in notmuch show fixed
-  * Fix bug notmuch dump header generation
-
- -- David Bremner <bremner@debian.org>  Sat, 01 Apr 2017 09:17:47 -0300
-
-notmuch (0.24-1) experimental; urgency=medium
-
-  * New upstream release
-    - regexp search for from: and subject:
-    - Emacs interface improvements:
-      - draft handling
-      - don't automatically expand application/*
-      - jump (shortcut) menu for tagging.
-      - fold long headers when sending
-    - library improvements
-      - catch some stray DatabaseModifiedErrors
-      - make exclude handling non-destructive.
-
- -- David Bremner <bremner@debian.org>  Sun, 12 Mar 2017 22:14:25 -0300
-
-notmuch (0.24~rc1-1) experimental; urgency=medium
-
-  * New upstream release candidate
-  * upstream release notes
-  * One library internals fix/optimization for regexp processing.
-
- -- David Bremner <bremner@debian.org>  Wed, 08 Mar 2017 08:08:34 -0400
-
-notmuch (0.24~rc0-1) experimental; urgency=medium
-
-  * New upstream feature release (candidate).
-
- -- David Bremner <bremner@debian.org>  Sun, 05 Mar 2017 19:32:08 -0400
-
-notmuch (0.23.7-3) unstable; urgency=medium
-
-  * Cherry pick fixes to dump header from 0.24.1
-
- -- David Bremner <bremner@debian.org>  Sat, 01 Apr 2017 21:09:36 -0300
-
-notmuch (0.23.7-2) unstable; urgency=medium
-
-  * Cherry pick 06adc276, fix use after free in libnotmuch4
-
- -- David Bremner <bremner@debian.org>  Sun, 19 Mar 2017 09:38:17 -0300
-
-notmuch (0.23.7-1) unstable; urgency=medium
-
-  * Move test suite $GNUPGHOME to /tmp to avoid problems with long build paths.
-  * Fix read-after-free bug in `notmuch new`.
-
- -- David Bremner <bremner@debian.org>  Tue, 28 Feb 2017 20:39:30 -0400
-
-notmuch (0.23.5-1) unstable; urgency=medium
-
-  * Remove RUNPATH from /usr/bin/notmuch
-
- -- David Bremner <bremner@debian.org>  Mon, 09 Jan 2017 06:24:39 -0400
-
-notmuch (0.23.4-1) unstable; urgency=medium
-
-  * Improve error handling in notmuch insert
-  * Restore autoload cookie for notmuch-search (notmuch-emacs)
-
- -- David Bremner <bremner@debian.org>  Sat, 24 Dec 2016 17:47:48 +0900
-
-notmuch (0.23.3-3) unstable; urgency=medium
-
-  * Disable gdb using tests on kfreebsd-*, due to apparent gdb breakage
-
- -- David Bremner <bremner@debian.org>  Mon, 05 Dec 2016 08:25:32 -0400
-
-notmuch (0.23.3-2) unstable; urgency=medium
-
-  * Add missing depends to notmuch-emacs. Thanks to micah for the
-    report.
-
- -- David Bremner <bremner@debian.org>  Thu, 01 Dec 2016 08:06:59 -0400
-
-notmuch (0.23.3-1) unstable; urgency=medium
-
-  * Re-enable test suite
-  * Fix test suite compatibility with gnupg 2.1.16. Fixes: "FTBFS:
-    Tests failures", thanks to Lucas Nussbaum (Closes: #844915).
-  * Bug fix: "race condition in `notmuch new`?", thanks to Paul Wise
-    (Closes: #843127).
-
- -- David Bremner <bremner@debian.org>  Sat, 26 Nov 2016 08:37:39 -0400
-
-notmuch (0.23.2-1) unstable; urgency=medium
-
-  * New upstream bugfix release
-  * Convert notmuch-emacs to dh-elpa, new binary package elpa-notmuch
-  * Bug fix: "maintainer script(s) do not start on #!", thanks to
-    treinen@debian.org; (Closes: #843287).
-
- -- David Bremner <bremner@debian.org>  Thu, 10 Nov 2016 22:36:04 -0400
-
-notmuch (0.23.1-1) unstable; urgency=medium
-
-  * New upstream bugfix release
-  * Fix test suite for Emacs 25.1
-  * Fix some Emacs customization regressions introduced in 0.23
-  * Bug fix: "testsuite fails with TERM=unknown", thanks to Gianfranco
-    Costamagna (Closes: #841319).
-
- -- David Bremner <bremner@debian.org>  Sun, 23 Oct 2016 22:06:12 -0300
-
-notmuch (0.23-2) unstable; urgency=medium
-
-  * upload to unstable
-
- -- David Bremner <bremner@debian.org>  Wed, 05 Oct 2016 21:27:00 -0300
-
-notmuch (0.23-1) experimental; urgency=medium
-
-  * New upstream release
-  * Bump minor version of libnotmuch4 because of new symbols.
-  * Several new features, see /usr/share/doc/notmuch/NEWS.gz
-
- -- David Bremner <bremner@debian.org>  Mon, 03 Oct 2016 22:46:26 -0300
-
-notmuch (0.23~rc1-1) experimental; urgency=medium
-
-  * New upstream release candidate
-  * Make configure more robust on "unknown" platforms. Fixes FTBFS on
-    kfreebsd.
-
- -- David Bremner <bremner@debian.org>  Fri, 30 Sep 2016 07:19:26 -0300
-
-notmuch (0.23~rc0-1) experimental; urgency=medium
-
-  * New upstream release candidate
-  * Bug fix: "Calls to notmuch_directory_get_mtime() don't return
-    the recently set mtime", thanks to Lars Luthman (Closes: #826881).
-  * Bug fix: "Please document compact command", thanks to Olivier
-    Berger (Closes: #825884).
-
- -- David Bremner <bremner@debian.org>  Mon, 26 Sep 2016 07:28:06 -0300
-
-notmuch (0.22.2-1) unstable; urgency=medium
-
-  * Fix test suite compatibility with GnuPG 2.1.15.  Bug fix: "FTBFS:
-    Tests failures", thanks to Lucas Nussbaum (Closes: #837013).
-
- -- David Bremner <bremner@debian.org>  Thu, 08 Sep 2016 19:09:53 -0300
-
-notmuch (0.22.1-3) unstable; urgency=medium
-
-  * Gag gdb even more. Bug fix: "FTBFS in testing", thanks to Santiago
-    Vila (Closes: #834271).
-
- -- David Bremner <bremner@debian.org>  Sun, 14 Aug 2016 13:31:13 +0900
-
-notmuch (0.22.1-2) unstable; urgency=medium
-
-  * Add explicit build-depends on gnupg, for the test suite.
-
- -- David Bremner <bremner@debian.org>  Tue, 19 Jul 2016 08:50:13 -0300
-
-notmuch (0.22.1-1) unstable; urgency=medium
-
-  * Correct the definition of `LIBNOTMUCH_CHECK_VERSION`.
-  * Document the (lack of) operations permitted on a closed database
-    (Closes: #826843).
-  * Fix race condition in dump / restore tests.
-  * [notmuch-emacs] Tell `message-mode` mode that outgoing messages are mail
-  * [notmuch-emacs] Respect charset of MIME parts when reading them
-
- -- David Bremner <bremner@debian.org>  Tue, 19 Jul 2016 06:42:09 -0300
-
-notmuch (0.22.1~rc0-1) experimental; urgency=medium
-
-  * release candidate for bugfix release
-
- -- David Bremner <bremner@debian.org>  Thu, 30 Jun 2016 21:28:13 +0200
-
-notmuch (0.22-1) unstable; urgency=medium
-
-  * New upstream release.  See /usr/share/doc/notmuch/NEWS for new
-    features and bug fixes.
-
- -- David Bremner <bremner@debian.org>  Tue, 26 Apr 2016 21:31:44 -0300
-
-notmuch (0.22~rc1-1) experimental; urgency=medium
-
-  * Upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Sun, 24 Apr 2016 18:03:15 -0300
-
-notmuch (0.22~rc0-1) experimental; urgency=medium
-
-  * Upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Sat, 16 Apr 2016 08:45:32 -0300
-
-notmuch (0.21-3) unstable; urgency=medium
-
-  * Add mips and mips64el to gdb build-dep blacklist
-
- -- David Bremner <bremner@debian.org>  Sat, 14 Nov 2015 19:07:06 -0400
-
-notmuch (0.21-2) unstable; urgency=medium
-
-  * Build-conflict with gdb on ppc64el and mipsel. Workaround gdb breakage on those
-    architectures (Closes: #804792).
-
- -- David Bremner <bremner@debian.org>  Thu, 12 Nov 2015 08:54:23 -0400
-
-notmuch (0.21-1) unstable; urgency=medium
-
-  * New upstream release. Highlights include
-    - revision tracking for metadata
-    - new features and bug fixes for emacs interface
-    See /usr/share/doc/notmuch/NEWS for more details.
-
- -- David Bremner <bremner@debian.org>  Thu, 29 Oct 2015 20:04:42 -0300
-
-notmuch (0.21~rc3-3) experimental; urgency=medium
-
-  * Build-conflict with gdb-minimal. gdb python scripts are needed for
-    the test suite
-
- -- David Bremner <bremner@debian.org>  Sun, 25 Oct 2015 22:08:56 -0300
-
-notmuch (0.21~rc3-2) experimental; urgency=medium
-
-  * Bug fix: "reply-to encrypted messages in tree view fails to quote
-    and defaults to unencrypted message", thanks to Vagrant Cascadian
-    (Closes: #795243).
-  * Bug fix: "install/notmuch-emacs may interact with console, fail
-    emacs24 upgrade", thanks to Hilko Bengen (Closes: #802952).
-
- -- David Bremner <bremner@debian.org>  Sun, 25 Oct 2015 13:42:57 -0300
-
-notmuch (0.21~rc3-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Thu, 22 Oct 2015 09:19:02 -0300
-
-notmuch (0.21~rc2-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Mon, 19 Oct 2015 07:25:10 -0300
-
-notmuch (0.21~rc1-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Thu, 15 Oct 2015 08:08:17 -0300
-
-notmuch (0.20.2-2) unstable; urgency=medium
-
-  * Fix linking in emacsen-install script. The previous version can
-    break an emacs upgrade.
-
- -- David Bremner <bremner@debian.org>  Sat, 26 Sep 2015 09:26:41 -0300
-
-notmuch (0.20.2-1) unstable; urgency=medium
-
-  * Bug fix: "notmuch-tree does not mark messages as read", thanks to
-    Raúl Benencia (Closes: #789693).
-
- -- David Bremner <bremner@debian.org>  Sat, 27 Jun 2015 15:03:33 +0200
-
-notmuch (0.20.1-1) unstable; urgency=medium
-
-  * Bug fix: "FTBFS on arm64", thanks to Edmund Grimley Evans (Closes:
-    #787341).
-
- -- David Bremner <bremner@debian.org>  Mon, 01 Jun 2015 21:58:54 +0200
-
-notmuch (0.20-1) unstable; urgency=medium
-
-  * New upstream release
-    - new mimetype search prefix
-    - improvements to emacs, vim, and mutt front-ends
-    - undeprecate single message mboxes.
-    - reduced noise on stderr from the library
-    - improved API for notmuch_query_search_{messages, thread}
-
- -- David Bremner <bremner@debian.org>  Sun, 31 May 2015 11:21:14 +0200
-
-notmuch (0.20~rc2-1) experimental; urgency=medium
-
-  * New upstream release candidate.
-  * Fix breakage of python bindings under python3
-
- -- David Bremner <bremner@debian.org>  Sat, 23 May 2015 21:05:03 +0200
-
-notmuch (0.20~rc1-1) experimental; urgency=medium
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Mon, 04 May 2015 08:08:00 +0200
-
-notmuch (0.19-1) experimental; urgency=medium
-
-  * New upstream release.
-    - Improvements to reliability of 'notmuch dump' and the error
-    handling for 'notmuch insert'.
-    - The new 'notmuch address' command is intended to make searching
-    for email addresses more convenient.
-    - At the library level the revised handling of missing messages
-    fixes at least one bug in threading.
-    - Interface improvements to the emacs interface, most notably the
-    ability to bindkeyboard shortcuts to saved searches.
-
- -- David Bremner <bremner@debian.org>  Fri, 14 Nov 2014 19:34:12 +0100
-
-notmuch (0.19~rc2-1) experimental; urgency=medium
-
-  * New upstream release candidate
-  * Updated defaults for "notmuch address"
-  * Assert compliance with policy 3.9.6
-
- -- David Bremner <bremner@debian.org>  Sun, 09 Nov 2014 16:46:31 +0100
-
-notmuch (0.19~rc1-1) experimental; urgency=low
-
-  * New upstream release candidate
-  * Bump libnotmuch SONAME because of API changes
-
- -- David Bremner <bremner@debian.org>  Thu, 06 Nov 2014 00:30:39 +0100
-
-notmuch (0.18.2-1) unstable; urgency=medium
-
-  * Rebuild for unstable.
-  * Translate T380-atomicity to use gdb/python
-  * Emacs 24.4 related bug fixes
-  * Simplify T360-symbol-hiding, port to ppc64el
-
- -- David Bremner <bremner@debian.org>  Sat, 25 Oct 2014 18:19:53 +0200
-
-notmuch (0.18.2~rc1-1) experimental; urgency=medium
-
-  * Test suite bug and portability fix release.
-
- -- David Bremner <bremner@debian.org>  Sat, 25 Oct 2014 10:48:16 +0200
-
-notmuch (0.18.1-2) unstable; urgency=medium
-
-  * Update build-deps to use emacs24 on buildd (Closes: #756085)
-  * Disable gdb atomicity test for arm64 as gdb is currently broken on
-    the (unofficial) buildds
-  * Re-enable atomicity test on armhf; upstream fix seems to have
-    worked.
-
- -- David Bremner <bremner@debian.org>  Sat, 09 Aug 2014 11:48:10 -0300
-
-notmuch (0.18.1-1) unstable; urgency=medium
-
-  * New upstream bug fix release
-    - Re-enable support for single-message mbox files
-    - Fix for phrase indexing
-    - Make tagging empty query in Emacs harmless
-
- -- David Bremner <bremner@debian.org>  Wed, 25 Jun 2014 07:20:45 -0300
-
-notmuch (0.18.1~rc0-1) experimental; urgency=medium
-
-  * New upstream bug fix release (candidate)
-  * Tighten dependence of python packages on libnotmuch
-    (Closes: #749881).
-  * Redo emacsen-install script from sample in emacsen-common
-    (Closes: #739839).
-
- -- David Bremner <bremner@debian.org>  Sat, 14 Jun 2014 07:50:28 -0300
-
-notmuch (0.18-3) unstable; urgency=medium
-
-  * Disable atomicity tests on armel.
-
- -- David Bremner <bremner@debian.org>  Thu, 08 May 2014 14:26:45 +0900
-
-notmuch (0.18-2) unstable; urgency=medium
-
-  * Disable atomicity tests on armhf. These should be re-enabled when
-    upstream releases a fix for this (in progress).
-
- -- David Bremner <bremner@debian.org>  Thu, 08 May 2014 08:28:33 +0900
-
-notmuch (0.18-1) unstable; urgency=medium
-
-  * New upstream release. For detailed release notes see
-    see /usr/share/doc/notmuch/NEWS.gz. Some highlights:
-    - Changes/enhancements to searching for messages by filesystem
-      location ('folder:' and 'path:' prefixes).
-    - Saved searches in Emacs have also been enhanced to allow
-      distinct search orders for each one.
-    - Another enhancement to the Emacs interface is that replies to
-      encrypted messages are now encrypted, reducing the risk of
-      unintentional information disclosure.
-    - The default dump output format has changed to the more robust
-      'batch-tag' format.
-    - The previously deprecated parsing of single message mboxes has
-      been removed.
-
- -- David Bremner <bremner@debian.org>  Tue, 06 May 2014 16:20:39 +0900
-
-notmuch (0.18~rc1-1) experimental; urgency=low
-
-  * Upstream release candidate
-    - include encoding fix for vim client.
-
- -- David Bremner <bremner@debian.org>  Sun, 04 May 2014 07:29:51 +0900
-
-notmuch (0.18~rc0-1) experimental; urgency=low
-
-  * Upstream release candidate.
-  * Bug fix: "insufficient sanitization of arguments to notmuch CLI",
-    thanks to Antoine Beaupré (Closes: #737496).
-  * Bug fix: "notmuch(1) manpage: typo: int -> in", thanks to Jakub
-    Wilk (Closes: #739556).
-  * Bug fix: "get a quiet option", thanks to Joerg Jaspert (Closes:
-    #666027).
-  * Bug fix: "Please remove me from Uploaders", thanks to martin f
-    krafft (Closes: #719100).
-  * Bug fix: "M-x notmuch-show-reply on an encrypted message should
-    insert encryption tags into the mml buffer", thanks to Daniel Kahn
-    Gillmor (Closes: #704648).
-
- -- David Bremner <bremner@debian.org>  Tue, 22 Apr 2014 09:27:29 +0900
-
-notmuch (0.17-5) unstable; urgency=medium
-
-  * Bug fix: "unowned directory after purge: /0755/", thanks to
-    Andreas Beckmann (Closes: #740325).
-
- -- David Bremner <bremner@debian.org>  Mon, 03 Mar 2014 07:29:06 -0400
-
-notmuch (0.17-4) unstable; urgency=medium
-
-  * Bug fix: "Please update ruby binary extension install path",
-    thanks to Christian Hofstaedtler (Closes: #739120).
-
- -- David Bremner <bremner@debian.org>  Tue, 18 Feb 2014 21:37:44 -0400
-
-notmuch (0.17-3) unstable; urgency=medium
-
-  * update notmuch-emacs for debian emacs policy 2.0.6
-  * Update emacs test suite for Hurd compatibility
-
- -- David Bremner <bremner@debian.org>  Sun, 12 Jan 2014 17:07:16 -0400
-
-notmuch (0.17-2) unstable; urgency=medium
-
-  * Bug fix: "package should warn in a NEWS.Debian file about possible
-    pre-upgrade action", thanks to Jonas Smedegaard (Closes: #733853).
-
- -- David Bremner <bremner@debian.org>  Wed, 01 Jan 2014 07:44:25 -0400
-
-notmuch (0.17-1) unstable; urgency=low
-
-  * New upstream feature release. See /usr/share/doc/notmuch/NEWS.gz
-    for details.  Highlights include:
-    - notmuch compact command (Closes: #720543).
-    - emacs "tree" view
-  * Remove madduck from uploaders (Closes: #719100).
-
- -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:28:20 -0400
-
-notmuch (0.17~rc4-1) experimental; urgency=low
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Sat, 28 Dec 2013 18:30:06 -0400
-
-notmuch (0.17~rc3-1) experimental; urgency=low
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Sat, 07 Dec 2013 17:05:11 +0800
-
-notmuch (0.17~rc2-1) experimental; urgency=low
-
-  * New upstream release candidate
-  * Remove gdb as build-dep on s390x. This implicitly disables failing
-    atomicity test.  For more information, see #728705
-
- -- David Bremner <bremner@debian.org>  Sun, 24 Nov 2013 19:34:28 -0400
-
-notmuch (0.17~rc1-1) experimental; urgency=low
-
-  * New upstream release candidate
-
- -- David Bremner <bremner@debian.org>  Wed, 20 Nov 2013 19:27:48 -0400
-
-notmuch (0.16-1) unstable; urgency=low
-
-  * New upstream feature release
-    - 'notmuch insert' command replaces notmuch-deliver (Closes: #692889).
-    - New ruby based vim interface (Closes: 616692, 636707).
-  * Provide a notmuch-dbg package, thanks to Daniel Kahn Gillmor
-    (Closes: #717339).
-  * Include alot to the list of recommended interfaces, thanks to
-    Simon Chopin (Closes: #709832).
-
- -- David Bremner <bremner@debian.org>  Sat, 03 Aug 2013 08:28:39 -0300
-
-notmuch (0.15.2-2) unstable; urgency=low
-
-  * Bug fix: tighten dependence of notmuch-mutt on notmuch,
-    thanks to Philippe Gimmel and Jameson Rollins (Closes: #703608).
-  * Bump Standards-Version to 3.9.4; no changes.
-
- -- David Bremner <bremner@debian.org>  Sat, 25 May 2013 18:37:23 -0300
-
-notmuch (0.15.2-1) experimental; urgency=low
-
-  * Upstream bug fix release.
-    - Improve support for parallel building
-    - Update Emacs tests for portability, fix FTBFS on hurd-i386
-
- -- David Bremner <bremner@debian.org>  Fri, 22 Mar 2013 20:42:42 -0400
-
-notmuch (0.15.1-1) experimental; urgency=low
-
-  * Upstream bug fix release: set default TERM for running tests.
-  * Re-enable build time self-tests.
-
- -- David Bremner <bremner@debian.org>  Thu, 24 Jan 2013 07:19:45 -0400
-
-notmuch (0.15-2) experimental; urgency=low
-
-  * Disable tests until a proper fix for running tests without a
-    proper TERM value is developed (again).
-
- -- David Bremner <bremner@debian.org>  Sun, 20 Jan 2013 18:36:16 -0400
-
-notmuch (0.15-1) experimental; urgency=low
-
-  * New upstream release.
-    - Date range search support
-    - Empty tag names and tags beginning with "-" are deprecated
-    - Support for single message mboxes is deprecated
-    - Fixed `notmuch new` to skip ignored broken symlinks
-    - New dump/restore format and tagging interface
-    - Emacs Interface
-      - Removal of the deprecated `notmuch-folders` variable
-      - Visibility of MIME parts can be toggled
-      - Emacs now buttonizes mid: links
-      - Improved text/calendar content handling
-      - Disabled coding conversions when reading
-      - Fixed errors with HTML email containing images in Emacs 24
-      - Fixed handling of tags with unusual characters in them
-      - Fixed buttonization of id: links without quote characters
-      - Automatic tag changes are now unified and customizable
-      - Support for stashing the thread id in show view
-      - New add-on tool: notmuch-pick
-
- -- David Bremner <bremner@debian.org>  Fri, 18 Jan 2013 21:23:36 -0400
-
-notmuch (0.15~rc1-1) experimental; urgency=low
-
-  * New upstream release candidate.
-  * Change priority to optional (Closes: #687217).
-  * Remove Dm-Upload-Allowed field, as this is no longer used by
-    Debian.
-  * Add python3 bindings, thanks to Jakub Wilk (Closes: #683515).
-  * Bug fix: ".ical attachment problem", (Closes: #688747).
-
- -- David Bremner <bremner@debian.org>  Wed, 16 Jan 2013 08:28:27 -0400
-
-notmuch (0.14-1) experimental; urgency=low
-
-  [ Stefano Zacchiroli ]
-  * notmuch-mutt: fix tag action invocation (Closes: #678012)
-  * Use notmuch-search-terms manpage in notmuch-mutt (Closes: #675073).
-
-  [ David Bremner ]
-  * Do a better job of cleaning up after configuration and testing
-    (Closes: #683505)
-  * Alternately depend on emacs24 instead of emacs23 (Closes: #677900).
-  * New upstream version
-    - incompatible changes to dump/restore syntax
-    - bug fixes for maildir synchronization
-
- -- David Bremner <bremner@debian.org>  Tue, 21 Aug 2012 10:39:33 +0200
-
-notmuch (0.13.2-1) unstable; urgency=low
-
-  * Upstream bugfix release. No changes to binary packages.
-
- -- David Bremner <bremner@debian.org>  Sat, 02 Jun 2012 18:16:01 -0300
-
-notmuch (0.13.1-1) unstable; urgency=low
-
-  * Upstream bugfix release.
-    - fix for encoding problems with reply in emacs
-    - notmuch_database_(get_directory|find_message_by_filename) now
-      work for read-only databases.
-
- -- David Bremner <bremner@debian.org>  Fri, 25 May 2012 21:19:06 -0300
-
-notmuch (0.13-1) unstable; urgency=low
-
-  * New upstream release. See /usr/share/doc/notmuch/NEWS.gz for changes.
-
-  [ Stefano Zacchiroli ]
-  * Recommend all notmuch UI (including notmuch-mutt) as alternatives,
-    to avoid unneeded vim/emacs installation. Thanks Matteo F. Vescovi
-    for the patch. (Closes: #673011)
-
- -- David Bremner <bremner@debian.org>  Tue, 15 May 2012 18:19:32 -0300
-
-notmuch (0.13~rc1-2) experimental; urgency=low
-
-  * New upstream pre-release
-  * new binary package "notmuch-mutt" for Mutt integration
-  * Bump libnotmuch SONAME because of API changes
-
- -- David Bremner <bremner@debian.org>  Sat, 05 May 2012 10:26:47 -0300
-
-notmuch (0.12-1) unstable; urgency=low
-
-  * New upstream release
-    - Python 3.2 support
-    - GMime 2.6 support
-    - Many updates to emacs interface (see /usr/share/doc/notmuch/NEWS)
-    - Optionally ignore some files/directories within mail hierarchy
-
- -- David Bremner <bremner@debian.org>  Tue, 20 Mar 2012 18:45:22 -0300
-
-notmuch (0.12~rc2-1) experimental; urgency=low
-
-  * Upstream pre-release
-  * New bug fixes since ~rc1
-    - fix for uninitialized variable
-    - fix for python bindings type signatures
-
- -- David Bremner <bremner@debian.org>  Sun, 18 Mar 2012 08:10:35 -0300
-
-notmuch (0.12~rc1-1) experimental; urgency=low
-
-  * Upstream pre-release.
-  * Bump standards version to 3.9.3; no changes.
-
- -- David Bremner <bremner@debian.org>  Thu, 01 Mar 2012 07:51:45 -0400
-
-notmuch (0.11.1-1) unstable; urgency=low
-
-  * Upstream bugfix release
-    - Fix error handling bug in python bindings
-    - Fix vulnerability in emacs reply handling
-
- -- David Bremner <bremner@debian.org>  Fri, 03 Feb 2012 08:35:41 -0400
-
-notmuch (0.11-1) unstable; urgency=low
-
-  * New upstream release.
-    - New 'hook' feature for notmuch-new
-    - performance and memory use improvements
-    - new add-on tool notmuch-deliver
-
- -- David Bremner <bremner@debian.org>  Fri, 13 Jan 2012 19:59:23 -0400
-
-notmuch (0.11~rc3-1) experimental; urgency=low
-
-  * New upstream release candidate
-    - Fix for uninitialized variable(s) in notmuch-reply
-
- -- David Bremner <bremner@debian.org>  Mon, 09 Jan 2012 07:07:46 -0400
-
-notmuch (0.11~rc2-1) experimental; urgency=low
-
-  * New upstream release candidate.
-    - Includes fix for one python bindings segfault.
-
- -- David Bremner <bremner@debian.org>  Mon, 02 Jan 2012 06:57:29 -0400
-
-notmuch (0.11~rc1-1) experimental; urgency=low
-
-  * New upstream release candidate.
-
- -- David Bremner <bremner@debian.org>  Sun, 25 Dec 2011 23:07:08 -0400
-
-notmuch (0.10.2-1) unstable; urgency=low
-
-  * Upstream bug fix release
-    - Fix segfault in python bindings due to missing g_type_init call.
-
- -- David Bremner <bremner@debian.org>  Sun, 04 Dec 2011 22:06:46 -0400
-
-notmuch (0.10.1-1) unstable; urgency=low
-
-  * Upstream bug fix release.
-    - Fix segfault on "notmuch --help"
-
- -- David Bremner <bremner@debian.org>  Fri, 25 Nov 2011 12:11:30 -0500
-
-notmuch (0.10-1) unstable; urgency=low
-
-  * New upstream release
-    - search performance improvements
-    - emacs UI improvements
-    - new dump/restore features
-    - new script contrib/nmbug for sharing tags
-    - see /usr/share/doc/notmuch/NEWS for details
-
- -- David Bremner <bremner@debian.org>  Wed, 23 Nov 2011 07:44:01 -0400
-
-notmuch (0.10~rc2-1) experimental; urgency=low
-
-  * New upstream release candidate
-    - includes patch to avoid long unix domain socket paths in tests
-
- -- David Bremner <bremner@debian.org>  Sat, 19 Nov 2011 08:21:39 -0400
-
-notmuch (0.10~rc1-1) experimental; urgency=low
-
-  * New upstream release candidate.
-
- -- David Bremner <bremner@debian.org>  Tue, 15 Nov 2011 19:46:55 -0400
-
-notmuch (0.9-1) unstable; urgency=low
-
-  * New upstream release.
-  * Only doc changes since last release candidate.
-  * Upload to unstable.
-
- -- David Bremner <bremner@debian.org>  Tue, 11 Oct 2011 21:51:29 -0300
-
-notmuch (0.9~rc2-1) experimental; urgency=low
-
-  * Upstream release candidate
-  * API changes for n_d_find_message and n_d_find_message_by_filename.
-    - New SONAME for libnotmuch
-    - bindings changes for ruby and python
-  * Less non-text parts reported in replies.
-  * emacs: provide button action to fetch unknown gpg keys
-
- -- David Bremner <bremner@debian.org>  Fri, 07 Oct 2011 18:53:04 -0300
-
-notmuch (0.9~rc1-1) experimental; urgency=low
-
-  * Upstream release candidate
-    - Atomicity improvements, thanks to Austin Clements
-    - Add missing call to g_type_init, thanks to Aaron Ecay
-  * notmuch-emacs: add versioned dependency on notmuch.
-    (Closes: #642240).
-
- -- David Bremner <bremner@debian.org>  Sun, 25 Sep 2011 11:26:01 -0300
-
-notmuch (0.8-1) unstable; urgency=low
-
-  * New upstream version.
-    - Improved handling of message/rfc822 parts
-    - Improved Build system portability
-    - Documentation update for Ruby bindings
-    - Unicode, iterator, PEP8 changes for python bindings
-
- -- David Bremner <bremner@debian.org>  Sat, 10 Sep 2011 08:53:55 -0300
-
-notmuch (0.8~rc1-1) experimental; urgency=low
-
-  * Upstream release candidate.
-
- -- David Bremner <bremner@debian.org>  Tue, 06 Sep 2011 22:24:24 -0300
-
-notmuch (0.7-1) unstable; urgency=low
-
-  * New upstream release (no changes since 0.7~rc1).
-  * Upload to unstable.
-
- -- David Bremner <bremner@debian.org>  Mon, 01 Aug 2011 21:46:26 +0200
-
-notmuch (0.7~rc1-1) experimental; urgency=low
-
-  * Upstream release candidate.
-  * Fix for notmuch.sym and parallel build (Thanks to
-    Thomas Jost)
-  * Bug fixes from Jason Woofenden for vim interface:
-    -  Fix "space (in show mode) mostly adds tag:inbox and tag:unread
-       instead of removing them"  (Closes: #633009).
-    -  Fix "says press 's'; to toggle signatures, but it's
-       really 'i'",  (Closes: #633115).
-    -  Fix "fix for from list on search pages", (Closes: #633045).
-  * Bug fixes for vim interface from Uwe Kleine-König
-    - use full path for sendmail/doc fix
-    - fix compose temp file name
-  * Python tag encoding fixes from Sebastian Spaeth.
-
- -- David Bremner <bremner@debian.org>  Fri, 29 Jul 2011 12:16:56 +0200
-
-notmuch (0.6.1-1) unstable; urgency=low
-
-  * Properly install README.Debian in notmuch-vim (Closes: #632992).
-    Thanks to Jason Woofenden for the report.
-  * Force notmuch to depend on the same version of libnotmuch. Thanks to
-    Uwe Kleine-König for the patch.
-  * Export typeinfo for Xapian exceptions from libnotmuch. This fixes
-    certain mysterious uncaught exception problems.
-
- -- David Bremner <bremner@debian.org>  Sun, 17 Jul 2011 10:20:42 -0300
-
-notmuch (0.6) unstable; urgency=low
-
-  * New upstream release; see /usr/share/doc/notmuch/NEWS for
-    details. Highlights include:
-    - Folder-based search (Closes: #597222)
-    - PGP/MIME decryption and verification
-  * Document strict dependency on emacs23 (Closes: #631994).
-
- -- David Bremner <bremner@debian.org>  Fri, 01 Jul 2011 11:45:22 -0300
-
-notmuch (0.6~rc1) experimental; urgency=low
-
-  * Git snapshot 3f777b2
-  * Upstream release candidate.
-  * Fix description of notmuch-vim to mention vim, not emacs
-    (Closes: #631974)
-  * Install zsh completion as an example instead of into /usr/share/zsh to
-    avoid file conflict with zsh.
-
- -- David Bremner <bremner@debian.org>  Thu, 30 Jun 2011 10:02:05 -0300
-
-notmuch (0.6~254) experimental; urgency=low
-
-  [David Bremner]
-  * Git snapshot fba968d
-  * Upstream release candidate
-  * Build binary package python-notmuch from upstream notmuch.
-  * Split off emacs and vim interfaces into their own packages.
-    (Closes: #578199)
-  * Hide Xapian exception symbols
-  * Build-depend on emacs23-nox instead of emacs
-
-  [ Jameson Rollins ]
-  * Bump standards version to 3.9.2 (No changes).
-
- -- David Bremner <bremner@debian.org>  Thu, 23 Jun 2011 07:50:05 -0300
-
-notmuch (0.6~237) experimental; urgency=low
-
-  * Git snapshot 12d6f90
-  * Emacs: hide original message in top posted replies, isearch fix,
-    message display/hiding fixes/improvements.
-  * CLI: received header fixes.
-  * python: Improve docs, Remove Messages().__len__, Implement
-    Message.__cmp__ and __hash__, Message.tags_to_maildir_flags
-
- -- David Bremner <bremner@debian.org>  Sat, 18 Jun 2011 11:14:51 -0300
-
-notmuch (0.6~215) experimental; urgency=low
-
-  * Git snapshot 5143e5e
-  * GMime: improve password handling, prevent premature closing stdout
-  * Emacs: sender address UI tweaks
-  * lib/message-file: plug three memleaks.
-  * Updated python bindings
-  * Sanitize "Subject:" and "Author:" fields in notmuch-search
-  * vim: new delete command, update mark as read command
-
- -- David Bremner <bremner@debian.org>  Sat, 04 Jun 2011 08:37:36 -0300
-
-notmuch (0.6~180) experimental; urgency=low
-
-  * Git snapshot 1a96c40
-  * Fix corruption of binary parts
-    (see ML id:"874o4a1m74.fsf@yoom.home.cworth.org")
-
- -- David Bremner <bremner@debian.org>  Tue, 31 May 2011 21:16:35 -0300
-
-notmuch (0.6~171) experimental; urgency=low
-
-  * Git snapshot cb8418784c2
-  * PGP/MIME handling in CLI and emacs front end.
-  * cli: Rewrite of multipart handling.
-  * emacs: Make the queries used in the all-tags section configurable
-  * emacs: Choose from address when composing/replying
-  * emacs: add notmuch-before- and notmuch-after-tag-hook
-  * notmuch reply: Avoid segmentation fault when printing multiple parts
-  * notmuch show: New part output handling.
-  * emacs: Show cleaner `From:' addresses in the summary line.
-  * emacs: Add custom `notmuch-show-elide-same-subject',
-    `notmuch-show-always-show-subject'
-  * emacs: Render text/x-vcalendar parts.
-  * emacs: Add `notmuch-show-multipart/alternative-discouraged'.
-  * vim: parse 'from' address, use sendmail directly, implement archive in
-    show view, refactor tagging stuff
-  * Eager metadata optimizations
-  * emacs: Fix notmuch-search-process-filter to handle incomplete lines
-  * Fix installation of zsh completion
-  * new: Enhance progress reporting
-  * Do not defer maildir flag synchronization for new messages
-  * vim: Get user email address from notmuch config file.
-  * lib: Save and restore term position in message while indexing.
-  * notmuch search: Clean up some memory leaks during search loop.
-  * New bindings for Go
-  * ruby: Add wrapper for message_get_filenames,  maildir sync. interface
-    query_get_s{ort,tring}
-  * Add support for folder-based searching.
-  * compatibility fixes for emacs22
-
- -- David Bremner <bremner@debian.org>  Sat, 28 May 2011 07:25:49 -0300
-
-notmuch (0.5+nmu3) unstable; urgency=low
-
-  * Non-maintainer upload.
-  * Upload to unstable.
-
- -- David Bremner <bremner@debian.org>  Sun, 01 May 2011 15:09:09 -0300
-
-notmuch (0.5+nmu2) experimental; urgency=low
-
-  * Non-maintainer upload.
-  * Second try at timeout for test. Put timeouts at top level.
-
- -- David Bremner <bremner@debian.org>  Sun, 19 Dec 2010 21:40:08 -0400
-
-notmuch (0.5+nmu1) experimental; urgency=low
-
-  * Non-maintainer upload.
-  * Add a timeout to emacs tests to hopefully work around build failures.
-
- -- David Bremner <bremner@debian.org>  Tue, 14 Dec 2010 22:23:51 -0400
-
-notmuch (0.5) unstable; urgency=low
-
-  * new: maildir-flag synchronization
-  * new: New "notmuch show --format=raw" (enables local emacs interface,
-    for example, to use remote notmuch via ssh)
-  * lib: Support for multiple files for a message
-    (notmuch_message_get_filenames)
-  * lib: Support for maildir-flag synchronization
-    (notmuch_message_tags_to_maildir_flags and
-    notmuch_message_maildir_flags_to_tags)
-  * emacs: Incompatible change to format of notmuch-fcc-dirs variable (for
-    users using the "fancy" configuration)
-  * emacs: Cleaner display of subject lines in thread views
-
- -- Carl Worth <cworth@debian.org>  Thu, 11 Nov 2010 20:49:11 -0800
-
-notmuch (0.4) unstable; urgency=low
-
-  * new: notmuch search --output=(summary|threads|messages|tags|files)
-  * new: notmuch show --format=mbox <search-specification>
-  * new: notmuch config [get|set] <section>.<item> [value ...]
-  * lib: Add notmuch_query_get_query_string and notmuch_query_get_sort
-  * emacs: Enable Fcc of all sent messages by default (to "sent" directory)
-  * emacs: Ability to all open messages in a thread to a pipe
-  * emacs: Optional support for detecting inline patches
-  * emacs: Automatically tag messages as "replied" when sending a reply
-  * emacs: Allow search-result color specifications to overlay each other
-  * emacs: Make hidden author names still available for incremental search.
-  * emacs: New binding of Control-TAB (works like TAB in reverse)
-  * test: New modularization of test suite.
-  * test: New testing of emacs interface.
-  * bugfix: Avoid setting Bcc header in "notmuch reply"
-  * bugfix: Avoid corruption of database when "notmuch new " is interrupted.
-  * bugfix: Fix failure with extremely long message ID headers.
-  * bugfix: Fix for messages with "charset=unknown-8bit"
-  * bugfix: Fix notmuch_query_search_threads to return NULL on any exception
-  * bugfix: Fix "notmuch search" to return non-zero on any exception
-  * emacs bugfix: Fix for message with a subject containing, "[1234]"
-  * emacs bugfix: Fix to correctly handle message IDs containing ".."
-  * emacs bugfix: Fix initialization so "M-x notmuch" works by default.
-
- -- Carl Worth <cworth@debian.org>  Mon, 01 Nov 2010 16:23:47 -0700
-
-notmuch (0.3.1) unstable; urgency=low
-
-  * Fix an infinite loop in "notmuch reply"
-  * Fix a potential SEGV in "notmuch search"
-  * emacs: Fix calculations for line wrapping in the "notmuch" view.
-  * emacs: Fix Fcc support to prompt to create a directory if necessary
-
- -- Carl Worth <cworth@debian.org>  Tue, 27 Apr 2010 17:02:07 -0700
-
-notmuch (0.3) unstable; urgency=low
-
-  * User-configurable tags for new messages
-  * Threads search results named based on subjects that match search
-  * Faster operation of "notmuch tag" (avoid unneeded sorting)
-  * Even Better guessing of From: header for "notmuch reply"
-  * Indication of author names that match a search
-  * emacs: An entirely new initial view for notmuch, (friendly yet powerful)
-  * emacs: Full-featured "customize" support for configuring notmuch
-  * emacs: Support for doing tab-completion of email addresses
-  * emacs: Support for file-based (Fcc) delivery of sent messages
-  * emacs: New 'G' key binding to trigger mail refresh (G == "Get new mail")
-  * emacs: Implement emacs message display with the JSON output from notmuch
-  * emacs: Better handling of HTML/MIME attachments (inline images!)
-  * emacs: Customizable support for tidying of text/plain message content
-  * emacs: New support for searchable citations (even when hidden)
-  * emacs: More flexible handling of header visibility
-  * emacs: The Return key now toggles message visibility anywhere
-  * emacs: Customizable formatting of search results
-  * emacs: Generate nicer names for search buffers when using a saved search.
-  * emacs: Add a notmuch User-Agent header when sending mail from notmuch/emacs
-  * emacs: New keybinding (M-Ret) to open all collapsed messages in a thread
-  * libnotmuch1: Provide a new NOTMUCH_SORT_UNSORTED value for queries
-
- -- Carl Worth <cworth@debian.org>  Tue, 27 Apr 2010 02:07:29 -0700
-
-notmuch (0.2) unstable; urgency=low
-
-  * Better guessing of From: header.
-  * Make "notmuch count" with no arguments count all messages
-  * Provide a new special-case search term of "*" to match all messages.
-  * Detect thread connections when a parent message is missing.
-  * Fix potential data loss in "notmuch new" with SIGINT
-  * Fix segfault when a message includes a MIME part that is empty.
-  * Fix handling of non-ASCII characters with --format=json
-  * Fix headers to be properly decoded in "notmuch reply"
-  * emacs: Show the last few lines of citations as well as the first few lines.
-  * emacs: The '+' and '-' commands can now add and remove tags by region.
-  * emacs: More meaningful buffer names for thread-view buffers.
-  * emacs: Customized colors of threads in search view based on tags.
-
- -- Carl Worth <cworth@debian.org>  Fri, 16 Apr 2010 10:20:23 -0700
-
-notmuch (0.1-1) unstable; urgency=low
-
-  [ martin f. krafft ]
-  * Add suggestion to vim-addon-manager.
-
-  [ Carl Worth ]
-  * Improve package description (closes: #566282).
-  * New upstream version (0.1) (closes: #576647).
-  * New versioning to track upstream version scheme.
-  * Split packaging into notmuch, libnotmuch1, and libnotmuch-dev.
-  * Update to advertise conformance with policy 3.8.4 (no changes).
-  * Add a debian/watch file to notice upstream tar files.
-
- -- Carl Worth <cworth@debian.org>  Tue, 06 Apr 2010 18:27:49 -0700
-
-notmuch (0.0+201001211401) unstable; urgency=low
-
-  * Upload to Debian (closes: #557354).
-  * New versioning scheme.
-  * Added emacs build dependency.
-  * Added Vcs-Browser field to debian/control.
-  * Downgrade recommendation for emacs to suggestion.
-  * Add vim to suggestions and enhancements.
-  * Put debian/* under separate copyright.
-  * Make Carl the maintainer.
-  * Add myself to uploaders.
-  * Install the vim plugin (using vim-addons).
-
- -- martin f. krafft <madduck@debian.org>  Thu, 21 Jan 2010 14:00:54 +1300
-
-notmuch (0.0-1) unstable; urgency=low
-
-  * New Debian package.
-
- -- Jameson Graef Rollins <jrollins@finestructure.net>  Fri, 27 Nov 2009 13:39:09 -0500
diff --git a/command-line-arguments.c b/command-line-arguments.c
new file mode 100644 (file)
index 0000000..d64aa85
--- /dev/null
@@ -0,0 +1,304 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include "error_util.h"
+#include "command-line-arguments.h"
+
+typedef enum {
+    OPT_FAILED, /* false */
+    OPT_OK, /* good */
+    OPT_GIVEBACK, /* pop one of the arguments you thought you were getting off the stack */
+} opt_handled;
+
+/*
+  Search the array of keywords for a given argument, assigning the
+  output variable to the corresponding value.  Return false if nothing
+  matches.
+*/
+
+static opt_handled
+_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next,
+                     const char *arg_str, bool negate)
+{
+    const notmuch_keyword_t *keywords;
+
+    if (next == '\0') {
+       /* No keyword given */
+       arg_str = "";
+    }
+
+    for (keywords = arg_desc->keywords; keywords->name; keywords++) {
+       if (strcmp (arg_str, keywords->name) != 0)
+           continue;
+
+       if (arg_desc->opt_flags && negate)
+           *arg_desc->opt_flags &= ~keywords->value;
+       else if (arg_desc->opt_flags)
+           *arg_desc->opt_flags |= keywords->value;
+       else
+           *arg_desc->opt_keyword = keywords->value;
+
+       return OPT_OK;
+    }
+
+    if (arg_desc->opt_keyword && arg_desc->keyword_no_arg_value && next != ':' && next != '=') {
+       for (keywords = arg_desc->keywords; keywords->name; keywords++) {
+           if (strcmp (arg_desc->keyword_no_arg_value, keywords->name) != 0)
+               continue;
+
+           *arg_desc->opt_keyword = keywords->value;
+           fprintf (stderr, "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
+                    "  Please specify the argument explicitly!\n", arg_desc->name, arg_desc->keyword_no_arg_value);
+
+           return OPT_GIVEBACK;
+       }
+       fprintf (stderr, "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n", arg_str, arg_desc->name);
+       return OPT_FAILED;
+    }
+
+    if (next != '\0')
+       fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
+    else
+       fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
+    return OPT_FAILED;
+}
+
+static opt_handled
+_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next,
+                     const char *arg_str, bool negate)
+{
+    bool value;
+
+    if (next == '\0' || strcmp (arg_str, "true") == 0) {
+       value = true;
+    } else if (strcmp (arg_str, "false") == 0) {
+       value = false;
+    } else {
+       fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
+       return OPT_FAILED;
+    }
+
+    *arg_desc->opt_bool = negate ? !value : value;
+
+    return OPT_OK;
+}
+
+static opt_handled
+_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
+
+    char *endptr;
+    if (next == '\0' || arg_str[0] == '\0') {
+       fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
+       return OPT_FAILED;
+    }
+
+    *arg_desc->opt_int = strtol (arg_str, &endptr, 10);
+    if (*endptr == '\0')
+       return OPT_OK;
+
+    fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
+            arg_str, arg_desc->name);
+    return OPT_FAILED;
+}
+
+static opt_handled
+_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
+
+    if (next == '\0') {
+       fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
+       return OPT_FAILED;
+    }
+    if (arg_str[0] == '\0' && ! arg_desc->allow_empty) {
+       fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
+       return OPT_FAILED;
+    }
+    *arg_desc->opt_string = arg_str;
+    return OPT_OK;
+}
+
+/* Return number of non-NULL opt_* fields in opt_desc. */
+static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
+{
+    return
+       !!opt_desc->opt_inherit +
+       !!opt_desc->opt_bool +
+       !!opt_desc->opt_int +
+       !!opt_desc->opt_keyword +
+       !!opt_desc->opt_flags +
+       !!opt_desc->opt_string +
+       !!opt_desc->opt_position;
+}
+
+/* Return true if opt_desc is valid. */
+static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
+{
+    int n = _opt_set_count (opt_desc);
+
+    if (n > 1)
+       INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
+                       opt_desc->name);
+
+    return n > 0;
+}
+
+/*
+   Search for the {pos_arg_index}th position argument, return false if
+   that does not exist.
+*/
+
+bool
+parse_position_arg (const char *arg_str, int pos_arg_index,
+                   const notmuch_opt_desc_t *arg_desc) {
+
+    int pos_arg_counter = 0;
+    while (_opt_valid (arg_desc)) {
+       if (arg_desc->opt_position) {
+           if (pos_arg_counter == pos_arg_index) {
+               *arg_desc->opt_position = arg_str;
+               if (arg_desc->present)
+                   *arg_desc->present = true;
+               return true;
+           }
+           pos_arg_counter++;
+       }
+       arg_desc++;
+    }
+    return false;
+}
+
+#define NEGATIVE_PREFIX "no-"
+
+/*
+ * Search for a non-positional (i.e. starting with --) argument matching arg,
+ * parse a possible value, and assign to *output_var
+ */
+
+int
+parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
+{
+    assert(argv);
+
+    const char *_arg = argv[opt_index];
+
+    assert(_arg);
+    assert(options);
+
+    const char *arg = _arg + 2; /* _arg starts with -- */
+    const char *negative_arg = NULL;
+
+    /* See if this is a --no-argument */
+    if (strlen (arg) > strlen (NEGATIVE_PREFIX) &&
+       strncmp (arg, NEGATIVE_PREFIX, strlen (NEGATIVE_PREFIX)) == 0) {
+       negative_arg = arg + strlen (NEGATIVE_PREFIX);
+    }
+
+    const notmuch_opt_desc_t *try;
+
+    const char *next_arg = NULL;
+    if (opt_index < argc - 1  && strncmp (argv[opt_index + 1], "--", 2) != 0)
+       next_arg = argv[opt_index + 1];
+
+    for (try = options; _opt_valid (try); try++) {
+       if (try->opt_inherit) {
+           int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
+           if (new_index >= 0)
+               return new_index;
+       }
+
+       if (! try->name)
+           continue;
+
+       char next;
+       const char *value;
+       bool negate = false;
+
+       if (strncmp (arg, try->name, strlen (try->name)) == 0) {
+           next = arg[strlen (try->name)];
+           value = arg + strlen (try->name) + 1;
+       } else if (negative_arg && (try->opt_bool || try->opt_flags) &&
+                  strncmp (negative_arg, try->name, strlen (try->name)) == 0) {
+           next = negative_arg[strlen (try->name)];
+           value = negative_arg + strlen (try->name) + 1;
+           /* The argument part of --no-argument matches, negate the result. */
+           negate = true;
+       } else {
+           continue;
+       }
+
+       /*
+        * If we have not reached the end of the argument (i.e. the
+        * next character is not a space or delimiter) then the
+        * argument could still match a longer option name later in
+        * the option table.
+        */
+       if (next != '=' && next != ':' && next != '\0')
+           continue;
+
+       bool lookahead = (next == '\0' && next_arg != NULL && ! try->opt_bool);
+
+       if (lookahead) {
+           next = ' ';
+           value = next_arg;
+           opt_index ++;
+       }
+
+       opt_handled opt_status = OPT_FAILED;
+       if (try->opt_keyword || try->opt_flags)
+           opt_status = _process_keyword_arg (try, next, value, negate);
+       else if (try->opt_bool)
+           opt_status = _process_boolean_arg (try, next, value, negate);
+       else if (try->opt_int)
+           opt_status = _process_int_arg (try, next, value);
+       else if (try->opt_string)
+           opt_status = _process_string_arg (try, next, value);
+       else
+           INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
+
+       if (opt_status == OPT_FAILED)
+           return -1;
+
+       if (lookahead && opt_status == OPT_GIVEBACK)
+           opt_index --;
+
+       if (try->present)
+           *try->present = true;
+
+       return opt_index+1;
+    }
+    return -1;
+}
+
+/* See command-line-arguments.h for description */
+int
+parse_arguments (int argc, char **argv,
+                const notmuch_opt_desc_t *options, int opt_index) {
+
+    int pos_arg_index = 0;
+    bool more_args = true;
+
+    while (more_args && opt_index < argc) {
+       if (strncmp (argv[opt_index],"--",2) != 0) {
+
+           more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
+
+           if (more_args) {
+               pos_arg_index++;
+               opt_index++;
+           }
+
+       } else {
+           int prev_opt_index = opt_index;
+
+           if (strlen (argv[opt_index]) == 2)
+               return opt_index+1;
+
+           opt_index = parse_option (argc, argv, options, opt_index);
+           if (opt_index < 0) {
+               fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
+               more_args = false;
+           }
+       }
+    }
+
+    return opt_index;
+}
diff --git a/command-line-arguments.h b/command-line-arguments.h
new file mode 100644 (file)
index 0000000..f722f97
--- /dev/null
@@ -0,0 +1,82 @@
+#ifndef NOTMUCH_OPTS_H
+#define NOTMUCH_OPTS_H
+
+#include <stdbool.h>
+
+#include "notmuch.h"
+
+/*
+ * Describe one of the possibilities for a keyword option
+ * 'value' will be copied to the output variable
+ */
+
+typedef struct notmuch_keyword {
+    const char *name;
+    int value;
+} notmuch_keyword_t;
+
+/* Describe one option. */
+typedef struct notmuch_opt_desc {
+    /* One and only one of opt_* must be set. */
+    const struct notmuch_opt_desc *opt_inherit;
+    bool *opt_bool;
+    int *opt_int;
+    int *opt_keyword;
+    int *opt_flags;
+    const char **opt_string;
+    const char **opt_position;
+
+    /* for opt_keyword only: if no matching arguments were found, and
+     * keyword_no_arg_value is set, then use keyword_no_arg_value instead. */
+    const char *keyword_no_arg_value;
+
+    /* Must be set except for opt_inherit and opt_position. */
+    const char *name;
+
+    /* Optional, if non-NULL, set to true if the option is present. */
+    bool *present;
+
+    /* Optional, allow empty strings for opt_string. */
+    bool allow_empty;
+
+    /* Must be set for opt_keyword and opt_flags. */
+    const struct notmuch_keyword *keywords;
+} notmuch_opt_desc_t;
+
+
+/*
+  This is the main entry point for command line argument parsing.
+
+  Parse command line arguments according to structure options,
+  starting at position opt_index.
+
+  All output of parsed values is via pointers in options.
+
+  Parsing stops at -- (consumed) or at the (k+1)st argument
+  not starting with -- (a "positional argument") if options contains
+  k positional argument descriptors.
+
+  Returns the index of first non-parsed argument, or -1 in case of error.
+
+*/
+int
+parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index);
+
+/*
+ * If the argument parsing loop provided by parse_arguments is not
+ * flexible enough, then the user might be interested in the following
+ * routines, but note that the API to parse_option might have to
+ * change. See command-line-arguments.c for descriptions of these
+ * functions.
+ */
+
+int
+parse_option (int argc, char **argv, const notmuch_opt_desc_t* options, int opt_index);
+
+bool
+parse_position_arg (const char *arg,
+                   int position_arg_index,
+                   const notmuch_opt_desc_t* options);
+
+
+#endif
diff --git a/compat b/compat
deleted file mode 100644 (file)
index b4de394..0000000
--- a/compat
+++ /dev/null
@@ -1 +0,0 @@
-11
diff --git a/compat/.gitignore b/compat/.gitignore
new file mode 100644 (file)
index 0000000..7ede45e
--- /dev/null
@@ -0,0 +1 @@
+/zlib.pc
diff --git a/compat/Makefile b/compat/Makefile
new file mode 100644 (file)
index 0000000..fa25832
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/compat/Makefile.local b/compat/Makefile.local
new file mode 100644 (file)
index 0000000..bcb9f0e
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- makefile -*-
+
+dir := compat
+extra_cflags += -I$(srcdir)/$(dir)
+
+notmuch_compat_srcs :=
+
+ifneq ($(HAVE_CANONICALIZE_FILE_NAME),1)
+notmuch_compat_srcs += $(dir)/canonicalize_file_name.c
+endif
+
+ifneq ($(HAVE_GETLINE),1)
+notmuch_compat_srcs += $(dir)/getline.c $(dir)/getdelim.c
+endif
+
+ifneq ($(HAVE_STRCASESTR),1)
+notmuch_compat_srcs += $(dir)/strcasestr.c
+endif
+
+ifneq ($(HAVE_STRSEP),1)
+notmuch_compat_srcs += $(dir)/strsep.c
+endif
+
+ifneq ($(HAVE_TIMEGM),1)
+notmuch_compat_srcs += $(dir)/timegm.c
+endif
+
+SRCS := $(SRCS) $(notmuch_compat_srcs)
diff --git a/compat/README b/compat/README
new file mode 100644 (file)
index 0000000..12aacf4
--- /dev/null
@@ -0,0 +1,21 @@
+notmuch/compat
+
+This directory consists of three things:
+
+1. Small programs used by the notmuch configure script to test for the
+   availability of certain system features, (library functions, etc.).
+
+   For example: have_getline.c
+
+2. Compatibility implementations of those system features for systems
+   that don't provide their own versions.
+
+   For example: getline.c
+
+   The compilation of these files is made conditional on the output of
+   the test programs from [1].
+
+3. Macro definitions abstracting compiler differences (e.g. function
+   attributes).
+
+   For example: function-attributes.h
diff --git a/compat/canonicalize_file_name.c b/compat/canonicalize_file_name.c
new file mode 100644 (file)
index 0000000..e92c0f6
--- /dev/null
@@ -0,0 +1,18 @@
+#include "compat.h"
+#include <limits.h>
+#undef _GNU_SOURCE
+#include <stdlib.h>
+
+char *
+canonicalize_file_name (const char * path)
+{
+#ifdef PATH_MAX
+    char *resolved_path =  malloc (PATH_MAX+1);
+    if (resolved_path == NULL)
+       return NULL;
+
+    return realpath (path, resolved_path);
+#else
+#error undefined PATH_MAX _and_ missing canonicalize_file_name not supported
+#endif
+}
diff --git a/compat/check_asctime.c b/compat/check_asctime.c
new file mode 100644 (file)
index 0000000..b0e56f0
--- /dev/null
@@ -0,0 +1,11 @@
+#include <time.h>
+#include <stdio.h>
+
+int main()
+{
+    struct tm tm;
+
+    (void) asctime_r (&tm, NULL);
+
+    return (0);
+}
diff --git a/compat/check_getpwuid.c b/compat/check_getpwuid.c
new file mode 100644 (file)
index 0000000..c435eb8
--- /dev/null
@@ -0,0 +1,11 @@
+#include <stdio.h>
+#include <pwd.h>
+
+int main()
+{
+    struct passwd passwd, *ignored;
+
+    (void) getpwuid_r (0, &passwd, NULL, 0, &ignored);
+
+    return (0);
+}
diff --git a/compat/compat.h b/compat/compat.h
new file mode 100644 (file)
index 0000000..88bc4df
--- /dev/null
@@ -0,0 +1,85 @@
+/* notmuch - Not much of an email library, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+/* This header file defines functions that will only be conditionally
+ * compiled for compatibility on systems that don't provide their own
+ * implementations of the functions.
+ */
+
+#ifndef NOTMUCH_COMPAT_H
+#define NOTMUCH_COMPAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !STD_GETPWUID
+#define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#if !STD_ASCTIME
+#define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+
+#if !HAVE_CANONICALIZE_FILE_NAME
+/* we only call this function from C, and this makes testing easier */
+#ifndef __cplusplus
+char *
+canonicalize_file_name (const char *path);
+#endif
+#endif
+
+#if !HAVE_GETLINE
+#include <stdio.h>
+#include <unistd.h>
+
+ssize_t
+getline (char **lineptr, size_t *n, FILE *stream);
+
+ssize_t
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp);
+
+#endif /* !HAVE_GETLINE */
+
+#if !HAVE_STRCASESTR
+char* strcasestr(const char *haystack, const char *needle);
+#endif /* !HAVE_STRCASESTR */
+
+#if !HAVE_STRSEP
+char *strsep(char **stringp, const char *delim);
+#endif /* !HAVE_STRSEP */
+
+#if !HAVE_TIMEGM
+#include <time.h>
+time_t timegm (struct tm *tm);
+#endif /* !HAVE_TIMEGM */
+
+/* Silence gcc warnings about unused results.  These warnings exist
+ * for a reason; any use of this needs to be justified. */
+#ifdef __GNUC__
+#define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void)(__z = __z); })
+#else /* !__GNUC__ */
+#define IGNORE_RESULT(x) x
+#endif /* __GNUC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NOTMUCH_COMPAT_H */
diff --git a/compat/function-attributes.h b/compat/function-attributes.h
new file mode 100644 (file)
index 0000000..1945b5b
--- /dev/null
@@ -0,0 +1,47 @@
+/* function-attributes.h - Provides compiler abstractions for
+ *                         function attributes
+ *
+ * Copyright (c) 2012 Justus Winter <4winter@informatik.uni-hamburg.de>
+ *
+ * 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/ .
+ */
+
+#ifndef FUNCTION_ATTRIBUTES_H
+#define FUNCTION_ATTRIBUTES_H
+
+/* clang provides this macro to test for support for function
+ * attributes. If it isn't defined, this provides a compatibility
+ * macro for other compilers.
+ */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif
+
+/* Provide a NORETURN_ATTRIBUTE macro similar to PRINTF_ATTRIBUTE from
+ * talloc.
+ *
+ * This attribute is understood by gcc since version 2.5. clang
+ * provides support for testing for function attributes.
+ */
+#ifndef NORETURN_ATTRIBUTE
+#if (__GNUC__ >= 3 ||                          \
+     (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || \
+     __has_attribute (noreturn))
+#define NORETURN_ATTRIBUTE __attribute__ ((noreturn))
+#else
+#define NORETURN_ATTRIBUTE
+#endif
+#endif
+
+#endif
diff --git a/compat/gen_zlib_pc.c b/compat/gen_zlib_pc.c
new file mode 100644 (file)
index 0000000..198a727
--- /dev/null
@@ -0,0 +1,18 @@
+#include <stdio.h>
+#include <zlib.h>
+
+static const char *template =
+       "prefix=/usr\n"
+       "exec_prefix=${prefix}\n"
+       "libdir=${exec_prefix}/lib\n"
+       "\n"
+       "Name: zlib\n"
+       "Description: zlib compression library\n"
+       "Version: %s\n"
+       "Libs: -lz\n";
+
+int main(void)
+{
+       printf(template, ZLIB_VERSION);
+       return 0;
+}
diff --git a/compat/getdelim.c b/compat/getdelim.c
new file mode 100644 (file)
index 0000000..407f3d0
--- /dev/null
@@ -0,0 +1,133 @@
+/* getdelim.c --- Implementation of replacement getdelim function.
+   Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007,
+   2008, 2009 Free Software Foundation, Inc.
+
+   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, 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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Ported from glibc by Simon Josefsson. */
+
+#include "compat.h"
+
+#include <stdio.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+#if USE_UNLOCKED_IO
+# include "unlocked-io.h"
+# define getc_maybe_unlocked(fp)       getc(fp)
+#elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED
+# undef flockfile
+# undef funlockfile
+# define flockfile(x) ((void) 0)
+# define funlockfile(x) ((void) 0)
+# define getc_maybe_unlocked(fp)       getc(fp)
+#else
+# define getc_maybe_unlocked(fp)       getc_unlocked(fp)
+#endif
+
+/* Read up to (and including) a DELIMITER from FP into *LINEPTR (and
+   NUL-terminate it).  *LINEPTR is a pointer returned from malloc (or
+   NULL), pointing to *N characters of space.  It is realloc'ed as
+   necessary.  Returns the number of characters read (not including
+   the null terminator), or -1 on error or EOF.  */
+
+ssize_t
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp)
+{
+  ssize_t result = -1;
+  size_t cur_len = 0;
+
+  if (lineptr == NULL || n == NULL || fp == NULL)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  flockfile (fp);
+
+  if (*lineptr == NULL || *n == 0)
+    {
+      char *new_lineptr;
+      *n = 120;
+      new_lineptr = (char *) realloc (*lineptr, *n);
+      if (new_lineptr == NULL)
+       {
+         result = -1;
+         goto unlock_return;
+       }
+      *lineptr = new_lineptr;
+    }
+
+  for (;;)
+    {
+      int i;
+
+      i = getc_maybe_unlocked (fp);
+      if (i == EOF)
+       {
+         result = -1;
+         break;
+       }
+
+      /* Make enough space for len+1 (for final NUL) bytes.  */
+      if (cur_len + 1 >= *n)
+       {
+         size_t needed_max =
+           SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX;
+         size_t needed = 2 * *n + 1;   /* Be generous. */
+         char *new_lineptr;
+
+         if (needed_max < needed)
+           needed = needed_max;
+         if (cur_len + 1 >= needed)
+           {
+             result = -1;
+             errno = EOVERFLOW;
+             goto unlock_return;
+           }
+
+         new_lineptr = (char *) realloc (*lineptr, needed);
+         if (new_lineptr == NULL)
+           {
+             result = -1;
+             goto unlock_return;
+           }
+
+         *lineptr = new_lineptr;
+         *n = needed;
+       }
+
+      (*lineptr)[cur_len] = i;
+      cur_len++;
+
+      if (i == delimiter)
+       break;
+    }
+  (*lineptr)[cur_len] = '\0';
+  result = cur_len ? (ssize_t) cur_len : result;
+
+ unlock_return:
+  funlockfile (fp); /* doesn't set errno */
+
+  return result;
+}
diff --git a/compat/getline.c b/compat/getline.c
new file mode 100644 (file)
index 0000000..222e0f6
--- /dev/null
@@ -0,0 +1,29 @@
+/* getline.c --- Implementation of replacement getline function.
+   Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc.
+
+   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, 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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Written by Simon Josefsson. */
+
+#include "compat.h"
+
+#include <stdio.h>
+
+ssize_t
+getline (char **lineptr, size_t *n, FILE *stream)
+{
+  return getdelim (lineptr, n, '\n', stream);
+}
diff --git a/compat/have_canonicalize_file_name.c b/compat/have_canonicalize_file_name.c
new file mode 100644 (file)
index 0000000..24c848e
--- /dev/null
@@ -0,0 +1,10 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+
+int main()
+{
+    char *found;
+    char *string;
+
+    found = canonicalize_file_name (string);
+}
diff --git a/compat/have_d_type.c b/compat/have_d_type.c
new file mode 100644 (file)
index 0000000..9ca6c6e
--- /dev/null
@@ -0,0 +1,10 @@
+#include <dirent.h>
+
+int main()
+{
+    struct dirent ent;
+
+    (void) ent.d_type;
+
+    return 0;
+}
diff --git a/compat/have_getline.c b/compat/have_getline.c
new file mode 100644 (file)
index 0000000..a8bcd17
--- /dev/null
@@ -0,0 +1,13 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <sys/types.h>
+
+int main()
+{
+    ssize_t count = 0;
+    size_t n = 0;
+    char **lineptr = NULL;
+    FILE *stream = NULL;
+
+    count = getline(lineptr, &n, stream);
+}
diff --git a/compat/have_strcasestr.c b/compat/have_strcasestr.c
new file mode 100644 (file)
index 0000000..c0fb762
--- /dev/null
@@ -0,0 +1,10 @@
+#define _GNU_SOURCE
+#include <strings.h>
+
+int main()
+{
+    char *found;
+    const char *haystack, *needle;
+
+    found = strcasestr(haystack, needle);
+}
diff --git a/compat/have_strsep.c b/compat/have_strsep.c
new file mode 100644 (file)
index 0000000..2abab81
--- /dev/null
@@ -0,0 +1,11 @@
+#define _GNU_SOURCE
+#include <string.h>
+
+int main()
+{
+    char *found;
+    char **stringp;
+    const char *delim;
+
+    found = strsep(stringp, delim);
+}
diff --git a/compat/have_timegm.c b/compat/have_timegm.c
new file mode 100644 (file)
index 0000000..483fc3b
--- /dev/null
@@ -0,0 +1,6 @@
+#include <time.h>
+
+int main()
+{
+    return (int) timegm((struct tm *)0);
+}
diff --git a/compat/strcasestr.c b/compat/strcasestr.c
new file mode 100644 (file)
index 0000000..62a3a54
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * slow simplistic reimplementation of strcasestr for systems that
+ * don't include it in their library
+ *
+ * based on a GPL implementation in OpenTTD found under GPL v2
+
+   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, version 2.
+
+   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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Imported into notmuch by Dirk Hohndel - original author unknown. */
+
+#include <string.h>
+
+#include "compat.h"
+
+char *strcasestr(const char *haystack, const char *needle)
+{
+       size_t hay_len = strlen(haystack);
+       size_t needle_len = strlen(needle);
+       while (hay_len >= needle_len) {
+               if (strncasecmp(haystack, needle, needle_len) == 0)
+                   return (char *) haystack;
+
+               haystack++;
+               hay_len--;
+       }
+
+       return NULL;
+}
diff --git a/compat/strsep.c b/compat/strsep.c
new file mode 100644 (file)
index 0000000..78ab9e7
--- /dev/null
@@ -0,0 +1,65 @@
+/* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#include <string.h>
+
+/* Taken from glibc 2.6.1 */
+
+char *strsep (char **stringp, const char *delim)
+{
+  char *begin, *end;
+
+  begin = *stringp;
+  if (begin == NULL)
+    return NULL;
+
+  /* A frequent case is when the delimiter string contains only one
+     character.  Here we don't need to call the expensive `strpbrk'
+     function and instead work using `strchr'.  */
+  if (delim[0] == '\0' || delim[1] == '\0')
+    {
+      char ch = delim[0];
+
+      if (ch == '\0')
+       end = NULL;
+      else
+       {
+         if (*begin == ch)
+           end = begin;
+         else if (*begin == '\0')
+           end = NULL;
+         else
+           end = strchr (begin + 1, ch);
+       }
+    }
+  else
+    /* Find the end of the token.  */
+    end = strpbrk (begin, delim);
+
+  if (end)
+    {
+      /* Terminate the token and set *STRINGP past NUL character.  */
+      *end++ = '\0';
+      *stringp = end;
+    }
+  else
+    /* No more delimiters; this is the last token.  */
+    *stringp = NULL;
+
+  return begin;
+}
diff --git a/compat/timegm.c b/compat/timegm.c
new file mode 100644 (file)
index 0000000..3560c37
--- /dev/null
@@ -0,0 +1,56 @@
+/* timegm.c --- Implementation of replacement timegm function.
+
+   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, 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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.  */
+
+/* Copyright 2013 Blake Jones. */
+
+#include <time.h>
+#include "compat.h"
+
+static int
+leapyear (int year)
+{
+    return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0));
+}
+
+/*
+ * This is a simple implementation of timegm() which does what is needed
+ * by create_output() -- just turns the "struct tm" into a GMT time_t.
+ * It does not normalize any of the fields of the "struct tm", nor does
+ * it set tm_wday or tm_yday.
+ */
+time_t
+timegm (struct tm *tm)
+{
+    int        monthlen[2][12] = {
+       { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+       { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+    };
+    int        year, month, days;
+
+    days = 365 * (tm->tm_year - 70);
+    for (year = 70; year < tm->tm_year; year++) {
+       if (leapyear(1900 + year)) {
+           days++;
+       }
+    }
+    for (month = 0; month < tm->tm_mon; month++) {
+       days += monthlen[leapyear(1900 + year)][month];
+    }
+    days += tm->tm_mday - 1;
+
+    return ((((days * 24) + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec);
+}
diff --git a/completion/Makefile b/completion/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/completion/Makefile.local b/completion/Makefile.local
new file mode 100644 (file)
index 0000000..8e86c9d
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- makefile -*-
+
+dir := completion
+
+# The dir variable will be re-assigned to later, so we can't use it
+# directly in any shell commands. Instead we save its value in other,
+# private variables that we can use in the commands.
+bash_script := $(srcdir)/$(dir)/notmuch-completion.bash
+zsh_scripts := $(srcdir)/$(dir)/zsh/_notmuch $(srcdir)/$(dir)/zsh/_email-notmuch
+
+install: install-$(dir)
+
+install-$(dir):
+       @echo $@
+ifeq ($(WITH_BASH),1)
+       mkdir -p "$(DESTDIR)$(bash_completion_dir)"
+       install -m0644 $(bash_script) "$(DESTDIR)$(bash_completion_dir)/notmuch"
+endif
+ifeq ($(WITH_ZSH),1)
+       mkdir -p "$(DESTDIR)$(zsh_completion_dir)"
+       install -m0644 $(zsh_scripts) "$(DESTDIR)$(zsh_completion_dir)"
+endif
diff --git a/completion/README b/completion/README
new file mode 100644 (file)
index 0000000..900e1c9
--- /dev/null
@@ -0,0 +1,16 @@
+notmuch completion
+
+This directory contains support for various shells to automatically
+complete partially entered notmuch command lines.
+
+notmuch-completion.bash
+
+  Command-line completion for the bash shell. This depends on
+  bash-completion package [1] version 2.0, which depends on bash
+  version 3.2 or later.
+
+  [1] https://github.com/scop/bash-completion
+
+zsh
+
+  Command-line completions for the zsh shell.
diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash
new file mode 100644 (file)
index 0000000..1542569
--- /dev/null
@@ -0,0 +1,622 @@
+# bash completion for notmuch                              -*- shell-script -*-
+#
+# Copyright © 2013 Jani Nikula
+#
+# Based on the bash-completion package:
+# https://github.com/scop/bash-completion
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 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/ .
+#
+# Author: Jani Nikula <jani@nikula.org>
+#
+#
+# BUGS:
+#
+# Add space after an --option without parameter (e.g. reply --decrypt)
+# on completion.
+#
+
+_notmuch_shared_options="--help --uuid= --version"
+
+# $1: current input of the form prefix:partialinput, where prefix is
+# to or from.
+_notmuch_email()
+{
+    local output prefix cur
+
+    prefix="${1%%:*}"
+    cur="${1#*:}"
+
+    # Cut the input to be completed at punctuation because
+    # (apparently) Xapian does not support the trailing wildcard '*'
+    # operator for input with punctuation. We let compgen handle the
+    # extra filtering required.
+    cur="${cur%%[^a-zA-Z0-9]*}"
+
+    case "$prefix" in
+       # Note: It would be more accurate and less surprising to have
+       # output=recipients here for to: addresses, but as gathering
+       # the recipient addresses requires disk access for each
+       # matching message, this becomes prohibitively slow.
+       to|from) output=sender;;
+       *) return;;
+    esac
+
+    # Only emit plain, lower case, unique addresses.
+    notmuch address --output=$output $prefix:"${cur}*" | \
+       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
+    # handle search prefixes and tags with colons and equal signs
+    _init_completion -n := || return
+
+    case "${cur}" in
+       tag:*)
+           COMPREPLY=( $(compgen -P "tag:" -W "`notmuch search --output=tags \*`" -- ${cur##tag:}) )
+           ;;
+       to:*)
+           COMPREPLY=( $(compgen -P "to:" -W "`_notmuch_email ${cur}`" -- ${cur##to:}) )
+           ;;
+       from:*)
+           COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_email ${cur}`" -- ${cur##from:}) )
+           ;;
+       path:*)
+           local path=`notmuch config get database.path`
+           compopt -o nospace
+           COMPREPLY=( $(compgen -d "$path/${cur##path:}" | sed "s|^$path/||" ) )
+           ;;
+       folder:*)
+           local path=`notmuch config get database.path`
+           compopt -o nospace
+           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: query: property:"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "${search_terms}" -- ${cur}) )
+           ;;
+    esac
+    # handle search prefixes and tags with colons
+    __ltrim_colon_completions "${cur}"
+}
+
+_notmuch_compact()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --backup)
+           _filedir -d
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--backup= --quiet ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+    esac
+}
+
+_notmuch_config()
+{
+    local cur prev words cword split
+    _init_completion || return
+
+    case "${prev}" in
+       config)
+           COMPREPLY=( $(compgen -W "get set list" -- ${cur}) )
+           ;;
+       get|set)
+           COMPREPLY=( $(compgen -W "`notmuch config list | sed 's/=.*\$//'`" -- ${cur}) )
+           ;;
+       # these will also complete on config get, but we don't care
+       database.path)
+           _filedir -d
+           ;;
+       maildir.synchronize_flags)
+           COMPREPLY=( $(compgen -W "true false" -- ${cur}) )
+           ;;
+    esac
+}
+
+_notmuch_count()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --output)
+           COMPREPLY=( $( compgen -W "messages threads files" -- "${cur}" ) )
+           return
+           ;;
+       --exclude)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
+       --input)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--output= --exclude= --batch --input= --lastmod ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_dump()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --format)
+           COMPREPLY=( $( compgen -W "sup batch-tag" -- "${cur}" ) )
+           return
+           ;;
+       --output)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--gzip --format= --output= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_emacs_mua()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --to|--cc|--bcc)
+           COMPREPLY=( $(compgen -W "`_notmuch_email to:${cur}`" -- ${cur}) )
+           return
+           ;;
+       --body)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+        -*)
+           local options="--subject= --to= --cc= --bcc= --body= --no-window-system --client --auto-daemon --create-frame --print --help --hello"
+
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           COMPREPLY=( $(compgen -W "`_notmuch_email to:${cur}`" -- ${cur}) )
+           return
+           ;;
+    esac
+}
+
+_notmuch_insert()
+{
+    local cur prev words cword split
+    # handle tags with colons and equal signs
+    _init_completion -s -n := || return
+
+    $split &&
+    case "${prev}" in
+       --folder)
+           local path=`notmuch config get database.path`
+           compopt -o nospace
+           COMPREPLY=( $(compgen -d "$path/${cur}" | \
+               sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
+           return
+           ;;
+       --decrypt)
+           COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       --*)
+           local options="--create-folder --folder= --keep --no-hooks --decrypt= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           return
+           ;;
+       +*)
+           COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) )
+           ;;
+       -*)
+           COMPREPLY=( $(compgen -P "-" -W "`notmuch search --output=tags \*`" -- ${cur##-}) )
+           ;;
+    esac
+    # handle tags with colons
+    __ltrim_colon_completions "${cur}"
+}
+
+_notmuch_new()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --decrypt)
+           COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--no-hooks --decrypt= --quiet ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
+           ;;
+    esac
+}
+
+_notmuch_reply()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --format)
+           COMPREPLY=( $( compgen -W "default json sexp headers-only" -- "${cur}" ) )
+           return
+           ;;
+       --reply-to)
+           COMPREPLY=( $( compgen -W "all sender" -- "${cur}" ) )
+           return
+           ;;
+       --decrypt)
+           COMPREPLY=( $( compgen -W "true auto false" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--format= --format-version= --reply-to= --decrypt= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_restore()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --format)
+           COMPREPLY=( $( compgen -W "sup batch-tag auto" -- "${cur}" ) )
+           return
+           ;;
+       --input)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--format= --accumulate --input= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+    esac
+}
+
+_notmuch_search()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --format)
+           COMPREPLY=( $( compgen -W "json sexp text text0" -- "${cur}" ) )
+           return
+           ;;
+       --output)
+           COMPREPLY=( $( compgen -W "summary threads messages files tags" -- "${cur}" ) )
+           return
+           ;;
+       --sort)
+           COMPREPLY=( $( compgen -W "newest-first oldest-first" -- "${cur}" ) )
+           return
+           ;;
+       --exclude)
+           COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_reindex()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --decrypt)
+           COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--decrypt= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_address()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --format)
+           COMPREPLY=( $( compgen -W "json sexp text text0" -- "${cur}" ) )
+           return
+           ;;
+       --output)
+           COMPREPLY=( $( compgen -W "sender recipients count address" -- "${cur}" ) )
+           return
+           ;;
+       --sort)
+           COMPREPLY=( $( compgen -W "newest-first oldest-first" -- "${cur}" ) )
+           return
+           ;;
+       --exclude)
+           COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
+           return
+           ;;
+       --deduplicate)
+           COMPREPLY=( $( compgen -W "no mailbox address" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--format= --output= --sort= --exclude= --deduplicate= ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_show()
+{
+    local cur prev words cword split
+    _init_completion -s || return
+
+    $split &&
+    case "${prev}" in
+       --entire-thread)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
+       --format)
+           COMPREPLY=( $( compgen -W "text json sexp mbox raw" -- "${cur}" ) )
+           return
+           ;;
+       --exclude|--body)
+           COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+           return
+           ;;
+        --decrypt)
+           COMPREPLY=( $( compgen -W "true auto false stash" -- "${cur}" ) )
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       -*)
+           local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           ;;
+    esac
+}
+
+_notmuch_tag()
+{
+    local cur prev words cword split
+    # handle tags with colons and equal signs
+    _init_completion -s -n := || return
+
+    $split &&
+    case "${prev}" in
+       --input)
+           _filedir
+           return
+           ;;
+    esac
+
+    ! $split &&
+    case "${cur}" in
+       --*)
+           local options="--batch --input= --remove-all ${_notmuch_shared_options}"
+           compopt -o nospace
+           COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+           return
+           ;;
+       +*)
+           COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) )
+           ;;
+       -*)
+           COMPREPLY=( $(compgen -P "-" -W "`notmuch search --output=tags \*`" -- ${cur##-}) )
+           ;;
+       *)
+           _notmuch_search_terms
+           return
+           ;;
+    esac
+    # handle tags with colons
+    __ltrim_colon_completions "${cur}"
+}
+
+_notmuch()
+{
+    local _notmuch_commands="compact config count dump help insert new reply restore reindex search address setup show tag emacs-mua"
+    local arg cur prev words cword split
+
+    # require bash-completion with _init_completion
+    type -t _init_completion >/dev/null 2>&1 || return
+
+    _init_completion || return
+
+    COMPREPLY=()
+
+    # subcommand
+    _get_first_arg
+
+    # complete --help option like the subcommand
+    if [ -z "${arg}" -a "${prev}" = "--help" ]; then
+       arg="help"
+    fi
+
+    if [ -z "${arg}" ]; then
+       # top level completion
+       case "${cur}" in
+           -*)
+               # XXX: handle ${_notmuch_shared_options} and --config=
+               local options="--help --version"
+               COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
+               ;;
+           *)
+               COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) )
+               ;;
+       esac
+    elif [ "${arg}" = "help" ]; then
+       # handle help command specially due to _notmuch_commands usage
+       local help_topics="$_notmuch_commands hooks search-terms properties"
+       COMPREPLY=( $(compgen -W "${help_topics}" -- ${cur}) )
+    else
+       # complete using _notmuch_subcommand if one exist
+       local completion_func="_notmuch_${arg//-/_}"
+       declare -f $completion_func >/dev/null && $completion_func
+    fi
+} &&
+complete -F _notmuch notmuch
diff --git a/completion/zsh/_email-notmuch b/completion/zsh/_email-notmuch
new file mode 100644 (file)
index 0000000..1cd0d78
--- /dev/null
@@ -0,0 +1,9 @@
+#autoload
+
+local expl
+local -a notmuch_addr
+
+notmuch_addr=( ${(f)"$(notmuch address --deduplicate=address --output=address -- from:/$PREFIX/)"} )
+
+_description notmuch-addr expl 'email address (notmuch)'
+compadd "$expl[@]" -a notmuch_addr
diff --git a/completion/zsh/_notmuch b/completion/zsh/_notmuch
new file mode 100644 (file)
index 0000000..e920f10
--- /dev/null
@@ -0,0 +1,293 @@
+#compdef notmuch -p notmuch-*
+
+# ZSH completion for `notmuch`
+# Copyright © 2018 Vincent Breitmoser <look@my.amazin.horse>
+
+_notmuch_command() {
+  local -a notmuch_commands
+  notmuch_commands=(
+    'help:display documentation for a subcommand'
+    'setup:interactively configure notmuch'
+
+    'address:output addresses from matching messages'
+    'compact:compact the notmuch database'
+    'config:access notmuch configuration file'
+    'count:count messages matching the given search terms'
+    'dump:creates a plain-text dump of the tags of each message'
+    'insert:add a message to the maildir and notmuch database'
+    'new:incorporate new mail into the notmuch database'
+    'reindex:re-index a set of messages'
+    'reply:constructs a reply template for a set of messages'
+    'restore:restores the tags from the given file (see notmuch dump)'
+    'search:search for messages matching the given search terms'
+    'show:show messages matching the given search terms'
+    'tag:add/remove tags for all messages matching the search terms'
+  )
+
+  if ((CURRENT == 1)); then
+    _describe -t commands 'notmuch command' notmuch_commands
+  else
+      local curcontext="$curcontext"
+      cmd=$words[1]
+      if (( $+functions[_notmuch_$cmd] )); then
+        _notmuch_$cmd
+      else
+        _message -e "unknown command $cmd"
+      fi
+  fi
+}
+
+_notmuch_term_tag _notmuch_term_is () {
+  local ret=1 expl
+  local -a notmuch_tags
+
+  notmuch_tags=( ${(f)"$(notmuch search --output=tags '*')"} )
+
+  _description notmuch-tag expl 'tag'
+  compadd "$expl[@]" -a notmuch_tags && ret=0
+  return $ret
+}
+
+_notmuch_term_to _notmuch_term_from() {
+  _email_addresses -c
+}
+
+_notmuch_term_mimetype() {
+  local ret=1 expl
+  local -a commontypes
+  commontypes=(
+    'text/plain'
+    'text/html'
+    'application/pdf'
+  )
+  _description typical-mimetypes expl 'common types'
+  compadd "$expl[@]" -a commontypes && ret=0
+
+  _mime_types && ret=0
+  return $ret
+}
+
+_notmuch_term_path() {
+  local ret=1 expl
+  local maildir="$(notmuch config get database.path)"
+  [[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret }
+
+  _description notmuch-folder expl 'maildir folder'
+  _files "$expl[@]" -W $maildir -/ && ret=0
+  return $ret
+}
+
+_notmuch_term_folder() {
+  local ret=1 expl
+  local maildir="$(notmuch config get database.path)"
+  [[ -d $maildir ]] || { _message -e "database.path not found" ; return $ret }
+
+  _description notmuch-folder expl 'maildir folder'
+  local ignoredfolders=( '*/(cur|new|tmp)' )
+  _files "$expl[@]" -W $maildir -F ignoredfolders -/ && ret=0
+  return $ret
+}
+
+_notmuch_term_query() {
+  local ret=1
+  local line query_name
+  local -a query_names query_content
+  for line in ${(f)"$(notmuch config list | grep '^query.')"}; do
+    query_name=${${line%%=*}#query.}
+    query_names+=( $query_name )
+    query_content+=( "$query_name = ${line#*=}" )
+  done
+
+  _description notmuch-named-query expl 'named query'
+  compadd "$expl[@]" -d query_content -a query_names && ret=0
+  return $ret
+}
+
+_notmuch_search_term() {
+  local ret=1 expl match
+  setopt localoptions extendedglob
+
+  typeset -a notmuch_search_terms
+  notmuch_search_terms=(
+    'from' 'to' 'subject' 'attachment' 'mimetype' 'tag' 'id' 'thread' 'path' 'folder' 'date' 'lastmod' 'query' 'property'
+  )
+
+  if compset -P '(#b)([^:]#):'; then
+    if (( $+functions[_notmuch_term_$match[1]] )); then
+      _notmuch_term_$match[1] && ret=0
+      return $ret
+    elif (( $+notmuch_search_terms[(r)$match[1]] )); then
+      _message "search term '$match[1]'" && ret=0
+      return $ret
+    else
+      _message -e "unknown search term '$match[1]'"
+      return $ret
+    fi
+  fi
+
+  _description notmuch-term expl 'search term'
+  compadd "$expl[@]" -S ':' -a notmuch_search_terms && ret=0
+
+  if [[ $CURRENT -gt 1 && $words[CURRENT-1] != '--' ]]; then
+    _description notmuch-op expl 'boolean operator'
+    compadd "$expl[@]" -- and or not xor && ret=0
+  fi
+
+  return $ret
+}
+
+_notmuch_tagging_or_search() {
+  setopt localoptions extendedglob
+  local ret=1 expl
+  local -a notmuch_tags
+
+  # first arg that is a search term, or $#words+1
+  integer searchtermarg=$(( $words[(I)--] != 0 ? $words[(i)--] : $words[(i)^(-|+)*] ))
+
+  if (( CURRENT > 1 )); then
+    () {
+      local -a words=( $argv )
+      local CURRENT=$(( CURRENT - searchtermarg + 1 ))
+      _notmuch_search_term && ret=0
+    } $words[searchtermarg,$]
+  fi
+
+  # only complete +/- tags if we're before the first search term
+  if (( searchtermarg >= CURRENT )); then
+    if compset -P '+'; then
+      notmuch_tags=( ${(f)"$(notmuch search --output=tags '*')"} )
+      _description notmuch-tag expl 'add tag'
+      compadd "$expl[@]" -a notmuch_tags
+      return 0
+    elif compset -P '-'; then
+      notmuch_tags=( ${(f)"$(notmuch search --output=tags '*')"} )
+      _description notmuch-tag expl 'remove tag'
+      compadd "$expl[@]" -a notmuch_tags
+      return 0
+    else
+      _description notmuch-tag expl 'add or remove tags'
+      compadd "$expl[@]" -S '' -- '+' '-' && ret=0
+    fi
+  fi
+
+  return $ret
+}
+
+_notmuch_address() {
+  _arguments -S \
+    '--format=[set output format]:output format:(json sexp text text0)' \
+    '--format-version=[set output format version]:format version: ' \
+    '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
+    '--output=[select output format]:output format:(sender recipients count address)' \
+    '--deduplicate=[deduplicate results]:deduplication mode:(no mailbox address)' \
+    '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_compact() {
+  _arguments \
+    '--backup=[save a backup before compacting]:backup directory:_files -/' \
+    '--quiet[do not print progress or results]'
+}
+
+_notmuch_count() {
+  _arguments \
+     - normal \
+        '--lastmod[append lastmod and uuid to output]' \
+        '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
+        '--output=[select what to count]:output format:(messages threads files)' \
+        '*::search term:_notmuch_search_term' \
+    - batch \
+      '--batch[operate in batch mode]' \
+      '(--batch)--input=[read batch operations from file]:batch file:_files'
+}
+
+_notmuch_dump() {
+  _arguments -S \
+    '--gzip[compress output with gzip]' \
+    '--format=[specify output format]:output format:(batch-tag sup)' \
+    '*--include=[configure metadata to output (default all)]:metadata type:(config properties tags)' \
+    '--output=[write output to file]:output file:_files' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_new() {
+  _arguments \
+    '--no-hooks[prevent hooks from being run]' \
+    '--quiet[do not print progress or results]' \
+    '--full-scan[don''t rely on directory modification times for scan]' \
+    '--decrypt=[decrypt messages]:decryption setting:((false\:"never decrypt" auto\:"decrypt if session key is known (default)" true\:"decrypt using secret keys" stash\:"decrypt, and store session keys"))'
+}
+
+_notmuch_reindex() {
+  _arguments \
+    '--decrypt=[decrypt messages]:decryption setting:((false\:"never decrypt" auto\:"decrypt if session key is known (default)" true\:"decrypt using secret keys" stash\:"decrypt, and store session keys"))' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_search() {
+  _arguments -S \
+    '--max-threads=[display only the first x threads from the search results]:number of threads to show: ' \
+    '--first=[omit the first x threads from the search results]:number of threads to omit: ' \
+    '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
+    '--output=[select what to output]:output:(summary threads messages files tags)' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_show() {
+  _arguments -S \
+    '--entire-thread=[output entire threads]:show thread:(true false)' \
+    '--format=[set output format]:output format:(text json sexp mbox raw)' \
+    '--format-version=[set output format version]:format version: ' \
+    '--part=[output a single decoded mime part]:part number: ' \
+    '--verify[verify signed MIME parts]' \
+    '--decrypt=[decrypt messages]:decryption setting:((false\:"never decrypt" auto\:"decrypt if session key is known (default)" true\:"decrypt using secret keys" stash\:"decrypt, and store session keys"))' \
+    '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
+    '--body=[output body]:output body content:(true false)' \
+    '--include-html[include text/html parts in the output]' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_reply() {
+  _arguments \
+    '--format=[set output format]:output format:(default json sexp headers-only)' \
+    '--format-version=[set output format version]:output format version: ' \
+    '--reply-to=[specify recipient types]:recipient types:(all sender)' \
+    '--decrypt=[decrypt messages]:decryption setting:((false\:"never decrypt" auto\:"decrypt if session key is known (default)" true\:"decrypt using secret keys"))' \
+    '*::search term:_notmuch_search_term'
+}
+
+_notmuch_restore() {
+  _arguments \
+    '--acumulate[add data to db instead of replacing]' \
+    '--format=[specify input format]:input format:(auto batch-tag sup)' \
+    '*--include=[configure metadata to import (default all)]:metadata type:(config properties tags)' \
+    '--input=[read from file]:notmuch dump file:_files'
+}
+
+_notmuch_tag() {
+  _arguments \
+    - normal \
+      '--remove-all[remove all tags from matching messages]:*:search term:_notmuch_search_term' \
+      '*::tag or search:_notmuch_tagging_or_search' \
+    - batch \
+      '--batch[operate in batch mode]' \
+      '(--batch)--input=[read batch operations from file]:batch file:_files'
+}
+
+_notmuch() {
+  if [[ $service == notmuch-* ]]; then
+    local compfunc=_${service//-/_}
+    (( $+functions[$compfunc] )) || return 1
+    $compfunc "$@"
+  else
+    _arguments \
+      '(* -)--help[show help]' \
+      '(* -)--version[show version]' \
+      '--config=-[specify config file]:config file:_files' \
+      '--uuid=-[check against database uuid or exit]:uuid: ' \
+      '*::notmuch commands:_notmuch_command'
+  fi
+}
+
+_notmuch "$@"
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..ea22587
--- /dev/null
+++ b/configure
@@ -0,0 +1,1329 @@
+#! /bin/sh
+
+set -u
+
+# Test whether this shell is capable of parameter substring processing.
+( option='a/b'; : ${option#*/} ) 2>/dev/null || {
+    echo "
+The shell interpreting '$0' is lacking some required features.
+
+To work around this problem you may try to execute:
+
+    ksh $0 $*
+ or
+    bash $0 $*
+"
+    exit 1
+}
+
+# Store original IFS value so it can be changed (and restored) in many places.
+readonly DEFAULT_IFS="$IFS"
+
+# The top-level directory for the source. This ./configure and all Makefiles
+# are good with ${srcdir} usually being relative. Some components (e.g. tests)
+# are executed in subdirectories and for those it is simpler to use
+# ${NOTMUCH_SRCDIR} which holds absolute path to the source.
+srcdir=$(dirname "$0")
+NOTMUCH_SRCDIR=$(cd "$srcdir" && pwd)
+
+subdirs="util compat lib parse-time-string completion doc emacs"
+subdirs="${subdirs} performance-test test test/test-databases"
+subdirs="${subdirs} bindings"
+
+# For a non-srcdir configure invocation (such as ../configure), create
+# the directory structure and copy Makefiles.
+if [ "$srcdir" != "." ]; then
+
+    for dir in . ${subdirs}; do
+       mkdir -p "$dir"
+       cp "$srcdir"/"$dir"/Makefile.local "$dir"
+       cp "$srcdir"/"$dir"/Makefile "$dir"
+    done
+
+    # Emacs only likes to generate compiled files next to the .el files
+    # by default so copy these as well (which is not ideal).
+    cp -a "$srcdir"/emacs/*.el emacs
+
+    # We were not able to create fully working Makefile using ruby mkmf.rb
+    # so ruby bindings source files are copied as well (ditto -- not ideal).
+    mkdir bindings/ruby
+    cp -a "$srcdir"/bindings/ruby/*.[ch] bindings/ruby
+    cp -a "$srcdir"/bindings/ruby/extconf.rb bindings/ruby
+fi
+
+# Set several defaults (optionally specified by the user in
+# environment variables)
+BASHCMD=${BASHCMD:-bash}
+PERL=${PERL:-perl}
+CC=${CC:-cc}
+CXX=${CXX:-c++}
+CFLAGS=${CFLAGS:--g -O2}
+CPPFLAGS=${CPPFLAGS:-}
+CXXFLAGS_for_sh=${CXXFLAGS:-${CFLAGS}}
+CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)}
+LDFLAGS=${LDFLAGS:-}
+XAPIAN_CONFIG=${XAPIAN_CONFIG:-}
+PYTHON=${PYTHON:-}
+RUBY=${RUBY:-ruby}
+
+# We don't allow the EMACS or GZIP Makefile variables inherit values
+# from the environment as we do with CC and CXX above. The reason is
+# that these names as environment variables have existing uses other
+# than the program name that we want. (EMACS is set to 't' when a
+# shell is running within emacs and GZIP specifies arguments to pass
+# on the gzip command line).
+
+# Set the defaults for values the user can specify with command-line
+# options.
+PREFIX=/usr/local
+LIBDIR=
+WITH_DOCS=1
+WITH_API_DOCS=1
+WITH_EMACS=1
+WITH_DESKTOP=1
+WITH_BASH=1
+WITH_RUBY=1
+WITH_ZSH=1
+WITH_RETRY_LOCK=1
+
+usage ()
+{
+    cat <<EOF
+Usage: ./configure [options]...
+
+This script configures notmuch to build on your system.
+
+It verifies that dependencies are available, determines flags needed
+to compile and link against various required libraries, and identifies
+whether various system functions can be used or if locally-provided
+replacements will be built instead.
+
+Finally, it allows you to control various aspects of the build and
+installation process.
+
+First, some common variables can specified via environment variables:
+
+       CC              The C compiler to use
+       CFLAGS          Flags to pass to the C compiler
+       CPPFLAGS        Flags to pass to the C preprocessor
+       CXX             The C++ compiler to use
+       CXXFLAGS        Flags to pass to the C compiler
+       LDFLAGS         Flags to pass when linking
+
+Each of these values can further be controlled by specifying them
+later on the "make" command line.
+
+Other environment variables can be used to control configure itself,
+(and for which there is no equivalent build-time control):
+
+       XAPIAN_CONFIG   The program to use to determine flags for
+                       compiling and linking against the Xapian
+                       library. [$XAPIAN_CONFIG]
+       PYTHON          Name of python command to use in
+                       configure and the test suite.
+       RUBY            Name of ruby command to use in
+                       configure and the test suite.
+
+Additionally, various options can be specified on the configure
+command line.
+
+       --prefix=PREFIX Install files in PREFIX [$PREFIX]
+
+By default, "make install" will install the resulting program to
+$PREFIX/bin, documentation to $PREFIX/man, etc. You can
+specify an installation prefix other than $PREFIX using
+--prefix, for instance:
+
+       ./configure --prefix=\$HOME
+
+Fine tuning of some installation directories is available:
+
+       --libdir=DIR            Install libraries to DIR [PREFIX/lib]
+       --includedir=DIR        Install header files to DIR [PREFIX/include]
+       --mandir=DIR            Install man pages to DIR [PREFIX/share/man]
+       --infodir=DIR           Install man pages to DIR [PREFIX/share/man]
+       --sysconfdir=DIR        Read-only single-machine data [PREFIX/etc]
+       --emacslispdir=DIR      Emacs code [PREFIX/share/emacs/site-lisp]
+       --emacsetcdir=DIR       Emacs miscellaneous files [PREFIX/share/emacs/site-lisp]
+       --bashcompletiondir=DIR Bash completions files [PREFIX/share/bash-completion/completions]
+       --zshcompletiondir=DIR  Zsh completions files [PREFIX/share/zsh/functions/Completion/Unix]
+
+Some features can be disabled (--with-feature=no is equivalent to
+--without-feature) :
+
+       --without-bash-completion       Do not install bash completions files
+       --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
+
+Additional options are accepted for compatibility with other
+configure-script calling conventions, but don't do anything yet:
+
+       --build=<cpu>-<vendor>-<os>     Currently ignored
+       --host=<cpu>-<vendor>-<os>      Currently ignored
+       --datadir=DIR                   Currently ignored
+       --localstatedir=DIR             Currently ignored
+       --libexecdir=DIR                Currently ignored
+       --disable-maintainer-mode       Currently ignored
+       --disable-dependency-tracking   Currently ignored
+
+EOF
+}
+
+# Parse command-line options
+for option; do
+    if [ "${option}" = '--help' ] ; then
+       usage
+       exit 0
+    elif [ "${option%%=*}" = '--prefix' ] ; then
+       PREFIX="${option#*=}"
+    elif [ "${option%%=*}" = '--libdir' ] ; then
+       LIBDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--includedir' ] ; then
+       INCLUDEDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--mandir' ] ; then
+       MANDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--infodir' ] ; then
+       INFODIR="${option#*=}"
+    elif [ "${option%%=*}" = '--sysconfdir' ] ; then
+       SYSCONFDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--emacslispdir' ] ; then
+       EMACSLISPDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--emacsetcdir' ] ; then
+       EMACSETCDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--bashcompletiondir' ] ; then
+       BASHCOMPLETIONDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--zshcompletiondir' ] ; then
+       ZSHCOMLETIONDIR="${option#*=}"
+    elif [ "${option%%=*}" = '--with-docs' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_DOCS=0
+           WITH_API_DOCS=0
+       else
+           WITH_DOCS=1
+       fi
+    elif [ "${option}" = '--without-docs' ] ; then
+       WITH_DOCS=0
+       WITH_API_DOCS=0
+    elif [ "${option%%=*}" = '--with-api-docs' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_API_DOCS=0
+       else
+           WITH_API_DOCS=1
+       fi
+    elif [ "${option}" = '--without-api-docs' ] ; then
+       WITH_API_DOCS=0
+    elif [ "${option%%=*}" = '--with-emacs' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_EMACS=0
+       else
+           WITH_EMACS=1
+       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
+       else
+           WITH_BASH=1
+       fi
+    elif [ "${option}" = '--without-bash-completion' ] ; then
+       WITH_BASH=0
+    elif [ "${option%%=*}" = '--with-ruby' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_RUBY=0
+       else
+           WITH_RUBY=1
+       fi
+    elif [ "${option}" = '--without-ruby' ] ; then
+       WITH_RUBY=0
+    elif [ "${option%%=*}" = '--with-retry-lock' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_RETRY_LOCK=0
+       else
+           WITH_RETRY_LOCK=1
+       fi
+    elif [ "${option}" = '--without-retry-lock' ] ; then
+       WITH_RETRY_LOCK=0
+    elif [ "${option%%=*}" = '--with-zsh-completion' ]; then
+       if [ "${option#*=}" = 'no' ]; then
+           WITH_ZSH=0
+       else
+           WITH_ZSH=1
+       fi
+    elif [ "${option}" = '--without-zsh-completion' ] ; then
+       WITH_ZSH=0
+    elif [ "${option%%=*}" = '--build' ] ; then
+       true
+    elif [ "${option%%=*}" = '--host' ] ; then
+       true
+    elif [ "${option%%=*}" = '--datadir' ] ; then
+       true
+    elif [ "${option%%=*}" = '--localstatedir' ] ; then
+       true
+    elif [ "${option%%=*}" = '--libexecdir' ] ; then
+       true
+    elif [ "${option}" = '--disable-maintainer-mode' ] ; then
+       true
+    elif [ "${option}" = '--disable-dependency-tracking' ] ; then
+       true
+    else
+       echo "Unrecognized option: ${option}"
+       echo "See:"
+       echo "  $0 --help"
+       echo ""
+       exit 1
+    fi
+done
+
+# We set this value early, (rather than just while printing the
+# Makefile.config file later like most values), because we need to
+# actually investigate this value compared to the ldconfig_paths value
+# below.
+if [ -z "$LIBDIR" ] ; then
+    libdir_expanded="${PREFIX}/lib"
+else
+    # very non-general variable expansion
+    libdir_expanded=$(printf %s "$LIBDIR" | sed "s|\${prefix}|${PREFIX}|; s|\$prefix\>|${PREFIX}|; s|//*|/|g")
+fi
+
+cat <<EOF
+Welcome to Notmuch, a system for indexing, searching and tagging your email.
+
+We hope that the process of building and installing notmuch is quick
+and smooth so that you can soon be reading and processing your email
+more efficiently than ever.
+
+If anything goes wrong in the configure process, you can override any
+decisions it makes by manually editing the Makefile.config file that
+it creates. Also please do as much as you can to figure out what could
+be different on your machine compared to those of the notmuch
+developers. Then, please email those details to the Notmuch list
+(notmuch@notmuchmail.org) so that we can hopefully make future
+versions of notmuch easier for you to use.
+
+We'll now investigate your system to verify that all required
+dependencies are available:
+
+EOF
+
+errors=0
+printf "int main(void){return 0;}\n" > minimal.c
+
+printf "Sanity checking C compilation environment... "
+test_cmdline="${CC} ${CFLAGS} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal"
+if  ${test_cmdline} > /dev/null 2>&1
+then
+    printf "OK.\n"
+else
+    printf "Fail.\n"
+    errors=$((errors + 1))
+    printf Executed:; printf ' %s' ${test_cmdline}; echo
+    ${test_cmdline}
+fi
+
+printf "Sanity checking C++ compilation environment... "
+test_cmdline="${CXX} ${CXXFLAGS_for_sh} ${CPPFLAGS} minimal.c ${LDFLAGS} -o minimal"
+if ${test_cmdline} > /dev/null 2>&1
+then
+    printf "OK.\n"
+else
+    printf "Fail.\n"
+    errors=$((errors + 1))
+    printf Executed:; printf ' %s' ${test_cmdline}; echo
+    ${test_cmdline}
+fi
+unset test_cmdline
+
+if [ $errors -gt 0 ]; then
+    cat <<EOF
+*** Error: Initial sanity checking of environment failed.  Please try
+running configure in a clean environment, and if the problem persists,
+report a bug.
+EOF
+    rm -f minimal minimal.c
+    exit 1
+fi
+
+printf "Reading libnotmuch version from source... "
+cat > _libversion.c <<EOF
+#include <stdio.h>
+#include "lib/notmuch.h"
+int main(void) {
+    printf("libnotmuch_version_major=%d\n",
+               LIBNOTMUCH_MAJOR_VERSION);
+    printf("libnotmuch_version_minor=%d\n",
+               LIBNOTMUCH_MINOR_VERSION);
+    printf("libnotmuch_version_release=%d\n",
+               LIBNOTMUCH_MICRO_VERSION);
+    return 0;
+}
+EOF
+if ${CC} ${CFLAGS} -I"$srcdir" _libversion.c -o _libversion > /dev/null 2>&1 \
+       && ./_libversion > _libversion.sh && . ./_libversion.sh
+then
+    printf "OK.\n"
+else
+    cat <<EOF
+
+*** Error: Reading lib/notmuch.h failed.
+Please try running configure again in a clean environment, and if the
+problem persists, report a bug.
+EOF
+    rm -f _libversion _libversion.c _libversion.sh
+    exit 1
+fi
+
+if pkg-config --version > /dev/null 2>&1; then
+    have_pkg_config=1
+else
+    have_pkg_config=0
+fi
+
+printf "Checking for Xapian development files... "
+have_xapian=0
+for xapian_config in ${XAPIAN_CONFIG} xapian-config xapian-config-1.3; do
+    if ${xapian_config} --version > /dev/null 2>&1; then
+       xapian_version=$(${xapian_config} --version | sed -e 's/.* //')
+       printf "Yes (%s).\n" ${xapian_version}
+       have_xapian=1
+       xapian_cxxflags=$(${xapian_config} --cxxflags)
+       xapian_ldflags=$(${xapian_config} --libs)
+       break
+    fi
+done
+if [ ${have_xapian} = "0" ]; then
+    printf "No.\n"
+    errors=$((errors + 1))
+fi
+
+have_xapian_compact=0
+have_xapian_field_processor=0
+if [ ${have_xapian} = "1" ]; then
+    printf "Checking for Xapian compaction support... "
+    cat>_compact.cc<<EOF
+#include <xapian.h>
+class TestCompactor : public Xapian::Compactor { };
+EOF
+    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _compact.cc -o _compact.o > /dev/null 2>&1
+    then
+       have_xapian_compact=1
+       printf "Yes.\n"
+    else
+       printf "No.\n"
+       errors=$((errors + 1))
+    fi
+
+    rm -f _compact.o _compact.cc
+
+    printf "Checking for Xapian FieldProcessor API... "
+    cat>_field_processor.cc<<EOF
+#include <xapian.h>
+class TitleFieldProcessor : public Xapian::FieldProcessor { };
+EOF
+    if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _field_processor.cc -o _field_processor.o > /dev/null 2>&1
+    then
+       have_xapian_field_processor=1
+       printf "Yes.\n"
+    else
+       printf "No. (optional)\n"
+    fi
+
+    rm -f _field_processor.o _field_processor.cc
+
+    default_xapian_backend=""
+    # DB_RETRY_LOCK is only supported on Xapian > 1.3.2
+    have_xapian_db_retry_lock=0
+    if [ $WITH_RETRY_LOCK = "1" ]; then
+       printf "Checking for Xapian lock retry support... "
+       cat>_retry.cc<<EOF
+#include <xapian.h>
+int flag = Xapian::DB_RETRY_LOCK;
+EOF
+       if ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} -c _retry.cc -o _retry.o > /dev/null 2>&1
+       then
+           have_xapian_db_retry_lock=1
+           printf "Yes.\n"
+       else
+           printf "No. (optional)\n"
+       fi
+       rm -f _retry.o _retry.cc
+    fi
+
+    printf "Testing default Xapian backend... "
+    cat >_default_backend.cc <<EOF
+#include <xapian.h>
+int main(int argc, char** argv) {
+   Xapian::WritableDatabase db("test.db",Xapian::DB_CREATE_OR_OPEN);
+}
+EOF
+    ${CXX} ${CXXFLAGS_for_sh} ${xapian_cxxflags} _default_backend.cc -o _default_backend ${xapian_ldflags}
+    ./_default_backend
+    if [ -f test.db/iamglass ]; then
+       default_xapian_backend=glass
+    else
+       default_xapian_backend=chert
+    fi
+    printf "%s\n" "${default_xapian_backend}";
+    rm -rf test.db _default_backend _default_backend.cc
+fi
+
+# we need to have a version >= 2.6.5 to avoid a crypto bug. We need
+# 2.6.7 for permissive "From " header handling.
+GMIME_MINVER=2.6.7
+GMIME3_MINVER=3.0.3
+
+printf "Checking for GMime development files... "
+if pkg-config --exists "gmime-3.0 > $GMIME3_MINVER"; then
+    printf "Yes (3.0).\n"
+    have_gmime=1
+    gmime_cflags=$(pkg-config --cflags gmime-3.0)
+    gmime_ldflags=$(pkg-config --libs gmime-3.0)
+    gmime_major=3
+    have_gmime_session_keys=1
+elif pkg-config --exists "gmime-2.6 >= $GMIME_MINVER"; then
+    printf "Yes (2.6).\n"
+    have_gmime=1
+    gmime_cflags=$(pkg-config --cflags gmime-2.6)
+    gmime_ldflags=$(pkg-config --libs gmime-2.6)
+    gmime_major=2
+    if pkg-config --exists "gmime-2.6 >= 2.6.21"; then
+        have_gmime_session_keys=1
+    else
+        have_gmime_session_keys=0
+    fi
+else
+    have_gmime=0
+    have_gmime_session_keys=0
+    printf "No.\n"
+    errors=$((errors + 1))
+fi
+
+# GMime already depends on Glib >= 2.12, but we use at least one Glib
+# function that only exists as of 2.22, (g_array_unref)
+printf "Checking for Glib development files (>= 2.22)... "
+have_glib=0
+if pkg-config --exists 'glib-2.0 >= 2.22'; then
+    printf "Yes.\n"
+    have_glib=1
+    # these are included in gmime cflags and ldflags
+    # glib_cflags=$(pkg-config --cflags glib-2.0)
+    # glib_ldflags=$(pkg-config --libs glib-2.0)
+else
+    printf "No.\n"
+    errors=$((errors + 1))
+fi
+
+if ! pkg-config --exists zlib; then
+  ${CC} -o compat/gen_zlib_pc "$srcdir"/compat/gen_zlib_pc.c >/dev/null 2>&1 &&
+  compat/gen_zlib_pc > compat/zlib.pc &&
+  PKG_CONFIG_PATH="$PKG_CONFIG_PATH":compat &&
+  export PKG_CONFIG_PATH
+  rm -f compat/gen_zlib_pc
+fi
+
+printf "Checking for zlib (>= 1.2.5.2)... "
+have_zlib=0
+if pkg-config --atleast-version=1.2.5.2 zlib; then
+    printf "Yes.\n"
+    have_zlib=1
+    zlib_cflags=$(pkg-config --cflags zlib)
+    zlib_ldflags=$(pkg-config --libs zlib)
+else
+    printf "No.\n"
+    errors=$((errors + 1))
+fi
+
+printf "Checking for talloc development files... "
+if pkg-config --exists talloc; then
+    printf "Yes.\n"
+    have_talloc=1
+    talloc_cflags=$(pkg-config --cflags talloc)
+    talloc_ldflags=$(pkg-config --libs talloc)
+else
+    printf "No.\n"
+    have_talloc=0
+    talloc_cflags=
+    errors=$((errors + 1))
+fi
+
+printf "Checking for bash... "
+if command -v ${BASHCMD} > /dev/null; then
+    have_bash=1
+    bash_absolute=$(command -v ${BASHCMD})
+    printf "Yes (%s).\n" "$bash_absolute"
+else
+    have_bash=0
+    printf "No. (%s not found)\n" "${BASHCMD}"
+fi
+
+printf "Checking for perl... "
+if command -v ${PERL} > /dev/null; then
+    have_perl=1
+    perl_absolute=$(command -v ${PERL})
+    printf "Yes (%s).\n" "$perl_absolute"
+else
+    have_perl=0
+    printf "No. (%s not found)\n" "${PERL}"
+fi
+
+printf "Checking for python... "
+have_python=0
+
+for name in ${PYTHON} python3 python python2; do
+    if command -v $name > /dev/null; then
+       have_python=1
+       python=$name
+       printf "Yes (%s).\n" "$name"
+       break
+    fi
+done
+
+if [ $have_python -eq 0 ]; then
+    printf "No.\n"
+    errors=$((errors + 1))
+fi
+
+printf "Checking for valgrind development files... "
+if pkg-config --exists valgrind; then
+    printf "Yes.\n"
+    have_valgrind=1
+    valgrind_cflags=$(pkg-config --cflags valgrind)
+else
+    printf "No (but that's fine).\n"
+    have_valgrind=0
+    valgrind_cflags=
+fi
+
+printf "Checking for bash-completion (>= 1.90)... "
+if pkg-config --atleast-version=1.90 bash-completion; then
+    printf "Yes.\n"
+else
+    printf "No (will not install bash completion).\n"
+    WITH_BASH=0
+fi
+
+if [ -z "${EMACSLISPDIR-}" ]; then
+    EMACSLISPDIR="\$(prefix)/share/emacs/site-lisp"
+fi
+
+if [ -z "${EMACSETCDIR-}" ]; then
+    EMACSETCDIR="\$(prefix)/share/emacs/site-lisp"
+fi
+
+printf "Checking if emacs is available... "
+if emacs --quick --batch > /dev/null 2>&1; then
+    printf "Yes.\n"
+    have_emacs=1
+else
+    printf "No (so will not byte-compile emacs code)\n"
+    have_emacs=0
+fi
+
+have_doxygen=0
+if [ $WITH_API_DOCS = "1" ] ; then
+    printf "Checking if doxygen is available... "
+    if command -v doxygen > /dev/null; then
+       printf "Yes.\n"
+       have_doxygen=1
+    else
+       printf "No (so will not install api docs)\n"
+    fi
+fi
+
+have_ruby_dev=0
+if [ $WITH_RUBY = "1" ] ; then
+    printf "Checking for ruby development files... "
+    if ${RUBY} -e "require 'mkmf'"> /dev/null 2>&1; then
+       printf "Yes.\n"
+       have_ruby_dev=1
+    else
+       printf "No (skipping ruby bindings)\n"
+    fi
+fi
+
+have_sphinx=0
+have_makeinfo=0
+have_install_info=0
+if [ $WITH_DOCS = "1" ] ; then
+    printf "Checking if sphinx is available and supports nroff output... "
+    if command -v sphinx-build > /dev/null && ${python} -m sphinx.writers.manpage > /dev/null 2>&1 ; then
+       printf "Yes.\n"
+       have_sphinx=1
+    else
+       printf "No (so will not install man pages).\n"
+    fi
+    printf "Checking if makeinfo is available... "
+    if command -v makeinfo > /dev/null; then
+       printf "Yes.\n"
+       have_makeinfo=1
+    else
+       printf "No (so will not build info pages).\n"
+    fi
+    printf "Checking if install-info is available... "
+    if command -v install-info > /dev/null; then
+       printf "Yes.\n"
+       have_install_info=1
+    else
+       printf "No (so will not install info pages).\n"
+    fi
+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
+
+printf "Checking for cppcheck... "
+if command -v cppcheck > /dev/null; then
+    have_cppcheck=1
+    printf "Yes.\n"
+else
+    have_cppcheck=0
+    printf "No.\n"
+fi
+
+libdir_in_ldconfig=0
+
+printf "Checking which platform we are on... "
+uname=$(uname)
+if [ $uname = "Darwin" ] ; then
+    printf "Mac OS X.\n"
+    platform=MACOSX
+    linker_resolves_library_dependencies=0
+elif [ $uname = "SunOS" ] ; then
+    printf "Solaris.\n"
+    platform=SOLARIS
+    linker_resolves_library_dependencies=0
+elif [ $uname = "FreeBSD" ] ; then
+    printf "FreeBSD.\n"
+    platform=FREEBSD
+    linker_resolves_library_dependencies=0
+elif [ $uname = "OpenBSD" ] ; then
+    printf "OpenBSD.\n"
+    platform=OPENBSD
+    linker_resolves_library_dependencies=0
+elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then
+    printf "%s\n" "$uname"
+    platform="$uname"
+    linker_resolves_library_dependencies=1
+
+    printf "Checking for %s in ldconfig... " "$libdir_expanded"
+    ldconfig_paths=$(/sbin/ldconfig -N -X -v 2>/dev/null | sed -n -e 's,^\(/.*\):\( (.*)\)\?$,\1,p')
+    # Separate ldconfig_paths only on newline (not on any potential
+    # embedded space characters in any filenames). Note, we use a
+    # literal newline in the source here rather than something like:
+    #
+    #  IFS=$(printf '\n')
+    #
+    # because the shell's command substitution deletes any trailing newlines.
+    IFS="
+"
+    for path in $ldconfig_paths; do
+       if [ "$path" -ef "$libdir_expanded" ]; then
+           libdir_in_ldconfig=1
+       fi
+    done
+    IFS=$DEFAULT_IFS
+    if [ "$libdir_in_ldconfig" = '0' ]; then
+       printf "No (will set RPATH)\n"
+    else
+       printf "Yes\n"
+    fi
+else
+    printf "Unknown.\n"
+    platform="$uname"
+    linker_resolves_library_dependencies=0
+    cat <<EOF
+
+*** Warning: Unknown platform. Notmuch might or might not build correctly.
+
+EOF
+fi
+
+if [ $errors -gt 0 ]; then
+    cat <<EOF
+
+*** Error: The dependencies of notmuch could not be satisfied. You will
+need to install the following packages before being able to compile
+notmuch:
+
+EOF
+    if [ $have_python -eq 0 ]; then
+       echo "  python interpreter"
+    fi
+    if [ $have_xapian -eq 0 -o $have_xapian_compact -eq 0 ]; then
+       echo "  Xapian library (>= version 1.2.6, including development files such as headers)"
+       echo "  https://xapian.org/"
+    fi
+    if [ $have_zlib -eq 0 ]; then
+       echo "  zlib library (>= version 1.2.5.2, including development files such as headers)"
+       echo "  https://zlib.net/"
+       echo
+    fi
+    if [ $have_gmime -eq 0 ]; then
+       echo "  GMime 2.6 library >= $GMIME_MINVER"
+       echo "  (including development files such as headers)"
+       echo "  https://github.com/jstedfast/gmime/"
+       echo
+    fi
+    if [ $have_glib -eq 0 ]; then
+       echo "  Glib library >= 2.22 (including development files such as headers)"
+       echo "  https://ftp.gnome.org/pub/gnome/sources/glib/"
+       echo
+    fi
+    if [ $have_talloc -eq 0 ]; then
+       echo "  The talloc library (including development files such as headers)"
+       echo "  https://talloc.samba.org/"
+       echo
+    fi
+    cat <<EOF
+With any luck, you're using a modern, package-based operating system
+that has all of these packages available in the distribution. In that
+case a simple command will install everything you need. For example:
+
+On Debian and similar systems:
+
+       sudo apt-get install libxapian-dev libgmime-2.6-dev libtalloc-dev zlib1g-dev
+
+Or on Fedora and similar systems:
+
+       sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel
+
+On other systems, similar commands can be used, but the details of the
+package names may be different.
+
+EOF
+    if [ $have_pkg_config -eq 0 ]; then
+cat <<EOF
+Note: the pkg-config program is not available. This configure script
+uses pkg-config to find the compilation flags required to link against
+the various libraries needed by notmuch. It's possible you simply need
+to install pkg-config with a command such as:
+
+       sudo apt-get install pkg-config
+Or:
+       sudo yum install pkgconfig
+
+But if pkg-config is not available for your system, then you will need
+to modify the configure script to manually set the cflags and ldflags
+variables to the correct values to link against each library in each
+case that pkg-config could not be used to determine those values.
+
+EOF
+    fi
+cat <<EOF
+When you have installed the necessary dependencies, you can run
+configure again to ensure the packages can be found, or simply run
+"make" to compile notmuch.
+
+EOF
+    exit 1
+fi
+
+printf "Checking for canonicalize_file_name... "
+if ${CC} -o compat/have_canonicalize_file_name "$srcdir"/compat/have_canonicalize_file_name.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_canonicalize_file_name=1
+else
+    printf "No (will use our own instead).\n"
+    have_canonicalize_file_name=0
+fi
+rm -f compat/have_canonicalize_file_name
+
+
+printf "Checking for getline... "
+if ${CC} -o compat/have_getline "$srcdir"/compat/have_getline.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_getline=1
+else
+    printf "No (will use our own instead).\n"
+    have_getline=0
+fi
+rm -f compat/have_getline
+
+printf "Checking for strcasestr... "
+if ${CC} -o compat/have_strcasestr "$srcdir"/compat/have_strcasestr.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_strcasestr=1
+else
+    printf "No (will use our own instead).\n"
+    have_strcasestr=0
+fi
+rm -f compat/have_strcasestr
+
+printf "Checking for strsep... "
+if ${CC} -o compat/have_strsep "$srcdir"/compat/have_strsep.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_strsep="1"
+else
+    printf "No (will use our own instead).\n"
+    have_strsep="0"
+fi
+rm -f compat/have_strsep
+
+printf "Checking for timegm... "
+if ${CC} -o compat/have_timegm "$srcdir"/compat/have_timegm.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_timegm="1"
+else
+    printf "No (will use our own instead).\n"
+    have_timegm="0"
+fi
+rm -f compat/have_timegm
+
+printf "Checking for dirent.d_type... "
+if ${CC} -o compat/have_d_type "$srcdir"/compat/have_d_type.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    have_d_type="1"
+else
+    printf "No (will use stat instead).\n"
+    have_d_type="0"
+fi
+rm -f compat/have_d_type
+
+printf "Checking for standard version of getpwuid_r... "
+if ${CC} -o compat/check_getpwuid "$srcdir"/compat/check_getpwuid.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    std_getpwuid=1
+else
+    printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n"
+    std_getpwuid=0
+fi
+rm -f compat/check_getpwuid
+
+printf "Checking for standard version of asctime_r... "
+if ${CC} -o compat/check_asctime "$srcdir"/compat/check_asctime.c > /dev/null 2>&1
+then
+    printf "Yes.\n"
+    std_asctime=1
+else
+    printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n"
+    std_asctime=0
+fi
+rm -f compat/check_asctime
+
+printf "Checking for rpath support... "
+if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1
+then
+    printf "Yes.\n"
+    rpath_ldflags="-Wl,--enable-new-dtags -Wl,-rpath,\$(libdir)"
+else
+    printf "No (nothing to worry about).\n"
+    rpath_ldflags=""
+fi
+
+printf "Checking for -Wl,--as-needed... "
+if ${CC} -Wl,--as-needed -o minimal minimal.c >/dev/null 2>&1
+then
+    printf "Yes.\n"
+    as_needed_ldflags="-Wl,--as-needed"
+else
+    printf "No (nothing to worry about).\n"
+    as_needed_ldflags=""
+fi
+
+printf "Checking for -Wl,--no-undefined... "
+if ${CC} -Wl,--no-undefined -o minimal minimal.c >/dev/null 2>&1
+then
+    printf "Yes.\n"
+    no_undefined_ldflags="-Wl,--no-undefined"
+else
+    printf "No (nothing to worry about).\n"
+    no_undefined_ldflags=""
+fi
+
+WARN_CXXFLAGS=""
+printf "Checking for available C++ compiler warning flags... "
+for flag in -Wall -Wextra -Wwrite-strings; do
+    if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1
+    then
+       WARN_CXXFLAGS="${WARN_CXXFLAGS}${WARN_CXXFLAGS:+ }${flag}"
+    fi
+done
+printf "\n\t%s\n" "${WARN_CXXFLAGS}"
+
+WARN_CFLAGS="${WARN_CXXFLAGS}"
+printf "Checking for available C compiler warning flags... "
+for flag in -Wmissing-declarations; do
+    if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1
+    then
+       WARN_CFLAGS="${WARN_CFLAGS}${WARN_CFLAGS:+ }${flag}"
+    fi
+done
+printf "\n\t%s\n" "${WARN_CFLAGS}"
+
+rm -f minimal minimal.c _libversion.c _libversion _libversion.sh
+
+# construct the Makefile.config
+cat > Makefile.config <<EOF
+# This Makefile.config was automatically generated by the ./configure
+# script of notmuch. If the configure script identified anything
+# incorrectly, then you can edit this file to try to correct things,
+# but be warned that if configure is run again it will destroy your
+# changes, (and this could happen by simply calling "make" if the
+# configure script is updated).
+
+# The top-level directory for the source, (the directory containing
+# the configure script). This may be different than the build
+# directory (the current directory at the time configure was run).
+srcdir = ${srcdir}
+NOTMUCH_SRCDIR = ${NOTMUCH_SRCDIR}
+
+# subdirectories to build
+subdirs = ${subdirs}
+
+configure_options = $@
+
+# We use vpath directives (rather than the VPATH variable) since the
+# VPATH variable matches targets as well as prerequisites, (which is
+# not useful since then a target left-over from a srcdir build would
+# cause a target to not be built in the non-srcdir build).
+#
+# Also, we don't use a single "vpath % \$(srcdir)" here because we
+# don't want the vpath to trigger for our emacs lisp compilation,
+# (unless we first find a way to convince emacs to build the .elc
+# target in a directory other than the directory of the .el
+# prerequisite). In the meantime, we're actually copying in the .el
+# files, (which is quite ugly).
+vpath %.c \$(srcdir)
+vpath %.cc \$(srcdir)
+vpath Makefile.% \$(srcdir)
+vpath %.py \$(srcdir)
+vpath %.rst \$(srcdir)
+
+# Library versions (used to make SONAME)
+# The major version of the library interface. This will control the soname.
+# As such, this number must be incremented for any incompatible change to
+# the library interface, (such as the deletion of an API or a major
+# semantic change that breaks formerly functioning code).
+#
+LIBNOTMUCH_VERSION_MAJOR = ${libnotmuch_version_major}
+
+# The minor version of the library interface. This should be incremented at
+# the time of release for any additions to the library interface,
+# (and when it is incremented, the release version of the library should
+#  be reset to 0).
+LIBNOTMUCH_VERSION_MINOR = ${libnotmuch_version_minor}
+
+# The release version the library interface. This should be incremented at
+# the time of release if there have been no changes to the interface, (but
+# simply compatible changes to the implementation).
+LIBNOTMUCH_VERSION_RELEASE = ${libnotmuch_version_release}
+
+# These are derived from the VERSION macros in lib/notmuch.h so
+# if you have to change them, something is wrong.
+
+# The C compiler to use
+CC = ${CC}
+
+# The C++ compiler to use
+CXX = ${CXX}
+
+# Command to execute emacs from Makefiles
+EMACS = emacs --quick
+
+# Default FLAGS for C compiler (can be overridden by user such as "make CFLAGS=-g")
+CFLAGS = ${CFLAGS}
+
+# Default FLAGS for C preprocessor (can be overridden by user such as "make CPPFLAGS=-I/usr/local/include")
+CPPFLAGS = ${CPPFLAGS}
+
+# Default FLAGS for C++ compiler (can be overridden by user such as "make CXXFLAGS=-g")
+CXXFLAGS = ${CXXFLAGS}
+
+# Default FLAGS for the linker (can be overridden by user such as "make LDFLAGS=-znow")
+LDFLAGS = ${LDFLAGS}
+
+# Flags to enable warnings when using the C++ compiler
+WARN_CXXFLAGS=${WARN_CXXFLAGS}
+
+# Flags to enable warnings when using the C compiler
+WARN_CFLAGS=${WARN_CFLAGS}
+
+# Name of python interpreter
+PYTHON = ${python}
+
+# Name of ruby interpreter
+RUBY = ${RUBY}
+
+# The prefix to which notmuch should be installed
+# Note: If you change this value here, be sure to ensure that the
+# LIBDIR_IN_LDCONFIG value below is still set correctly.
+prefix = ${PREFIX}
+
+# The directory to which libraries should be installed
+# Note: If you change this value here, be sure to ensure that the
+# LIBDIR_IN_LDCONFIG value below is still set correctly.
+libdir = ${LIBDIR:=\$(prefix)/lib}
+
+# Whether libdir is in a path configured into ldconfig
+LIBDIR_IN_LDCONFIG = ${libdir_in_ldconfig}
+
+# The directory to which header files should be installed
+includedir = ${INCLUDEDIR:=\$(prefix)/include}
+
+# The directory to which man pages should be installed
+mandir = ${MANDIR:=\$(prefix)/share/man}
+
+# The directory to which man pages should be installed
+infodir = ${INFODIR:=\$(prefix)/share/info}
+
+# The directory to which read-only (configuration) files should be installed
+sysconfdir = ${SYSCONFDIR:=\$(prefix)/etc}
+
+# The directory to which emacs lisp files should be installed
+emacslispdir=${EMACSLISPDIR}
+
+# The directory to which emacs miscellaneous (machine-independent) files should
+# be installed
+emacsetcdir=${EMACSETCDIR}
+
+# Whether bash exists, and if so where
+HAVE_BASH = ${have_bash}
+BASH_ABSOLUTE = ${bash_absolute}
+
+# Whether perl exists, and if so where
+HAVE_PERL = ${have_perl}
+PERL_ABSOLUTE = ${perl_absolute}
+
+# Whether there's an emacs binary available for byte-compiling
+HAVE_EMACS = ${have_emacs}
+
+# Whether there's a sphinx-build binary available for building documentation
+HAVE_SPHINX=${have_sphinx}
+
+# Whether there's a makeinfo binary available for building info format documentation
+HAVE_MAKEINFO=${have_makeinfo}
+
+# Whether there's an install-info binary available for installing info format documentation
+HAVE_INSTALL_INFO=${have_install_info}
+
+# 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
+
+# The directory to which bash completions files should be installed
+bash_completion_dir = ${BASHCOMPLETIONDIR:=\$(prefix)/share/bash-completion/completions}
+
+# The directory to which zsh completions files should be installed
+zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/functions/Completion/Unix}
+
+# Whether the canonicalize_file_name function is available (if not, then notmuch will
+# build its own version)
+HAVE_CANONICALIZE_FILE_NAME = ${have_canonicalize_file_name}
+
+# Whether the cppcheck static checker is available
+HAVE_CPPCHECK = ${have_cppcheck}
+
+# Whether the getline function is available (if not, then notmuch will
+# build its own version)
+HAVE_GETLINE = ${have_getline}
+
+# Are the ruby development files (and ruby) available? If not skip
+# building/testing ruby bindings.
+HAVE_RUBY_DEV = ${have_ruby_dev}
+
+# Whether the strcasestr function is available (if not, then notmuch will
+# build its own version)
+HAVE_STRCASESTR = ${have_strcasestr}
+
+# Whether the strsep function is available (if not, then notmuch will
+# build its own version)
+HAVE_STRSEP = ${have_strsep}
+
+# Whether the timegm function is available (if not, then notmuch will
+# build its own version)
+HAVE_TIMEGM = ${have_timegm}
+
+# Whether struct dirent has d_type (if not, then notmuch will use stat)
+HAVE_D_TYPE = ${have_d_type}
+
+# Whether the GMime version can handle extraction and reuse of session keys
+HAVE_GMIME_SESSION_KEYS = ${have_gmime_session_keys}
+
+# Whether the Xapian version in use supports compaction
+HAVE_XAPIAN_COMPACT = ${have_xapian_compact}
+
+# Whether the Xapian version in use supports field processors
+HAVE_XAPIAN_FIELD_PROCESSOR = ${have_xapian_field_processor}
+
+# Whether the Xapian version in use supports DB_RETRY_LOCK
+HAVE_XAPIAN_DB_RETRY_LOCK = ${have_xapian_db_retry_lock}
+
+# Whether the getpwuid_r function is standards-compliant
+# (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
+# to enable the standards-compliant version -- needed for Solaris)
+STD_GETPWUID = ${std_getpwuid}
+
+# Whether the asctime_r function is standards-compliant
+# (if not, then notmuch will #define _POSIX_PTHREAD_SEMANTICS
+# to enable the standards-compliant version -- needed for Solaris)
+STD_ASCTIME = ${std_asctime}
+
+# Supported platforms (so far) are: LINUX, MACOSX, SOLARIS, FREEBSD, OPENBSD
+PLATFORM = ${platform}
+
+# Whether the linker will automatically resolve the dependency of one
+# library on another (if not, then linking a binary requires linking
+# directly against both)
+LINKER_RESOLVES_LIBRARY_DEPENDENCIES = ${linker_resolves_library_dependencies}
+
+# Flags needed to compile and link against Xapian
+XAPIAN_CXXFLAGS = ${xapian_cxxflags}
+XAPIAN_LDFLAGS = ${xapian_ldflags}
+
+# Which backend will Xapian use by default?
+DEFAULT_XAPIAN_BACKEND = ${default_xapian_backend}
+
+# Flags needed to compile and link against GMime
+GMIME_CFLAGS = ${gmime_cflags}
+GMIME_LDFLAGS = ${gmime_ldflags}
+
+# Flags needed to compile and link against zlib
+ZLIB_CFLAGS = ${zlib_cflags}
+ZLIB_LDFLAGS = ${zlib_ldflags}
+
+# Flags needed to compile and link against talloc
+TALLOC_CFLAGS = ${talloc_cflags}
+TALLOC_LDFLAGS = ${talloc_ldflags}
+
+# Flags needed to have linker set rpath attribute
+RPATH_LDFLAGS = ${rpath_ldflags}
+
+# Flags needed to have linker link only to necessary libraries
+AS_NEEDED_LDFLAGS = ${as_needed_ldflags}
+
+# Flags to have the linker flag undefined symbols in object files
+NO_UNDEFINED_LDFLAGS = ${no_undefined_ldflags}
+
+# Whether valgrind header files are available
+HAVE_VALGRIND = ${have_valgrind}
+
+# And if so, flags needed at compile time for valgrind macros
+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}
+
+# Support for zsh completion
+WITH_ZSH = ${WITH_ZSH}
+
+# Combined flags for compiling and linking against all of the above
+COMMON_CONFIGURE_CFLAGS = \\
+       \$(GMIME_CFLAGS) \$(TALLOC_CFLAGS) \$(ZLIB_CFLAGS)      \\
+       -DHAVE_VALGRIND=\$(HAVE_VALGRIND) \$(VALGRIND_CFLAGS)   \\
+       -DHAVE_GETLINE=\$(HAVE_GETLINE)                         \\
+       -DWITH_EMACS=\$(WITH_EMACS)                             \\
+       -DHAVE_CANONICALIZE_FILE_NAME=\$(HAVE_CANONICALIZE_FILE_NAME) \\
+       -DHAVE_STRCASESTR=\$(HAVE_STRCASESTR)                   \\
+       -DHAVE_STRSEP=\$(HAVE_STRSEP)                           \\
+       -DHAVE_TIMEGM=\$(HAVE_TIMEGM)                           \\
+       -DHAVE_D_TYPE=\$(HAVE_D_TYPE)                           \\
+       -DSTD_GETPWUID=\$(STD_GETPWUID)                         \\
+       -DSTD_ASCTIME=\$(STD_ASCTIME)                           \\
+       -DHAVE_GMIME_SESSION_KEYS=\$(HAVE_GMIME_SESSION_KEYS)   \\
+       -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)
+
+CONFIGURE_CFLAGS = \$(COMMON_CONFIGURE_CFLAGS)
+
+CONFIGURE_CXXFLAGS = \$(COMMON_CONFIGURE_CFLAGS) \$(XAPIAN_CXXFLAGS)
+
+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.
+
+NOTMUCH_SRCDIR='${NOTMUCH_SRCDIR}'
+
+# Whether the Xapian version in use supports compaction
+NOTMUCH_HAVE_XAPIAN_COMPACT=${have_xapian_compact}
+
+# Whether the Xapian version in use supports field processors
+NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR=${have_xapian_field_processor}
+
+# Whether the Xapian version in use supports lock retry
+NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=${have_xapian_db_retry_lock}
+
+# Whether the GMime version can handle extraction and reuse of session keys
+NOTMUCH_HAVE_GMIME_SESSION_KEYS=${have_gmime_session_keys}
+
+# Which backend will Xapian use by default?
+NOTMUCH_DEFAULT_XAPIAN_BACKEND=${default_xapian_backend}
+
+# do we have man pages?
+NOTMUCH_HAVE_MAN=$((have_sphinx))
+
+# Whether bash exists, and if so where
+NOTMUCH_HAVE_BASH=${have_bash}
+NOTMUCH_BASH_ABSOLUTE=${bash_absolute}
+
+# Whether perl exists, and if so where
+NOTMUCH_HAVE_PERL=${have_perl}
+NOTMUCH_PERL_ABSOLUTE=${perl_absolute}
+
+# Name of python interpreter
+NOTMUCH_PYTHON=${python}
+
+# Name of ruby interpreter
+NOTMUCH_RUBY=${RUBY}
+
+# Are the ruby development files (and ruby) available? If not skip
+# building/testing ruby bindings.
+NOTMUCH_HAVE_RUBY_DEV=${have_ruby_dev}
+
+# Major version of gmime
+NOTMUCH_GMIME_MAJOR=${gmime_major}
+
+# Platform we are run on
+PLATFORM=${platform}
+EOF
+
+# Finally, after everything configured, inform the user how to continue.
+cat <<EOF
+
+All required packages were found. You may now run the following
+commands to compile and install notmuch:
+
+       make
+       sudo make install
+
+EOF
diff --git a/contrib/go/.gitignore b/contrib/go/.gitignore
new file mode 100644 (file)
index 0000000..223504b
--- /dev/null
@@ -0,0 +1,3 @@
+/src/github.com/
+/pkg/
+/bin/
diff --git a/contrib/go/LICENSE b/contrib/go/LICENSE
new file mode 100644 (file)
index 0000000..4362b49
--- /dev/null
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/contrib/go/Makefile b/contrib/go/Makefile
new file mode 100644 (file)
index 0000000..1b9e750
--- /dev/null
@@ -0,0 +1,40 @@
+# Makefile for the go bindings of notmuch
+
+export GOPATH      ?= $(shell pwd)
+export CGO_CFLAGS  ?= -I../../../../lib
+export CGO_LDFLAGS ?= -L../../../../lib
+
+GO         ?= go
+GOFMT      ?= gofmt
+
+all: notmuch notmuch-addrlookup
+
+.PHONY: notmuch
+notmuch:
+       $(GO) install notmuch
+
+.PHONY: goconfig
+goconfig:
+       if [ ! -d github.com/msbranco/goconfig ]; then \
+           $(GO) get github.com/msbranco/goconfig; \
+       fi
+
+.PHONY: notmuch-addrlookup
+notmuch-addrlookup: notmuch goconfig
+       $(GO) install notmuch-addrlookup
+
+.PHONY: format
+format:
+       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch
+       $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch-addrlookup
+
+.PHONY: check-format
+check-format:
+       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch
+       $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch-addrlookup
+
+.PHONY: clean
+clean:
+       $(GO) clean notmuch
+       $(GO) clean notmuch-addrlookup
+       rm -rf pkg bin
diff --git a/contrib/go/README b/contrib/go/README
new file mode 100644 (file)
index 0000000..1825ae0
--- /dev/null
@@ -0,0 +1,16 @@
+go-notmuch
+==========
+
+simple go bindings to the libnotmuch library[1].
+
+they are heavily inspired from the vala bindings from Sebastian Spaeth: 
+  https://github.com/spaetz/vala-notmuch
+
+note: the whole library hasn't been wrapped (yet?)
+
+todo
+----
+
+ - improve notmuch-addrlookup regexp
+
+[1] https://notmuchmail.org/
diff --git a/contrib/go/src/notmuch-addrlookup/addrlookup.go b/contrib/go/src/notmuch-addrlookup/addrlookup.go
new file mode 100644 (file)
index 0000000..916e5bb
--- /dev/null
@@ -0,0 +1,261 @@
+package main
+
+// stdlib imports
+import "os"
+import "path"
+import "log"
+import "fmt"
+import "regexp"
+import "strings"
+import "sort"
+
+// 3rd-party imports
+import "notmuch"
+import "github.com/msbranco/goconfig"
+
+type mail_addr_freq struct {
+       addr  string
+       count [3]uint
+}
+
+type frequencies map[string]uint
+
+/* Used to sort the email addresses from most to least used */
+func sort_by_freq(m1, m2 *mail_addr_freq) int {
+       if m1.count[0] == m2.count[0] &&
+               m1.count[1] == m2.count[1] &&
+               m1.count[2] == m2.count[2] {
+               return 0
+       }
+
+       if m1.count[0] > m2.count[0] ||
+               m1.count[0] == m2.count[0] &&
+                       m1.count[1] > m2.count[1] ||
+               m1.count[0] == m2.count[0] &&
+                       m1.count[1] == m2.count[1] &&
+                       m1.count[2] > m2.count[2] {
+               return -1
+       }
+
+       return 1
+}
+
+type maddresses []*mail_addr_freq
+
+func (self *maddresses) Len() int {
+       return len(*self)
+}
+
+func (self *maddresses) Less(i, j int) bool {
+       m1 := (*self)[i]
+       m2 := (*self)[j]
+       v := sort_by_freq(m1, m2)
+       if v <= 0 {
+               return true
+       }
+       return false
+}
+
+func (self *maddresses) Swap(i, j int) {
+       (*self)[i], (*self)[j] = (*self)[j], (*self)[i]
+}
+
+// find most frequent real name for each mail address
+func frequent_fullname(freqs frequencies) string {
+       var maxfreq uint = 0
+       fullname := ""
+       freqs_sz := len(freqs)
+
+       for mail, freq := range freqs {
+               if (freq > maxfreq && mail != "") || freqs_sz == 1 {
+                       // only use the entry if it has a real name
+                       // or if this is the only entry
+                       maxfreq = freq
+                       fullname = mail
+               }
+       }
+       return fullname
+}
+
+func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies {
+
+       freqs := make(frequencies)
+
+       pattern := `\s*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
+       // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
+       //      "<?(?P<mail>\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)"
+       pattern = `.*` + strings.ToLower(name) + `.*`
+       var re *regexp.Regexp = nil
+       var err error = nil
+       if re, err = regexp.Compile(pattern); err != nil {
+               log.Printf("error: %v\n", err)
+               return &freqs
+       }
+
+       headers := []string{"from"}
+       if pass == 1 {
+               headers = append(headers, "to", "cc", "bcc")
+       }
+
+       for ; msgs.Valid(); msgs.MoveToNext() {
+               msg := msgs.Get()
+               //println("==> msg [", msg.GetMessageId(), "]")
+               for _, header := range headers {
+                       froms := strings.ToLower(msg.GetHeader(header))
+                       //println("  froms: ["+froms+"]")
+                       for _, from := range strings.Split(froms, ",") {
+                               from = strings.Trim(from, " ")
+                               match := re.FindString(from)
+                               //println("  -> match: ["+match+"]")
+                               occ, ok := freqs[match]
+                               if !ok {
+                                       freqs[match] = 0
+                                       occ = 0
+                               }
+                               freqs[match] = occ + 1
+                       }
+               }
+       }
+       return &freqs
+}
+
+func search_address_passes(queries [3]*notmuch.Query, name string) []string {
+       var val []string
+       addr_freq := make(map[string]*mail_addr_freq)
+       addr_to_realname := make(map[string]*frequencies)
+
+       var pass uint = 0 // 0-based
+       for _, query := range queries {
+               if query == nil {
+                       //println("**warning: idx [",idx,"] contains a nil query")
+                       continue
+               }
+               msgs := query.SearchMessages()
+               ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname)
+               for addr, count := range *ht {
+                       freq, ok := addr_freq[addr]
+                       if !ok {
+                               freq = &mail_addr_freq{addr: addr, count: [3]uint{0, 0, 0}}
+                       }
+                       freq.count[pass] = count
+                       addr_freq[addr] = freq
+               }
+               msgs.Destroy()
+               pass += 1
+       }
+
+       addrs := make(maddresses, len(addr_freq))
+       {
+               iaddr := 0
+               for _, freq := range addr_freq {
+                       addrs[iaddr] = freq
+                       iaddr += 1
+               }
+       }
+       sort.Sort(&addrs)
+
+       for _, addr := range addrs {
+               freqs, ok := addr_to_realname[addr.addr]
+               if ok {
+                       val = append(val, frequent_fullname(*freqs))
+               } else {
+                       val = append(val, addr.addr)
+               }
+       }
+       //println("val:",val)
+       return val
+}
+
+type address_matcher struct {
+       // the notmuch database
+       db *notmuch.Database
+       // full path of the notmuch database
+       user_db_path string
+       // user primary email
+       user_primary_email string
+       // user tag to mark from addresses as in the address book
+       user_addrbook_tag string
+}
+
+func new_address_matcher() *address_matcher {
+       // honor NOTMUCH_CONFIG
+       home := os.Getenv("NOTMUCH_CONFIG")
+       if home == "" {
+               home = os.Getenv("HOME")
+       }
+
+       cfg, err := goconfig.ReadConfigFile(path.Join(home, ".notmuch-config"))
+       if err != nil {
+               log.Fatalf("error loading config file:", err)
+       }
+
+       db_path, _ := cfg.GetString("database", "path")
+       primary_email, _ := cfg.GetString("user", "primary_email")
+       addrbook_tag, err := cfg.GetString("user", "addrbook_tag")
+       if err != nil {
+               addrbook_tag = "addressbook"
+       }
+
+       self := &address_matcher{db: nil,
+               user_db_path:       db_path,
+               user_primary_email: primary_email,
+               user_addrbook_tag:  addrbook_tag}
+       return self
+}
+
+func (self *address_matcher) run(name string) {
+       queries := [3]*notmuch.Query{}
+
+       // open the database
+       if db, status := notmuch.OpenDatabase(self.user_db_path,
+               notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS {
+               self.db = db
+       } else {
+               log.Fatalf("Failed to open the database: %v\n", status)
+       }
+
+       // pass 1: look at all from: addresses with the address book tag
+       query := "tag:" + self.user_addrbook_tag
+       if name != "" {
+               query = query + " and from:" + name + "*"
+       }
+       queries[0] = self.db.CreateQuery(query)
+
+       // pass 2: look at all to: addresses sent from our primary mail
+       query = ""
+       if name != "" {
+               query = "to:" + name + "*"
+       }
+       if self.user_primary_email != "" {
+               query = query + " from:" + self.user_primary_email
+       }
+       queries[1] = self.db.CreateQuery(query)
+
+       // if that leads only to a few hits, we check every from too
+       if queries[0].CountMessages()+queries[1].CountMessages() < 10 {
+               query = ""
+               if name != "" {
+                       query = "from:" + name + "*"
+               }
+               queries[2] = self.db.CreateQuery(query)
+       }
+
+       // actually retrieve and sort addresses
+       results := search_address_passes(queries, name)
+       for _, v := range results {
+               if v != "" && v != "\n" {
+                       fmt.Println(v)
+               }
+       }
+       return
+}
+
+func main() {
+       //fmt.Println("args:",os.Args)
+       app := new_address_matcher()
+       name := ""
+       if len(os.Args) > 1 {
+               name = os.Args[1]
+       }
+       app.run(name)
+}
diff --git a/contrib/go/src/notmuch/notmuch.go b/contrib/go/src/notmuch/notmuch.go
new file mode 100644 (file)
index 0000000..5496198
--- /dev/null
@@ -0,0 +1,1404 @@
+// Wrapper around the notmuch library
+
+package notmuch
+
+/*
+#cgo LDFLAGS: -lnotmuch
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "notmuch.h"
+*/
+import "C"
+import "unsafe"
+
+// Status codes used for the return values of most functions
+type Status C.notmuch_status_t
+
+const (
+       STATUS_SUCCESS Status = iota
+       STATUS_OUT_OF_MEMORY
+       STATUS_READ_ONLY_DATABASE
+       STATUS_XAPIAN_EXCEPTION
+       STATUS_FILE_ERROR
+       STATUS_FILE_NOT_EMAIL
+       STATUS_DUPLICATE_MESSAGE_ID
+       STATUS_NULL_POINTER
+       STATUS_TAG_TOO_LONG
+       STATUS_UNBALANCED_FREEZE_THAW
+       STATUS_UNBALANCED_ATOMIC
+
+       STATUS_LAST_STATUS
+)
+
+func (self Status) String() string {
+       var p *C.char
+
+       // p is read-only
+       p = C.notmuch_status_to_string(C.notmuch_status_t(self))
+       if p != nil {
+               s := C.GoString(p)
+               return s
+       }
+       return ""
+}
+
+/* Various opaque data types. For each notmuch_<foo>_t see the various
+ * notmuch_<foo> functions below. */
+
+type Database struct {
+       db *C.notmuch_database_t
+}
+
+type Query struct {
+       query *C.notmuch_query_t
+}
+
+type Threads struct {
+       threads *C.notmuch_threads_t
+}
+
+type Thread struct {
+       thread *C.notmuch_thread_t
+}
+
+type Messages struct {
+       messages *C.notmuch_messages_t
+}
+
+type Message struct {
+       message *C.notmuch_message_t
+}
+
+type Tags struct {
+       tags *C.notmuch_tags_t
+}
+
+type Directory struct {
+       dir *C.notmuch_directory_t
+}
+
+type Filenames struct {
+       fnames *C.notmuch_filenames_t
+}
+
+type DatabaseMode C.notmuch_database_mode_t
+
+const (
+       DATABASE_MODE_READ_ONLY DatabaseMode = iota
+       DATABASE_MODE_READ_WRITE
+)
+
+// Create a new, empty notmuch database located at 'path'
+func NewDatabase(path string) (*Database, Status) {
+
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       self := &Database{db: nil}
+       st := Status(C.notmuch_database_create(c_path, &self.db))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return self, st
+}
+
+/* Open an existing notmuch database located at 'path'.
+ *
+ * The database should have been created at some time in the past,
+ * (not necessarily by this process), by calling
+ * notmuch_database_create with 'path'. By default the database should be
+ * opened for reading only. In order to write to the database you need to
+ * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode.
+ *
+ * An existing notmuch database can be identified by the presence of a
+ * directory named ".notmuch" below 'path'.
+ *
+ * The caller should call notmuch_database_destroy when finished with
+ * this database.
+ *
+ * In case of any failure, this function returns NULL, (after printing
+ * an error message on stderr).
+ */
+func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) {
+
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       self := &Database{db: nil}
+       st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return self, st
+}
+
+/* Close the given notmuch database, freeing all associated
+ * resources. See notmuch_database_open. */
+func (self *Database) Close() Status {
+       return Status(C.notmuch_database_destroy(self.db))
+}
+
+/* Return the database path of the given database.
+ */
+func (self *Database) GetPath() string {
+
+       /* The return value is a string owned by notmuch so should not be
+        * modified nor freed by the caller. */
+       var p *C.char = C.notmuch_database_get_path(self.db)
+       if p != nil {
+               s := C.GoString(p)
+               return s
+       }
+       return ""
+}
+
+/* Return the database format version of the given database. */
+func (self *Database) GetVersion() uint {
+       return uint(C.notmuch_database_get_version(self.db))
+}
+
+/* Does this database need to be upgraded before writing to it?
+ *
+ * If this function returns TRUE then no functions that modify the
+ * database (notmuch_database_index_file, notmuch_message_add_tag,
+ * notmuch_directory_set_mtime, etc.) will work unless the function
+ * notmuch_database_upgrade is called successfully first. */
+func (self *Database) NeedsUpgrade() bool {
+       do_upgrade := C.notmuch_database_needs_upgrade(self.db)
+       if do_upgrade == 0 {
+               return false
+       }
+       return true
+}
+
+// TODO: notmuch_database_upgrade
+
+/* Retrieve a directory object from the database for 'path'.
+ *
+ * Here, 'path' should be a path relative to the path of 'database'
+ * (see notmuch_database_get_path), or else should be an absolute path
+ * with initial components that match the path of 'database'.
+ *
+ * Can return NULL if a Xapian exception occurs.
+ */
+func (self *Database) GetDirectory(path string) (*Directory, Status) {
+       var c_path *C.char = C.CString(path)
+       defer C.free(unsafe.Pointer(c_path))
+
+       if c_path == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       var c_dir *C.notmuch_directory_t
+       st := Status(C.notmuch_database_get_directory(self.db, c_path, &c_dir))
+       if st != STATUS_SUCCESS || c_dir == nil {
+               return nil, st
+       }
+       return &Directory{dir: c_dir}, st
+}
+
+/* Add a new message to the given notmuch database.
+ *
+ * Here,'filename' should be a path relative to the path of
+ * 'database' (see notmuch_database_get_path), or else should be an
+ * absolute filename with initial components that match the path of
+ * 'database'.
+ *
+ * The file should be a single mail message (not a multi-message mbox)
+ * that is expected to remain at its current location, (since the
+ * notmuch database will reference the filename, and will not copy the
+ * entire contents of the file.
+ *
+ * If 'message' is not NULL, then, on successful return '*message'
+ * will be initialized to a message object that can be used for things
+ * such as adding tags to the just-added message. The user should call
+ * notmuch_message_destroy when done with the message. On any failure
+ * '*message' will be set to NULL.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not added.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
+ *     ID as another message already in the database. The new
+ *     filename was successfully added to the message in the database
+ *     (if not already present).
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
+ *     file, (such as permission denied, or file not found,
+ *     etc.). Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
+ *     like an email message. Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be added.
+ */
+func (self *Database) AddMessage(fname string) (*Message, Status) {
+       var c_fname *C.char = C.CString(fname)
+       defer C.free(unsafe.Pointer(c_fname))
+
+       if c_fname == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       var c_msg *C.notmuch_message_t = new(C.notmuch_message_t)
+       st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg))
+
+       return &Message{message: c_msg}, st
+}
+
+/* Remove a message from the given notmuch database.
+ *
+ * Note that only this particular filename association is removed from
+ * the database. If the same message (as determined by the message ID)
+ * is still available via other filenames, then the message will
+ * persist in the database for those filenames. When the last filename
+ * is removed for a particular message, the database content for that
+ * message will be entirely removed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the
+ *     message was removed from the database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not removed.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but
+ *     the message persists in the database with at least one other
+ *     filename.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be removed.
+ */
+func (self *Database) RemoveMessage(fname string) Status {
+
+       var c_fname *C.char = C.CString(fname)
+       defer C.free(unsafe.Pointer(c_fname))
+
+       if c_fname == nil {
+               return STATUS_OUT_OF_MEMORY
+       }
+
+       st := C.notmuch_database_remove_message(self.db, c_fname)
+       return Status(st)
+}
+
+/* Find a message with the given message_id.
+ *
+ * If the database contains a message with the given message_id, then
+ * a new notmuch_message_t object is returned. The caller should call
+ * notmuch_message_destroy when done with the message.
+ *
+ * This function returns NULL in the following situations:
+ *
+ *     * No message is found with the given message_id
+ *     * An out-of-memory situation occurs
+ *     * A Xapian exception occurs
+ */
+func (self *Database) FindMessage(message_id string) (*Message, Status) {
+
+       var c_msg_id *C.char = C.CString(message_id)
+       defer C.free(unsafe.Pointer(c_msg_id))
+
+       if c_msg_id == nil {
+               return nil, STATUS_OUT_OF_MEMORY
+       }
+
+       msg := &Message{message: nil}
+       st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message))
+       if st != STATUS_SUCCESS {
+               return nil, st
+       }
+       return msg, st
+}
+
+/* Return a list of all tags found in the database.
+ *
+ * This function creates a list of all tags found in the database. The
+ * resulting list contains all tags from all messages found in the database.
+ *
+ * On error this function returns NULL.
+ */
+func (self *Database) GetAllTags() *Tags {
+       tags := C.notmuch_database_get_all_tags(self.db)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* Create a new query for 'database'.
+ *
+ * Here, 'database' should be an open database, (see
+ * notmuch_database_open and notmuch_database_create).
+ *
+ * For the query string, we'll document the syntax here more
+ * completely in the future, but it's likely to be a specialized
+ * version of the general Xapian query syntax:
+ *
+ * https://xapian.org/docs/queryparser.html
+ *
+ * As a special case, passing either a length-zero string, (that is ""),
+ * or a string consisting of a single asterisk (that is "*"), will
+ * result in a query that returns all messages in the database.
+ *
+ * See notmuch_query_set_sort for controlling the order of results.
+ * See notmuch_query_search_messages and notmuch_query_search_threads
+ * to actually execute the query.
+ *
+ * User should call notmuch_query_destroy when finished with this
+ * query.
+ *
+ * Will return NULL if insufficient memory is available.
+ */
+func (self *Database) CreateQuery(query string) *Query {
+
+       var c_query *C.char = C.CString(query)
+       defer C.free(unsafe.Pointer(c_query))
+
+       if c_query == nil {
+               return nil
+       }
+
+       q := C.notmuch_query_create(self.db, c_query)
+       if q == nil {
+               return nil
+       }
+       return &Query{query: q}
+}
+
+/* Sort values for notmuch_query_set_sort */
+type Sort C.notmuch_sort_t
+
+const (
+       SORT_OLDEST_FIRST Sort = 0
+       SORT_NEWEST_FIRST
+       SORT_MESSAGE_ID
+       SORT_UNSORTED
+)
+
+/* Return the query_string of this query. See notmuch_query_create. */
+func (self *Query) String() string {
+       // FIXME: do we own 'q' or not ?
+       q := C.notmuch_query_get_query_string(self.query)
+       //defer C.free(unsafe.Pointer(q))
+
+       if q != nil {
+               s := C.GoString(q)
+               return s
+       }
+
+       return ""
+}
+
+/* Specify the sorting desired for this query. */
+func (self *Query) SetSort(sort Sort) {
+       C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort))
+}
+
+/* Return the sort specified for this query. See notmuch_query_set_sort. */
+func (self *Query) GetSort() Sort {
+       return Sort(C.notmuch_query_get_sort(self.query))
+}
+
+/* Execute a query for threads, returning a notmuch_threads_t object
+ * which can be used to iterate over the results. The returned threads
+ * object is owned by the query and as such, will only be valid until
+ * notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_threads_t *threads;
+ *     notmuch_thread_t *thread;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (threads = notmuch_query_search_threads (query);
+ *          notmuch_threads_valid (threads);
+ *          notmuch_threads_move_to_next (threads))
+ *     {
+ *         thread = notmuch_threads_get (threads);
+ *         ....
+ *         notmuch_thread_destroy (thread);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a thread before its containing
+ * query, you can call notmuch_thread_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your thread objects
+ * are long-lived, then you don't need to call notmuch_thread_destroy
+ * and all the memory will still be reclaimed when the query is
+ * destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_threads_t object. (For consistency, we do provide a
+ * notmuch_threads_destroy function, but there's no good reason
+ * to call it if the query is about to be destroyed).
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ */
+func (self *Query) SearchThreads() *Threads {
+       threads := C.notmuch_query_search_threads(self.query)
+       if threads == nil {
+               return nil
+       }
+       return &Threads{threads: threads}
+}
+
+/* Execute a query for messages, returning a notmuch_messages_t object
+ * which can be used to iterate over the results. The returned
+ * messages object is owned by the query and as such, will only be
+ * valid until notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_messages_t *messages;
+ *     notmuch_message_t *message;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (messages = notmuch_query_search_messages (query);
+ *          notmuch_messages_valid (messages);
+ *          notmuch_messages_move_to_next (messages))
+ *     {
+ *         message = notmuch_messages_get (messages);
+ *         ....
+ *         notmuch_message_destroy (message);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a message before its containing
+ * query, you can call notmuch_message_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your message
+ * objects are long-lived, then you don't need to call
+ * notmuch_message_destroy and all the memory will still be reclaimed
+ * when the query is destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_messages_t object. (For consistency, we do provide a
+ * notmuch_messages_destroy function, but there's no good
+ * reason to call it if the query is about to be destroyed).
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ */
+func (self *Query) SearchMessages() *Messages {
+       msgs := C.notmuch_query_search_messages(self.query)
+       if msgs == nil {
+               return nil
+       }
+       return &Messages{messages: msgs}
+}
+
+/* Destroy a notmuch_query_t along with any associated resources.
+ *
+ * This will in turn destroy any notmuch_threads_t and
+ * notmuch_messages_t objects generated by this query, (and in
+ * turn any notmuch_thread_t and notmuch_message_t objects generated
+ * from those results, etc.), if such objects haven't already been
+ * destroyed.
+ */
+func (self *Query) Destroy() {
+       if self.query != nil {
+               C.notmuch_query_destroy(self.query)
+       }
+}
+
+/* Return an estimate of the number of messages matching a search
+ *
+ * This function performs a search and returns Xapian's best
+ * guess as to number of matching messages.
+ *
+ * If a Xapian exception occurs, this function may return 0 (after
+ * printing a message).
+ */
+func (self *Query) CountMessages() uint {
+       return uint(C.notmuch_query_count_messages(self.query))
+}
+
+/* Is the given 'threads' iterator pointing at a valid thread.
+ *
+ * When this function returns TRUE, notmuch_threads_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_threads_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+func (self *Threads) Valid() bool {
+       if self.threads == nil {
+               return false
+       }
+       valid := C.notmuch_threads_valid(self.threads)
+       if valid == 0 {
+               return false
+       }
+       return true
+}
+
+/* Get the current thread from 'threads' as a notmuch_thread_t.
+ *
+ * Note: The returned thread belongs to 'threads' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+func (self *Threads) Get() *Thread {
+       if self.threads == nil {
+               return nil
+       }
+       thread := C.notmuch_threads_get(self.threads)
+       if thread == nil {
+               return nil
+       }
+       return &Thread{thread}
+}
+
+/* Move the 'threads' iterator to the next thread.
+ *
+ * If 'threads' is already pointing at the last thread then the
+ * iterator will be moved to a point just beyond that last thread,
+ * (where notmuch_threads_valid will return FALSE and
+ * notmuch_threads_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+func (self *Threads) MoveToNext() {
+       if self.threads == nil {
+               return
+       }
+       C.notmuch_threads_move_to_next(self.threads)
+}
+
+/* Destroy a notmuch_threads_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_threads_t object will be reclaimed when the
+ * containing query object is destroyed.
+ */
+func (self *Threads) Destroy() {
+       if self.threads != nil {
+               C.notmuch_threads_destroy(self.threads)
+       }
+}
+
+/**
+ * Get the thread ID of 'thread'.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+func (self *Thread) GetThreadId() string {
+       if self.thread == nil {
+               return ""
+       }
+       id := C.notmuch_thread_get_thread_id(self.thread)
+       if id == nil {
+               return ""
+       }
+       return C.GoString(id)
+}
+
+/**
+ * Get the total number of messages in 'thread'.
+ *
+ * This count consists of all messages in the database belonging to
+ * this thread. Contrast with notmuch_thread_get_matched_messages() .
+ */
+func (self *Thread) GetTotalMessages() int {
+       if self.thread == nil {
+               return 0
+       }
+       return int(C.notmuch_thread_get_total_messages(self.thread))
+}
+
+/**
+ * Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread' in oldest-first order.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+func (self *Thread) GetToplevelMessages() (*Messages, Status) {
+       if self.thread == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+
+       msgs := C.notmuch_thread_get_toplevel_messages(self.thread)
+       if msgs == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+       return &Messages{msgs}, STATUS_SUCCESS
+}
+
+/**
+ * Get a notmuch_thread_t iterator for all messages in 'thread' in
+ * oldest-first order.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+func (self *Thread) GetMessages() (*Messages, Status) {
+       if self.thread == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+
+       msgs := C.notmuch_thread_get_messages(self.thread)
+       if msgs == nil {
+               return nil, STATUS_NULL_POINTER
+       }
+       return &Messages{msgs}, STATUS_SUCCESS
+}
+
+/**
+ * Get the number of messages in 'thread' that matched the search.
+ *
+ * This count includes only the messages in this thread that were
+ * matched by the search from which the thread was created and were
+ * not excluded by any exclude tags passed in with the query (see
+ * notmuch_query_add_tag_exclude). Contrast with
+ * notmuch_thread_get_total_messages() .
+ */
+func (self *Thread) GetMatchedMessages() int {
+       if self.thread == nil {
+               return 0
+       }
+       return int(C.notmuch_thread_get_matched_messages(self.thread))
+}
+
+/**
+ * Get the authors of 'thread' as a UTF-8 string.
+ *
+ * The returned string is a comma-separated list of the names of the
+ * authors of mail messages in the query results that belong to this
+ * thread.
+ *
+ * The string contains authors of messages matching the query first, then
+ * non-matched authors (with the two groups separated by '|'). Within
+ * each group, authors are ordered by date.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+func (self *Thread) GetAuthors() string {
+       if self.thread == nil {
+               return ""
+       }
+       str := C.notmuch_thread_get_authors(self.thread)
+       if str == nil {
+               return ""
+       }
+       return C.GoString(str)
+}
+
+/**
+ * Get the subject of 'thread' as a UTF-8 string.
+ *
+ * The subject is taken from the first message (according to the query
+ * order---see notmuch_query_set_sort) in the query results that
+ * belongs to this thread.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+func (self *Thread) GetSubject() string {
+       if self.thread == nil {
+               return ""
+       }
+       str := C.notmuch_thread_get_subject(self.thread)
+       if str == nil {
+               return ""
+       }
+       return C.GoString(str)
+}
+
+/**
+ * Get the date of the oldest message in 'thread' as a time_t value.
+ */
+func (self *Thread) GetOldestDate() int64 {
+       if self.thread == nil {
+               return 0
+       }
+       date := C.notmuch_thread_get_oldest_date(self.thread)
+
+       return int64(date)
+}
+
+/**
+ * Get the date of the newest message in 'thread' as a time_t value.
+ */
+func (self *Thread) GetNewestDate() int64 {
+       if self.thread == nil {
+               return 0
+       }
+       date := C.notmuch_thread_get_newest_date(self.thread)
+
+       return int64(date)
+}
+
+/**
+ * Get the tags for 'thread', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * Note: In the Notmuch database, tags are stored on individual
+ * messages, not on threads. So the tags returned here will be all
+ * tags of the messages which matched the search and which belong to
+ * this thread.
+ *
+ * The tags object is owned by the thread and as such, will only be
+ * valid for as long as the thread is valid, (for example, until
+ * notmuch_thread_destroy or until the query from which it derived is
+ * destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_thread_t *thread;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     thread = notmuch_threads_get (threads);
+ *
+ *     for (tags = notmuch_thread_get_tags (thread);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_tags_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_thread_destroy (thread);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+func (self *Thread) GetTags() *Tags {
+       if self.thread == nil {
+               return nil
+       }
+
+       tags := C.notmuch_thread_get_tags(self.thread)
+       if tags == nil {
+               return nil
+       }
+
+       return &Tags{tags}
+}
+
+/**
+ * Destroy a notmuch_thread_t object.
+ */
+func (self *Thread) Destroy() {
+       if self.thread != nil {
+               C.notmuch_thread_destroy(self.thread)
+       }
+}
+
+/* Is the given 'messages' iterator pointing at a valid message.
+ *
+ * When this function returns TRUE, notmuch_messages_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_messages_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+func (self *Messages) Valid() bool {
+       if self.messages == nil {
+               return false
+       }
+       valid := C.notmuch_messages_valid(self.messages)
+       if valid == 0 {
+               return false
+       }
+       return true
+}
+
+/* Get the current message from 'messages' as a notmuch_message_t.
+ *
+ * Note: The returned message belongs to 'messages' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+func (self *Messages) Get() *Message {
+       if self.messages == nil {
+               return nil
+       }
+       msg := C.notmuch_messages_get(self.messages)
+       if msg == nil {
+               return nil
+       }
+       return &Message{message: msg}
+}
+
+/* Move the 'messages' iterator to the next message.
+ *
+ * If 'messages' is already pointing at the last message then the
+ * iterator will be moved to a point just beyond that last message,
+ * (where notmuch_messages_valid will return FALSE and
+ * notmuch_messages_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+func (self *Messages) MoveToNext() {
+       if self.messages == nil {
+               return
+       }
+       C.notmuch_messages_move_to_next(self.messages)
+}
+
+/* Destroy a notmuch_messages_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_messages_t object will be reclaimed when the containing
+ * query object is destroyed.
+ */
+func (self *Messages) Destroy() {
+       if self.messages != nil {
+               C.notmuch_messages_destroy(self.messages)
+       }
+}
+
+/* Return a list of tags from all messages.
+ *
+ * The resulting list is guaranteed not to contain duplicated tags.
+ *
+ * WARNING: You can no longer iterate over messages after calling this
+ * function, because the iterator will point at the end of the list.
+ * We do not have a function to reset the iterator yet and the only
+ * way how you can iterate over the list again is to recreate the
+ * message list.
+ *
+ * The function returns NULL on error.
+ */
+func (self *Messages) CollectTags() *Tags {
+       if self.messages == nil {
+               return nil
+       }
+       tags := C.notmuch_messages_collect_tags(self.messages)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* Get the message ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (which is until the query from which it derived
+ * is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message has a unique message ID, (Notmuch will generate an ID for a
+ * message if the original file does not contain one).
+ */
+func (self *Message) GetMessageId() string {
+
+       if self.message == nil {
+               return ""
+       }
+       id := C.notmuch_message_get_message_id(self.message)
+       // we don't own id
+       // defer C.free(unsafe.Pointer(id))
+       if id == nil {
+               return ""
+       }
+       return C.GoString(id)
+}
+
+/* Get the thread ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (for example, until the user calls
+ * notmuch_message_destroy on 'message' or until a query from which it
+ * derived is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message belongs to a single thread.
+ */
+func (self *Message) GetThreadId() string {
+
+       if self.message == nil {
+               return ""
+       }
+       id := C.notmuch_message_get_thread_id(self.message)
+       // we don't own id
+       // defer C.free(unsafe.Pointer(id))
+
+       if id == nil {
+               return ""
+       }
+
+       return C.GoString(id)
+}
+
+/* Get a notmuch_messages_t iterator for all of the replies to
+ * 'message'.
+ *
+ * Note: This call only makes sense if 'message' was ultimately
+ * obtained from a notmuch_thread_t object, (such as by coming
+ * directly from the result of calling notmuch_thread_get_
+ * toplevel_messages or by any number of subsequent
+ * calls to notmuch_message_get_replies).
+ *
+ * If 'message' was obtained through some non-thread means, (such as
+ * by a call to notmuch_query_search_messages), then this function
+ * will return NULL.
+ *
+ * If there are no replies to 'message', this function will return
+ * NULL. (Note that notmuch_messages_valid will accept that NULL
+ * value as legitimate, and simply return FALSE for it.)
+ */
+func (self *Message) GetReplies() *Messages {
+       if self.message == nil {
+               return nil
+       }
+       msgs := C.notmuch_message_get_replies(self.message)
+       if msgs == nil {
+               return nil
+       }
+       return &Messages{messages: msgs}
+}
+
+/* Get a filename for the email corresponding to 'message'.
+ *
+ * The returned filename is an absolute filename, (the initial
+ * component will match notmuch_database_get_path() ).
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Note: If this message corresponds to multiple files in the mail
+ * store, (that is, multiple files contain identical message IDs),
+ * this function will arbitrarily return a single one of those
+ * filenames.
+ */
+func (self *Message) GetFileName() string {
+       if self.message == nil {
+               return ""
+       }
+       fname := C.notmuch_message_get_filename(self.message)
+       // we don't own fname
+       // defer C.free(unsafe.Pointer(fname))
+
+       if fname == nil {
+               return ""
+       }
+
+       return C.GoString(fname)
+}
+
+type Flag C.notmuch_message_flag_t
+
+const (
+       MESSAGE_FLAG_MATCH Flag = 0
+)
+
+/* Get a value of a flag for the email corresponding to 'message'. */
+func (self *Message) GetFlag(flag Flag) bool {
+       if self.message == nil {
+               return false
+       }
+       v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag))
+       if v == 0 {
+               return false
+       }
+       return true
+}
+
+/* Set a value of a flag for the email corresponding to 'message'. */
+func (self *Message) SetFlag(flag Flag, value bool) {
+       if self.message == nil {
+               return
+       }
+       var v C.notmuch_bool_t = 0
+       if value {
+               v = 1
+       }
+       C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v)
+}
+
+/* Get the timestamp (seconds since the epoch) of 'message'.
+ *
+ * Return status:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Timestamp successfully retrieved
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'message' argument is NULL
+ *
+ */
+func (self *Message) GetDate() (int64, Status) {
+       if self.message == nil {
+               return -1, STATUS_NULL_POINTER
+       }
+       timestamp := C.notmuch_message_get_date(self.message)
+       return int64(timestamp), STATUS_SUCCESS
+}
+
+/* Get the value of the specified header from 'message'.
+ *
+ * The value will be read from the actual message file, not from the
+ * notmuch database. The header name is case insensitive.
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Returns an empty string ("") if the message does not contain a
+ * header line matching 'header'. Returns NULL if any error occurs.
+ */
+func (self *Message) GetHeader(header string) string {
+       if self.message == nil {
+               return ""
+       }
+
+       var c_header *C.char = C.CString(header)
+       defer C.free(unsafe.Pointer(c_header))
+
+       /* we don't own value */
+       value := C.notmuch_message_get_header(self.message, c_header)
+       if value == nil {
+               return ""
+       }
+
+       return C.GoString(value)
+}
+
+/* Get the tags for 'message', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * The tags object is owned by the message and as such, will only be
+ * valid for as long as the message is valid, (which is until the
+ * query from which it derived is destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_message_t *message;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     message = notmuch_database_find_message (database, message_id);
+ *
+ *     for (tags = notmuch_message_get_tags (message);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_result_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_message_destroy (message);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+func (self *Message) GetTags() *Tags {
+       if self.message == nil {
+               return nil
+       }
+       tags := C.notmuch_message_get_tags(self.message)
+       if tags == nil {
+               return nil
+       }
+       return &Tags{tags: tags}
+}
+
+/* The longest possible tag value. */
+const TAG_MAX = 200
+
+/* Add a tag to the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) AddTag(tag string) Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       c_tag := C.CString(tag)
+       defer C.free(unsafe.Pointer(c_tag))
+
+       return Status(C.notmuch_message_add_tag(self.message, c_tag))
+}
+
+/* Remove a tag from the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) RemoveTag(tag string) Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       c_tag := C.CString(tag)
+       defer C.free(unsafe.Pointer(c_tag))
+
+       return Status(C.notmuch_message_remove_tag(self.message, c_tag))
+}
+
+/* Remove all tags from the given message.
+ *
+ * See notmuch_message_freeze for an example showing how to safely
+ * replace tag values.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) RemoveAllTags() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       return Status(C.notmuch_message_remove_all_tags(self.message))
+}
+
+/* Freeze the current state of 'message' within the database.
+ *
+ * This means that changes to the message state, (via
+ * notmuch_message_add_tag, notmuch_message_remove_tag, and
+ * notmuch_message_remove_all_tags), will not be committed to the
+ * database until the message is thawed with notmuch_message_thaw.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls will
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * The ability to do freeze/thaw allows for safe transactions to
+ * change tag values. For example, explicitly setting a message to
+ * have a given set of tags might look like this:
+ *
+ *    notmuch_message_freeze (message);
+ *
+ *    notmuch_message_remove_all_tags (message);
+ *
+ *    for (i = 0; i < NUM_TAGS; i++)
+ *        notmuch_message_add_tag (message, tags[i]);
+ *
+ *    notmuch_message_thaw (message);
+ *
+ * With freeze/thaw used like this, the message in the database is
+ * guaranteed to have either the full set of original tag values, or
+ * the full set of new tag values, but nothing in between.
+ *
+ * Imagine the example above without freeze/thaw and the operation
+ * somehow getting interrupted. This could result in the message being
+ * left with no tags if the interruption happened after
+ * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully frozen.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+func (self *Message) Freeze() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+       return Status(C.notmuch_message_freeze(self.message))
+}
+
+/* Thaw the current 'message', synchronizing any changes that may have
+ * occurred while 'message' was frozen into the notmuch database.
+ *
+ * See notmuch_message_freeze for an example of how to use this
+ * function to safely provide tag changes.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls with
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
+ *     its frozen count has successfully been reduced by 1).
+ *
+ * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw
+ *     an unfrozen message. That is, there have been an unbalanced
+ *     number of calls to notmuch_message_freeze and
+ *     notmuch_message_thaw.
+ */
+func (self *Message) Thaw() Status {
+       if self.message == nil {
+               return STATUS_NULL_POINTER
+       }
+
+       return Status(C.notmuch_message_thaw(self.message))
+}
+
+/* Destroy a notmuch_message_t object.
+ *
+ * It can be useful to call this function in the case of a single
+ * query object with many messages in the result, (such as iterating
+ * over the entire database). Otherwise, it's fine to never call this
+ * function and there will still be no memory leaks. (The memory from
+ * the messages get reclaimed when the containing query is destroyed.)
+ */
+func (self *Message) Destroy() {
+       if self.message == nil {
+               return
+       }
+       C.notmuch_message_destroy(self.message)
+}
+
+/* Is the given 'tags' iterator pointing at a valid tag.
+ *
+ * When this function returns TRUE, notmuch_tags_get will return a
+ * valid string. Whereas when this function returns FALSE,
+ * notmuch_tags_get will return NULL.
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) Valid() bool {
+       if self.tags == nil {
+               return false
+       }
+       v := C.notmuch_tags_valid(self.tags)
+       if v == 0 {
+               return false
+       }
+       return true
+}
+
+/* Get the current tag from 'tags' as a string.
+ *
+ * Note: The returned string belongs to 'tags' and has a lifetime
+ * identical to it (and the query to which it ultimately belongs).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) Get() string {
+       if self.tags == nil {
+               return ""
+       }
+       s := C.notmuch_tags_get(self.tags)
+       // we don't own 's'
+
+       return C.GoString(s)
+}
+func (self *Tags) String() string {
+       return self.Get()
+}
+
+/* Move the 'tags' iterator to the next tag.
+ *
+ * If 'tags' is already pointing at the last tag then the iterator
+ * will be moved to a point just beyond that last tag, (where
+ * notmuch_tags_valid will return FALSE and notmuch_tags_get will
+ * return NULL).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+func (self *Tags) MoveToNext() {
+       if self.tags == nil {
+               return
+       }
+       C.notmuch_tags_move_to_next(self.tags)
+}
+
+/* Destroy a notmuch_tags_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_tags_t object will be reclaimed when the containing
+ * message or query objects are destroyed.
+ */
+func (self *Tags) Destroy() {
+       if self.tags == nil {
+               return
+       }
+       C.notmuch_tags_destroy(self.tags)
+}
+
+// TODO: wrap notmuch_directory_<fct>
+
+/* Destroy a notmuch_directory_t object. */
+func (self *Directory) Destroy() {
+       if self.dir == nil {
+               return
+       }
+       C.notmuch_directory_destroy(self.dir)
+}
+
+// TODO: wrap notmuch_filenames_<fct>
+
+/* Destroy a notmuch_filenames_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_filenames_t object will be reclaimed when the
+ * containing directory object is destroyed.
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will do nothing.
+ */
+func (self *Filenames) Destroy() {
+       if self.fnames == nil {
+               return
+       }
+       C.notmuch_filenames_destroy(self.fnames)
+}
+
+/* EOF */
diff --git a/contrib/notmuch-mutt/.gitignore b/contrib/notmuch-mutt/.gitignore
new file mode 100644 (file)
index 0000000..116bb71
--- /dev/null
@@ -0,0 +1,2 @@
+/notmuch-mutt.1
+/README.html
diff --git a/contrib/notmuch-mutt/Makefile b/contrib/notmuch-mutt/Makefile
new file mode 100644 (file)
index 0000000..855438b
--- /dev/null
@@ -0,0 +1,25 @@
+NAME = notmuch-mutt
+
+-include ../../Makefile.config
+PERL_ABSOLUTE ?= $(shell command -v perl 2>/dev/null)
+prefix ?= /usr/local
+sysconfdir ?= ${prefix}/etc
+mandir ?= ${prefix}/share/man
+
+all: $(NAME) $(NAME).1
+
+$(NAME).1: $(NAME)
+       pod2man $< > $@
+
+README.html: README
+       markdown $< > $@
+
+install: all
+       mkdir -p $(DESTDIR)$(prefix)/bin
+       sed "1s|^#!.*|#! $(PERL_ABSOLUTE)|" < $(NAME) > $(DESTDIR)$(prefix)/bin/$(NAME)
+       chmod 755 $(DESTDIR)$(prefix)/bin/$(NAME)
+       install -D -m 644 $(NAME).1 $(DESTDIR)$(mandir)/man1/$(NAME).1
+       install -D -m 644 $(NAME).rc $(DESTDIR)$(sysconfdir)/Muttrc.d/$(NAME).rc
+
+clean:
+       rm -f notmuch-mutt.1 README.html
diff --git a/contrib/notmuch-mutt/README b/contrib/notmuch-mutt/README
new file mode 100644 (file)
index 0000000..26996c4
--- /dev/null
@@ -0,0 +1,61 @@
+notmuch-mutt: Notmuch (of a) helper for Mutt
+============================================
+
+notmuch-mutt provide integration among the [Mutt] [1] mail user agent and the
+[Notmuch] [2] mail indexer.
+
+notmuch-mutt offer two main integration features. The first one is the ability
+of stating a **search query interactively** and then jump to a fresh Maildir
+containing its search results only. The second one is the ability to
+**reconstruct threads on the fly** starting from the currently highlighted
+mail, which comes handy when a thread has been split across different maildirs,
+archived, or the like.
+
+notmuch-mutt enables to trigger mail searches via a Mutt macro (usually F8) and
+reconstruct threads via another (usually F9). Check the manpage for the 2-liner
+configuration snippet for your Mutt configuration files (~/.muttrc,
+/etc/Muttrc, or a /etc/Muttrc.d snippet).
+
+A [blog style introduction] [3] to notmuch-mutt is available and includes some
+more rationale for its existence.
+
+Arguably, some of the logics of notmuch-mutt could disappear by adding support
+for a --output=symlinks flag to notmuch.
+
+
+[1]: http://www.mutt.org/
+[2]: https://notmuchmail.org/
+[3]: https://upsilon.cc/~zack/blog/posts/2011/01/how_to_use_Notmuch_with_Mutt/
+
+
+Requirements
+------------
+
+To *run* notmuch-mutt you will need Perl with the following libraries:
+
+- Digest::SHA <https://metacpan.org/release/Digest-SHA>
+  (Debian package: libdigest-sha-perl)
+- Mail::Box <https://metacpan.org/pod/Mail::Box>
+  (Debian package: libmail-box-perl)
+- Mail::Header <https://metacpan.org/pod/Mail::Header>
+  (Debian package: libmailtools-perl)
+- String::ShellQuote <https://metacpan.org/pod/String::ShellQuote>
+  (Debian package: libstring-shellquote-perl)
+- Term::ReadLine::Gnu <https://metacpan.org/pod/Term::ReadLine::Gnu>
+  (Debian package: libterm-readline-gnu-perl)
+
+To *build* notmuch-mutt documentation you will need:
+
+- pod2man (coming with Perl) to generate the manpage
+- markdown to generate README.html out of this file
+
+
+License
+-------
+
+notmuch-mutt is copyright (C) 2011-2012 Stefano Zacchiroli <zack@upsilon.cc>.
+
+notmuch-mutt is released under the terms of the GNU General Public License
+(GPL), version 3 or above. A copy of the license is available online at
+<https://www.gnu.org/licenses/>.
+
diff --git a/contrib/notmuch-mutt/notmuch-mutt b/contrib/notmuch-mutt/notmuch-mutt
new file mode 100755 (executable)
index 0000000..0e46a8c
--- /dev/null
@@ -0,0 +1,289 @@
+#!/usr/bin/env perl
+#
+# notmuch-mutt - notmuch (of a) helper for Mutt
+#
+# Copyright: © 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
+# License: GNU General Public License (GPL), version 3 or above
+#
+# See the bottom of this file for more documentation.
+# A manpage can be obtained by running "pod2man notmuch-mutt > notmuch-mutt.1"
+
+use strict;
+use warnings;
+
+use File::Path;
+use Getopt::Long qw(:config no_getopt_compat);
+use Mail::Header;
+use Mail::Box::Maildir;
+use Pod::Usage;
+use String::ShellQuote;
+use Term::ReadLine;
+use Digest::SHA;
+
+
+my $xdg_cache_dir = "$ENV{HOME}/.cache";
+$xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME};
+my $cache_dir = "$xdg_cache_dir/notmuch/mutt";
+
+
+# create an empty maildir (if missing) or empty an existing maildir"
+sub empty_maildir($) {
+    my ($maildir) = (@_);
+    rmtree($maildir) if (-d $maildir);
+    my $folder = new Mail::Box::Maildir(folder => $maildir,
+                                       create => 1);
+    $folder->close();
+}
+
+# search($maildir, $remove_dups, $query)
+# search mails according to $query with notmuch; store results in $maildir
+sub search($$$) {
+    my ($maildir, $remove_dups, $query) = @_;
+    my $dup_option = "";
+
+    $query = shell_quote($query);
+
+    if ($remove_dups) {
+      $dup_option = "--duplicate=1";
+    }
+
+    empty_maildir($maildir);
+    system("notmuch search --output=files $dup_option $query"
+          . " | sed -e 's: :\\\\ :g'"
+          . " | xargs -r -I searchoutput ln -s searchoutput $maildir/cur/");
+}
+
+sub prompt($$) {
+    my ($text, $default) = @_;
+    my $query = "";
+    my $term = Term::ReadLine->new( "notmuch-mutt" );
+    my $histfile = "$cache_dir/history";
+
+    $term->ornaments( 0 );
+    $term->unbind_key( ord( "\t" ) );
+    $term->MinLine( 3 );
+    $histfile = $ENV{MUTT_NOTMUCH_HISTFILE} if $ENV{MUTT_NOTMUCH_HISTFILE};
+    $term->ReadHistory($histfile) if (-r $histfile);
+    while (1) {
+       chomp($query = $term->readline($text, $default));
+       if ($query eq "?") {
+           system("man", "notmuch-search-terms");
+       } else {
+           $term->WriteHistory($histfile);
+           return $query;
+       }
+    }
+}
+
+sub get_message_id() {
+    my $mid = undef;
+    my @headers = ();
+
+    while (<STDIN>) {  # collect header lines in @headers
+       push(@headers, $_);
+       last if $_ =~ /^$/;
+    }
+    my $head = Mail::Header->new(\@headers);
+    $mid = $head->get("message-id") or undef;
+
+    if ($mid) {  # Message-ID header found
+       $mid =~ /^<(.*)>$/;  # extract message id
+       $mid = $1;
+    } else {  # Message-ID header not found, synthesize a message id
+             # based on SHA1, as notmuch would do.  See:
+             # https://git.notmuchmail.org/git/notmuch/blob/HEAD:/lib/sha1.c
+       my $sha = Digest::SHA->new(1);
+       $sha->add($_) foreach(@headers);
+       $sha->addfile(\*STDIN);
+       $mid = 'notmuch-sha1-' . $sha->hexdigest;
+    }
+
+    return $mid;
+}
+
+sub search_action($$$@) {
+    my ($interactive, $results_dir, $remove_dups, @params) = @_;
+
+    if (! $interactive) {
+       search($results_dir, $remove_dups, join(' ', @params));
+    } else {
+       my $query = prompt("search ('?' for man): ", join(' ', @params));
+       if ($query ne "") {
+           search($results_dir, $remove_dups, $query);
+       }
+    }
+}
+
+sub thread_action($$@) {
+    my ($results_dir, $remove_dups, @params) = @_;
+
+    my $mid = get_message_id();
+    if (! defined $mid) {
+       empty_maildir($results_dir);
+       die "notmuch-mutt: cannot find Message-Id, abort.\n";
+    }
+    my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid");
+    my $tid = `$search_cmd`;   # get thread id
+    chomp($tid);
+
+    search($results_dir, $remove_dups, $tid);
+}
+
+sub tag_action(@) {
+    my $mid = get_message_id();
+    defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
+
+    system("notmuch", "tag", @_, "--", "id:$mid");
+}
+
+sub die_usage() {
+    my %podflags = ( "verbose" => 1,
+                   "exitval" => 2 );
+    pod2usage(%podflags);
+}
+
+sub main() {
+    mkpath($cache_dir) unless (-d $cache_dir);
+
+    my $results_dir = "$cache_dir/results";
+    my $interactive = 0;
+    my $help_needed = 0;
+    my $remove_dups = 0;
+
+    my $getopt = GetOptions(
+       "h|help" => \$help_needed,
+       "o|output-dir=s" => \$results_dir,
+       "p|prompt" => \$interactive,
+       "r|remove-dups" => \$remove_dups);
+    if (! $getopt || $#ARGV < 0) { die_usage() };
+    my ($action, @params) = ($ARGV[0], @ARGV[1..$#ARGV]);
+
+    foreach my $param (@params) {
+      $param =~ s/folder:=/folder:/g;
+    }
+
+    if ($help_needed) {
+       die_usage();
+    } elsif ($action eq "search" && $#ARGV == 0 && ! $interactive) {
+       print STDERR "Error: no search term provided\n\n";
+       die_usage();
+    } elsif ($action eq "search") {
+       search_action($interactive, $results_dir, $remove_dups, @params);
+    } elsif ($action eq "thread") {
+       thread_action($results_dir, $remove_dups, @params);
+    } elsif ($action eq "tag") {
+       tag_action(@params);
+    } else {
+       die_usage();
+    }
+}
+
+main();
+
+__END__
+
+=head1 NAME
+
+notmuch-mutt - notmuch (of a) helper for Mutt
+
+=head1 SYNOPSIS
+
+=over
+
+=item B<notmuch-mutt> [I<OPTION>]... search [I<SEARCH-TERM>]...
+
+=item B<notmuch-mutt> [I<OPTION>]... thread < I<MAIL>
+
+=item B<notmuch-mutt> [I<OPTION>]... tag [I<TAGS>]... < I<MAIL>
+
+=back
+
+=head1 DESCRIPTION
+
+notmuch-mutt is a frontend to the notmuch mail indexer capable of populating
+a maildir with search results.
+
+=head1 OPTIONS
+
+=over 4
+
+=item -o DIR
+
+=item --output-dir DIR
+
+Store search results as (symlink) messages under maildir DIR. Beware: DIR will
+be overwritten. (Default: F<~/.cache/notmuch/mutt/results/>)
+
+=item -p
+
+=item --prompt
+
+Instead of using command line search terms, prompt the user for them (only for
+"search").
+
+=item -r
+
+=item --remove-dups
+
+Remove emails with duplicate message-ids from search results.  (Passes
+--duplicate=1 to notmuch search command.)  Note this can hide search
+results if an email accidentally or maliciously uses the same message-id
+as a different email.
+
+=item -h
+
+=item --help
+
+Show usage information and exit.
+
+=back
+
+=head1 INTEGRATION WITH MUTT
+
+notmuch-mutt can be used to integrate notmuch with the Mutt mail user agent
+(unsurprisingly, given the name). To that end, you should define macros like
+the following in your Mutt configuration (usually one of: F<~/.muttrc>,
+F</etc/Muttrc>, or a configuration snippet under F</etc/Muttrc.d/>):
+
+    macro index <F8> \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <shell-escape>notmuch-mutt -r --prompt search<enter>\
+    <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+          "notmuch: search mail"
+
+    macro index <F9> \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <pipe-message>notmuch-mutt -r thread<enter>\
+    <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+          "notmuch: reconstruct thread"
+
+    macro index <F6> \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <pipe-message>notmuch-mutt tag -- -inbox<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+          "notmuch: remove message from inbox"
+
+The first macro (activated by <F8>) prompts the user for notmuch search terms
+and then jump to a temporary maildir showing search results. The second macro
+(activated by <F9>) reconstructs the thread corresponding to the current mail
+and show it as search results. The third macro (activated by <F6>) removes the
+tag C<inbox> from the current message; by changing C<-inbox> this macro may be
+customised to add or remove tags appropriate to the users notmuch work-flow.
+
+To keep notmuch index current you should then periodically run C<notmuch
+new>. Depending on your local mail setup, you might want to do that via cron,
+as a hook triggered by mail retrieval, etc.
+
+=head1 SEE ALSO
+
+mutt(1), notmuch(1)
+
+=head1 AUTHOR
+
+Copyright: (C) 2011-2012 Stefano Zacchiroli <zack@upsilon.cc>
+
+License: GNU General Public License (GPL), version 3 or higher
+
+=cut
diff --git a/contrib/notmuch-mutt/notmuch-mutt.rc b/contrib/notmuch-mutt/notmuch-mutt.rc
new file mode 100644 (file)
index 0000000..6b299dc
--- /dev/null
@@ -0,0 +1,19 @@
+macro index <F8> \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<shell-escape>notmuch-mutt -r --prompt search<enter>\
+<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+      "notmuch: search mail"
+
+macro index <F9> \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<pipe-message>notmuch-mutt -r thread<enter>\
+<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+      "notmuch: reconstruct thread"
+
+macro index <F6> \
+"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+<pipe-message>notmuch-mutt tag -- -inbox<enter>\
+<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
+      "notmuch: remove message from inbox"
diff --git a/control b/control
deleted file mode 100644 (file)
index 922f6d8..0000000
--- a/control
+++ /dev/null
@@ -1,166 +0,0 @@
-Source: notmuch
-Section: mail
-Priority: optional
-Maintainer: Carl Worth <cworth@debian.org>
-Uploaders:
- Jameson Graef Rollins <jrollins@finestructure.net>,
- David Bremner <bremner@debian.org>
-Build-Conflicts: ruby1.8, gdb-minimal, gdb [ia64 mips mips64el]
-Build-Depends:
- dpkg-dev (>= 1.17.14),
- debhelper (>= 11~),
- pkg-config,
- libxapian-dev,
- libgmime-3.0-dev (>= 3.0.3~) | libgmime-2.6-dev (>= 2.6.7~),
- libtalloc-dev,
- libz-dev,
- python-all (>= 2.6.6-3~),
- python3-all (>= 3.1.2-7~),
- dh-python,
- dh-elpa (>= 1.3),
- python3-sphinx,
- ruby, ruby-dev (>>1:1.9.3~),
- emacs-nox | emacs-gtk | emacs-lucid |
- emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) |
- emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
- gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] <!nocheck>,
- dtach (>= 0.8) <!nocheck>,
- gpgsm <!nocheck>,
- gnupg <!nocheck>,
- bash-completion (>=1.9.0~),
- texinfo
-Standards-Version: 4.1.3
-Homepage: https://notmuchmail.org/
-Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release
-Vcs-Browser: https://git.notmuchmail.org/git/notmuch
-
-Package: notmuch
-Architecture: any
-Depends: libnotmuch5 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
-Recommends: elpa-notmuch | notmuch-vim | notmuch-mutt | alot,  gnupg-agent, gpgsm
-Description: thread-based email index, search and tagging
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package contains the notmuch command-line interface
-
-Package: libnotmuch5
-Section: libs
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}
-Pre-Depends: ${misc:Pre-Depends}
-Description: thread-based email index, search and tagging (runtime)
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package contains the runtime library, necessary to run
- applications using libnotmuch.
-
-Package: libnotmuch-dev
-Section: libdevel
-Architecture: any
-Depends: ${misc:Depends}, libnotmuch5 (= ${binary:Version})
-Description: thread-based email index, search and tagging (development)
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides the necessary development libraries and header
- files to allow you to develop new software using libnotmuch.
-
-Package: python-notmuch
-Architecture: all
-Section: python
-Depends: ${misc:Depends}, ${python:Depends}, libnotmuch5 (>= ${source:Version})
-Description: Python interface to the notmuch mail search and index library
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a Python interface to the notmuch
- functionality, directly interfacing with a shared notmuch library.
-
-Package: python3-notmuch
-Architecture: all
-Section: python
-Depends: ${misc:Depends}, ${python3:Depends}, libnotmuch5 (>= ${source:Version})
-Description: Python 3 interface to the notmuch mail search and index library
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a Python 3 interface to the notmuch
- functionality, directly interfacing with a shared notmuch library.
-
-Package: ruby-notmuch
-Architecture: any
-Section: ruby
-Depends: ${shlibs:Depends}, ${misc:Depends}
-Description: Ruby interface to the notmuch mail search and index library
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a Ruby interface to the notmuch
- functionality, directly interfacing with a shared notmuch library.
-
-Package: notmuch-emacs
-Section: oldlibs
-Architecture: all
-Depends: elpa-notmuch, ${misc:Depends}
-Description: thread-based email index, search and tagging (transitional package)
- This dummy package help ease transition to the new package elpa-notmuch
-
-Package: elpa-notmuch
-Architecture: all
-Depends: ${misc:Depends}, ${elpa:Depends}
-Description: thread-based email index, search and tagging (emacs interface)
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides an emacs based mail user agent based on
- notmuch.
-
-Package: notmuch-vim
-Architecture: all
-Breaks: notmuch (<<0.6~254~)
-Replaces: notmuch (<<0.6~254~)
-Depends: ${misc:Depends}, notmuch, vim-addon-manager, vim-ruby, ruby-notmuch
-Recommends: ruby-mail
-Description: thread-based email index, search and tagging (vim interface)
- Notmuch is a system for indexing, searching, reading, and tagging
- large collections of email messages in maildir or mh format. It uses
- the Xapian library to provide fast, full-text search with a very
- convenient search syntax.
- .
- This package provides a vim based mail user agent based on
- notmuch.
-
-Package: notmuch-mutt
-Architecture: all
-Depends:
- notmuch (>= 0.4),
- libmail-box-perl, libmailtools-perl,
- libstring-shellquote-perl, libterm-readline-gnu-perl,
- ${misc:Depends}
-Recommends: mutt
-Enhances: notmuch, mutt
-Description: thread-based email index, search and tagging (Mutt interface)
- notmuch-mutt provides integration among the Mutt mail user agent and
- the Notmuch mail indexer.
- .
- notmuch-mutt offer two main integration features. The first one is
- the ability of stating a search query interactively and then jump to
- a fresh Maildir containing its search results only. The second one is
- the ability to reconstruct threads on the fly starting from the
- current highlighted mail.
diff --git a/copyright b/copyright
deleted file mode 100644 (file)
index 0931d9b..0000000
--- a/copyright
+++ /dev/null
@@ -1,48 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: notmuch
-Source: git://notmuchmail.org/git/notmuch
-Upstream-Contact: Notmuch Mailing List <notmuch@notmuchmail.org>
-
-Files: *
-Copyright:  Copyright 2009 Carl Worth <cworth@cworth.org>
- Bart Trojanowski <bart@jukie.net>
- Keith Packard <keithp@keithp.com>
- Alexander Botero-Lowry <alex.boterolowry@gmail.com>
- Ingmar Vanhassel <ingmar@exherbo.org>
- Jed Brown <jed@59A2.org>
- Jan Janak <jan@ryngle.com>
- Chris Wilson <chris@chris-wilson.co.uk>
- Keith Amidon <keith@nicira.com>
- Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
- Mikhail Gusarov <dottedmag@dottedmag.net>
- Jeffrey C. Ollie <jeff@ocjtech.us>
- Jameson Graef Rollins <jrollins@finestructure.net>
- Stewart Smith <stewart@flamingspork.com>
- Adrian Perez <aperez@igalia.com>
- Kan-Ru Chen <kanru@kanru.info>
- James Rowe <jnrowe@gmail.com>
- Eric Anholt <eric@anholt.net>
- Alec Berryman <alec@thened.net>
- Tassilo Horn <tassilo@member.fsf.org>
- Stefan Schmidt <stefan@datenfreihafen.org>
- Rolland Santimano <rollandsantimano@yahoo.com>
- Peter Wang <novalazy@gmail.com>
- Lars Kellogg-Stedman <lars@seas.harvard.edu>
- Holger Freyther <zecke@selfish.org>
- David Bremner <bremner@unb.ca>
- Alexander Botero-Lowry <alexbl@fortitudo.(none)>
-License: GPL-3+
-
-Files: debian/*
-Copyright:  Copyright 2010 Jameson Graef Rollins <jrollins@finestructure.net>
- martin f. krafft <madduck@debian.org>
-License: GPL-3+
-
-License: GPL-3+
- This package 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.
- .
- On Debian systems, the complete text of the GNU General Public License
- version 3 can be found in file "/usr/share/common-licenses/GPL-3".
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644 (file)
index 0000000..cd0decc
--- /dev/null
@@ -0,0 +1,14 @@
+/tmp/
+/libnotmuch-dev/
+/libnotmuch*/
+/notmuch-emacs/
+/elpa-notmuch/
+/notmuch/
+/notmuch-mutt/
+/notmuch-vim/
+/ruby-notmuch/
+/python*-notmuch/
+/*.debhelper
+/*.debhelper.log
+/*.substvars
+/files
diff --git a/debian/NEWS b/debian/NEWS
new file mode 100644 (file)
index 0000000..bf661fe
--- /dev/null
@@ -0,0 +1,74 @@
+notmuch (0.21~rc1-1) experimental; urgency=medium
+
+  This release of notmuch requires a non-reversible database upgrade
+  to support database revision tracking. This upgrade will happen on
+  the first run of 'notmuch-new' after updating. Notmuch will backup
+  your tags for your before doing the upgrade, but it never hurts to
+  make your own backup with notmuch dump.
+
+ -- David Bremner <bremner@debian.org>  Thu, 15 Oct 2015 08:13:04 -0300
+
+notmuch (0.19-1) experimental; urgency=medium
+
+  This release of notmuch again requires a non-reversable database
+  upgrade to support database features. This upgrade will happen on
+  the first run of 'notmuch-new' after updating. Notmuch will backup
+  your tags for your before doing the upgrade, but it never hurts to
+  make your own backup with notmuch dump.
+
+ -- David Bremner <bremner@debian.org>  Fri, 14 Nov 2014 20:34:55 +0100
+
+notmuch (0.18~rc0-1) experimental; urgency=low
+
+  This release of notmuch requires a non-reversable database upgrade
+  to support the new path: and updated folder: prefixes. Notmuch
+  will backup your tags for your before doing the upgrade, but it
+  never hurts to make your own backup with notmuch dump before
+  next running 'notmuch new'
+
+ -- David Bremner <bremner@debian.org>  Tue, 22 Apr 2014 09:32:11 +0900
+
+notmuch (0.17-1) unstable; urgency=low
+
+  Previously on big endian architectures like sparc and powerpc the
+  computation of SHA1 hashes was incorrect. This meant that messages
+  with overlong or missing message-ids were given different computed
+  message-ids than on more common little endian architectures like
+  i386 and amd64.  If you use notmuch on a big endian architecture,
+  you are strongly advised to make a backup of your tags using
+  `notmuch dump` before this upgrade.  You can locate the affected
+  files using something like:
+
+  notmuch dump | \
+    awk '/^notmuch-sha1-[0-9a-f]{40} / \
+      {system("notmuch search --exclude=false --output=files id:" $1)}'
+
+ -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:31:16 -0400
+
+notmuch (0.16-1) unstable; urgency=low
+
+  The vim interface has been rewritten from scratch. In particular
+  it requires a version of vim with ruby support.
+
+ -- David Bremner <bremner@debian.org>  Sat, 16 Feb 2013 08:12:02 -0400
+
+notmuch (0.14-1) unstable; urgency=low
+
+  There is an incompatible change in option syntax for dump and restore
+  in this release. Please update your scripts.
+
+  From upstream NEWS:
+
+  The deprecated positional output file argument to notmuch dump has
+  been replaced with an --output option. The input file positional
+  argument for restore has been replaced with an --input option for
+  consistency with dump.
+
+ -- David Bremner <bremner@debian.org>  Sun, 05 Aug 2012 11:52:49 -0300
+
+notmuch (0.6~238) unstable; urgency=low
+
+  The emacs user interface to notmuch is now contained in a separate
+  package called notmuch-emacs.
+
+ -- David Bremner <bremner@debian.org>  Mon, 20 Jun 2011 23:57:55 -0300
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..9ed2547
--- /dev/null
@@ -0,0 +1,1294 @@
+notmuch (0.28.2-1) unstable; urgency=medium
+
+  * [notmuch-emacs] Invoke gpg from with --batch and --no-tty
+
+ -- David Bremner <bremner@debian.org>  Sun, 17 Feb 2019 07:30:33 -0400
+
+notmuch (0.28.1-1) unstable; urgency=medium
+
+  * New upstream bug fix release
+  * Bug fix: "muttprint/evince fails to show "print", thanks to
+    Joerg Jaspert (Closes: #920856).
+
+ -- David Bremner <bremner@debian.org>  Fri, 01 Feb 2019 08:05:05 -0400
+
+notmuch (0.28-2) unstable; urgency=medium
+
+  * Override location of bash, because /usr/bin/bash might exist
+    thanks to usrmerge.
+
+ -- David Bremner <bremner@debian.org>  Fri, 12 Oct 2018 20:54:00 -0300
+
+notmuch (0.28-1) unstable; urgency=medium
+
+  * New upstream releases.
+  * Includes threading fixes, support for relative database paths, and
+    rewritten zsh completion.
+
+ -- David Bremner <bremner@debian.org>  Fri, 12 Oct 2018 20:17:27 -0300
+
+notmuch (0.28~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate.
+
+ -- David Bremner <bremner@debian.org>  Wed, 03 Oct 2018 20:36:57 -0300
+
+notmuch (0.27-3) unstable; urgency=medium
+
+  * Update Vcs-Git to use https and specify correct branch
+  * Update Build-depends for unversioned emacs packages.
+
+ -- David Bremner <bremner@debian.org>  Sat, 08 Sep 2018 18:20:10 -0300
+
+notmuch (0.27-2) unstable; urgency=medium
+
+  * Add texinfo as a build-dep, build info version of documentation.
+
+ -- David Bremner <bremner@debian.org>  Thu, 28 Jun 2018 21:01:29 -0300
+
+notmuch (0.27-1) unstable; urgency=medium
+
+  * New upstream feature release
+    - thread subqueries (match terms in different messages of same thread)
+    - notmuch new --full-scan (ignore mtimes)
+    - notmuch show --decrypt=stash (decrypt and stash on first read)
+
+ -- David Bremner <bremner@debian.org>  Tue, 12 Jun 2018 22:39:33 -0300
+
+notmuch (0.27~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 31 May 2018 08:19:00 -0300
+
+notmuch (0.27~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 26 May 2018 09:12:37 -0700
+
+notmuch (0.26.2-2) unstable; urgency=medium
+
+  * Mark gdb and dtach as <!nocheck>, meaning they are only needed for
+    the test suite.
+  * Re-enable gdb based tests on s390x, ppc64el, armel, mipsel, they
+    pass on porterbox. Leave disabled on mipsel64 (gdb tests still
+    failing), and mips (many tests fail on porterbox).
+
+ -- David Bremner <bremner@debian.org>  Sun, 06 May 2018 08:36:52 -0300
+
+notmuch (0.26.2-1) unstable; urgency=medium
+
+  * Upstream bugfix release
+    - Break reference loops when indexing, fixes INTERNAL_ERROR in "notmuch show"
+    - Don't call get_mset(0,0,X), fixes crash on some gcc8 using distros
+
+ -- David Bremner <bremner@debian.org>  Sat, 28 Apr 2018 08:10:24 -0300
+
+notmuch (0.26.1-1) unstable; urgency=medium
+
+  * Bump LIBRARY_MINOR_VERSION to 1, for new functions in 0.26
+
+ -- David Bremner <bremner@debian.org>  Mon, 02 Apr 2018 08:08:01 -0300
+
+notmuch (0.26-1) unstable; urgency=medium
+
+  [ Daniel Kahn Gillmor ]
+  * build against python3-sphinx instead of python-sphinx
+  * d/changelog: strip trailing whitespace
+  * move to debhelper 10
+  * Standards-Version: bump to 4.1.3 (drop priority: extra
+    from transitional packages)
+
+  [ David Bremner ]
+  * New upstream release (see /usr/share/doc/notmuch/NEWS.gz)
+    - new command 'notmuch reindex'
+    - optional indexing of encrypted emails.
+    - indexing of files with duplicate message-id
+  * update symbols
+
+ -- David Bremner <bremner@debian.org>  Tue, 09 Jan 2018 07:13:21 -0400
+
+notmuch (0.26~rc2-1) experimental; urgency=medium
+
+  * Third upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Tue, 09 Jan 2018 07:13:11 -0400
+
+notmuch (0.26~rc1-1) experimental; urgency=medium
+
+  * Second upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Mon, 01 Jan 2018 21:17:39 -0400
+
+notmuch (0.26~rc0-1) experimental; urgency=medium
+
+  * Upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 28 Dec 2017 10:21:08 -0400
+
+notmuch (0.25.3-1) unstable; urgency=medium
+
+  * Upstream bugfix release. Fix for OpenPGP UID validity reporting,
+    and build failure with GMime 3.0.3+.
+  * Bug fix: "notmuch FTBFS on Alpha due to broken gdb", thanks to
+    Michael Cree (Closes: #881028).
+
+ -- David Bremner <bremner@debian.org>  Fri, 08 Dec 2017 21:08:00 -0400
+
+notmuch (0.25.2-1) unstable; urgency=medium
+
+  * New upstream bugfix release: fix for segfault when compiled
+    against gmime-2.6
+
+ -- David Bremner <bremner@debian.org>  Sun, 05 Nov 2017 20:05:49 -0400
+
+notmuch (0.25.1-1) unstable; urgency=medium
+
+  * new upstream bugfix release: mitigation for emacs bug 28350
+  * remove obsolete lintian override
+  * reformat debian/NEWS
+
+ -- David Bremner <bremner@debian.org>  Mon, 11 Sep 2017 22:20:48 -0300
+
+notmuch (0.25-6) unstable; urgency=medium
+
+  * Bug fix: "deletes shipped file on reinstall:
+    /etc/emacs/site-start.d/50notmuch.el", thanks to Andreas Beckmann
+    (Closes: #872197).
+
+ -- David Bremner <bremner@debian.org>  Tue, 15 Aug 2017 07:52:21 -0300
+
+notmuch (0.25-5) unstable; urgency=medium
+
+  * Bug fix: "dependency on elpa-emacs doesn't seem right", thanks
+    to Jiri Palecek (Closes: #871642).
+
+ -- David Bremner <bremner@debian.org>  Thu, 10 Aug 2017 06:42:50 -0400
+
+notmuch (0.25-4) unstable; urgency=medium
+
+  * Recommend elpa-emacs instead emacs-notmuch
+
+ -- David Bremner <bremner@debian.org>  Fri, 04 Aug 2017 18:11:35 -0400
+
+notmuch (0.25-3) unstable; urgency=medium
+
+  * Remove old startup file /etc/emacs/site-start.d/50notmuch.el
+
+ -- David Bremner <bremner@debian.org>  Thu, 03 Aug 2017 09:26:00 -0400
+
+notmuch (0.25-2) unstable; urgency=medium
+
+  * Drop build-dep on libgmime-2.4-dev, long unsupported upstream
+  * Bug fix: "please transition to gmime 3.0", thanks to Daniel Kahn
+    Gillmor (Closes: #867353).
+
+ -- David Bremner <bremner@debian.org>  Wed, 26 Jul 2017 10:59:14 -0400
+
+notmuch (0.25-1) unstable; urgency=medium
+
+  * New upstream release
+    - regexp search for mid, paths, tags
+    - drop inline images when indexing html
+
+ -- David Bremner <bremner@debian.org>  Tue, 25 Jul 2017 08:28:20 -0300
+
+notmuch (0.25~rc1-2) unstable; urgency=low
+
+  * upload to unstable
+
+ -- David Bremner <bremner@debian.org>  Tue, 18 Jul 2017 19:47:45 -0300
+
+notmuch (0.25~rc1-1) experimental; urgency=medium
+
+  * Bump standards version to 4.0.0 (no changes needed)
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Tue, 18 Jul 2017 07:04:14 -0300
+
+notmuch (0.25~rc0-2) experimental; urgency=medium
+
+  * Fix compilation on 32 bit architectures (time_t vs. gint64)
+
+ -- David Bremner <bremner@debian.org>  Mon, 17 Jul 2017 08:49:32 -0300
+
+notmuch (0.25~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Drop notmuch-dbg, use notmuch-dbgsym if debug symbols are needed.
+  * [lib] Bump SONAME to libnotmuch.so.5
+  * Bug fix: "doesn't check gpg/pgp signatures by default", thanks
+    to Vagrant Cascadian (Closes: #755544).
+  * Bug fix: "allow separation of command-line options and their
+    values: --option foo", thanks to Paul Wise (Closes: #825886).
+
+ -- David Bremner <bremner@debian.org>  Sun, 16 Jul 2017 08:48:59 -0300
+
+notmuch (0.24.2-2) unstable; urgency=medium
+
+  * rebuild for unstable
+
+ -- David Bremner <bremner@debian.org>  Sun, 02 Jul 2017 12:48:46 -0300
+
+notmuch (0.24.2-1) experimental; urgency=medium
+
+  * Fix dump output to not include tags when not asked for
+  * Fix file name stashing in emacs tree view.
+
+ -- David Bremner <bremner@debian.org>  Thu, 01 Jun 2017 07:24:55 -0300
+
+notmuch (0.24.1-1) experimental; urgency=medium
+
+  * Restore Xapian wildcard queries to from: and subject:
+  * Handle empty queries for from: and subject:
+  * Memory leaks in notmuch show fixed
+  * Fix bug notmuch dump header generation
+
+ -- David Bremner <bremner@debian.org>  Sat, 01 Apr 2017 09:17:47 -0300
+
+notmuch (0.24-1) experimental; urgency=medium
+
+  * New upstream release
+    - regexp search for from: and subject:
+    - Emacs interface improvements:
+      - draft handling
+      - don't automatically expand application/*
+      - jump (shortcut) menu for tagging.
+      - fold long headers when sending
+    - library improvements
+      - catch some stray DatabaseModifiedErrors
+      - make exclude handling non-destructive.
+
+ -- David Bremner <bremner@debian.org>  Sun, 12 Mar 2017 22:14:25 -0300
+
+notmuch (0.24~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * upstream release notes
+  * One library internals fix/optimization for regexp processing.
+
+ -- David Bremner <bremner@debian.org>  Wed, 08 Mar 2017 08:08:34 -0400
+
+notmuch (0.24~rc0-1) experimental; urgency=medium
+
+  * New upstream feature release (candidate).
+
+ -- David Bremner <bremner@debian.org>  Sun, 05 Mar 2017 19:32:08 -0400
+
+notmuch (0.23.7-3) unstable; urgency=medium
+
+  * Cherry pick fixes to dump header from 0.24.1
+
+ -- David Bremner <bremner@debian.org>  Sat, 01 Apr 2017 21:09:36 -0300
+
+notmuch (0.23.7-2) unstable; urgency=medium
+
+  * Cherry pick 06adc276, fix use after free in libnotmuch4
+
+ -- David Bremner <bremner@debian.org>  Sun, 19 Mar 2017 09:38:17 -0300
+
+notmuch (0.23.7-1) unstable; urgency=medium
+
+  * Move test suite $GNUPGHOME to /tmp to avoid problems with long build paths.
+  * Fix read-after-free bug in `notmuch new`.
+
+ -- David Bremner <bremner@debian.org>  Tue, 28 Feb 2017 20:39:30 -0400
+
+notmuch (0.23.5-1) unstable; urgency=medium
+
+  * Remove RUNPATH from /usr/bin/notmuch
+
+ -- David Bremner <bremner@debian.org>  Mon, 09 Jan 2017 06:24:39 -0400
+
+notmuch (0.23.4-1) unstable; urgency=medium
+
+  * Improve error handling in notmuch insert
+  * Restore autoload cookie for notmuch-search (notmuch-emacs)
+
+ -- David Bremner <bremner@debian.org>  Sat, 24 Dec 2016 17:47:48 +0900
+
+notmuch (0.23.3-3) unstable; urgency=medium
+
+  * Disable gdb using tests on kfreebsd-*, due to apparent gdb breakage
+
+ -- David Bremner <bremner@debian.org>  Mon, 05 Dec 2016 08:25:32 -0400
+
+notmuch (0.23.3-2) unstable; urgency=medium
+
+  * Add missing depends to notmuch-emacs. Thanks to micah for the
+    report.
+
+ -- David Bremner <bremner@debian.org>  Thu, 01 Dec 2016 08:06:59 -0400
+
+notmuch (0.23.3-1) unstable; urgency=medium
+
+  * Re-enable test suite
+  * Fix test suite compatibility with gnupg 2.1.16. Fixes: "FTBFS:
+    Tests failures", thanks to Lucas Nussbaum (Closes: #844915).
+  * Bug fix: "race condition in `notmuch new`?", thanks to Paul Wise
+    (Closes: #843127).
+
+ -- David Bremner <bremner@debian.org>  Sat, 26 Nov 2016 08:37:39 -0400
+
+notmuch (0.23.2-1) unstable; urgency=medium
+
+  * New upstream bugfix release
+  * Convert notmuch-emacs to dh-elpa, new binary package elpa-notmuch
+  * Bug fix: "maintainer script(s) do not start on #!", thanks to
+    treinen@debian.org; (Closes: #843287).
+
+ -- David Bremner <bremner@debian.org>  Thu, 10 Nov 2016 22:36:04 -0400
+
+notmuch (0.23.1-1) unstable; urgency=medium
+
+  * New upstream bugfix release
+  * Fix test suite for Emacs 25.1
+  * Fix some Emacs customization regressions introduced in 0.23
+  * Bug fix: "testsuite fails with TERM=unknown", thanks to Gianfranco
+    Costamagna (Closes: #841319).
+
+ -- David Bremner <bremner@debian.org>  Sun, 23 Oct 2016 22:06:12 -0300
+
+notmuch (0.23-2) unstable; urgency=medium
+
+  * upload to unstable
+
+ -- David Bremner <bremner@debian.org>  Wed, 05 Oct 2016 21:27:00 -0300
+
+notmuch (0.23-1) experimental; urgency=medium
+
+  * New upstream release
+  * Bump minor version of libnotmuch4 because of new symbols.
+  * Several new features, see /usr/share/doc/notmuch/NEWS.gz
+
+ -- David Bremner <bremner@debian.org>  Mon, 03 Oct 2016 22:46:26 -0300
+
+notmuch (0.23~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Make configure more robust on "unknown" platforms. Fixes FTBFS on
+    kfreebsd.
+
+ -- David Bremner <bremner@debian.org>  Fri, 30 Sep 2016 07:19:26 -0300
+
+notmuch (0.23~rc0-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Bug fix: "Calls to notmuch_directory_get_mtime() don't return
+    the recently set mtime", thanks to Lars Luthman (Closes: #826881).
+  * Bug fix: "Please document compact command", thanks to Olivier
+    Berger (Closes: #825884).
+
+ -- David Bremner <bremner@debian.org>  Mon, 26 Sep 2016 07:28:06 -0300
+
+notmuch (0.22.2-1) unstable; urgency=medium
+
+  * Fix test suite compatibility with GnuPG 2.1.15.  Bug fix: "FTBFS:
+    Tests failures", thanks to Lucas Nussbaum (Closes: #837013).
+
+ -- David Bremner <bremner@debian.org>  Thu, 08 Sep 2016 19:09:53 -0300
+
+notmuch (0.22.1-3) unstable; urgency=medium
+
+  * Gag gdb even more. Bug fix: "FTBFS in testing", thanks to Santiago
+    Vila (Closes: #834271).
+
+ -- David Bremner <bremner@debian.org>  Sun, 14 Aug 2016 13:31:13 +0900
+
+notmuch (0.22.1-2) unstable; urgency=medium
+
+  * Add explicit build-depends on gnupg, for the test suite.
+
+ -- David Bremner <bremner@debian.org>  Tue, 19 Jul 2016 08:50:13 -0300
+
+notmuch (0.22.1-1) unstable; urgency=medium
+
+  * Correct the definition of `LIBNOTMUCH_CHECK_VERSION`.
+  * Document the (lack of) operations permitted on a closed database
+    (Closes: #826843).
+  * Fix race condition in dump / restore tests.
+  * [notmuch-emacs] Tell `message-mode` mode that outgoing messages are mail
+  * [notmuch-emacs] Respect charset of MIME parts when reading them
+
+ -- David Bremner <bremner@debian.org>  Tue, 19 Jul 2016 06:42:09 -0300
+
+notmuch (0.22.1~rc0-1) experimental; urgency=medium
+
+  * release candidate for bugfix release
+
+ -- David Bremner <bremner@debian.org>  Thu, 30 Jun 2016 21:28:13 +0200
+
+notmuch (0.22-1) unstable; urgency=medium
+
+  * New upstream release.  See /usr/share/doc/notmuch/NEWS for new
+    features and bug fixes.
+
+ -- David Bremner <bremner@debian.org>  Tue, 26 Apr 2016 21:31:44 -0300
+
+notmuch (0.22~rc1-1) experimental; urgency=medium
+
+  * Upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sun, 24 Apr 2016 18:03:15 -0300
+
+notmuch (0.22~rc0-1) experimental; urgency=medium
+
+  * Upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 16 Apr 2016 08:45:32 -0300
+
+notmuch (0.21-3) unstable; urgency=medium
+
+  * Add mips and mips64el to gdb build-dep blacklist
+
+ -- David Bremner <bremner@debian.org>  Sat, 14 Nov 2015 19:07:06 -0400
+
+notmuch (0.21-2) unstable; urgency=medium
+
+  * Build-conflict with gdb on ppc64el and mipsel. Workaround gdb breakage on those
+    architectures (Closes: #804792).
+
+ -- David Bremner <bremner@debian.org>  Thu, 12 Nov 2015 08:54:23 -0400
+
+notmuch (0.21-1) unstable; urgency=medium
+
+  * New upstream release. Highlights include
+    - revision tracking for metadata
+    - new features and bug fixes for emacs interface
+    See /usr/share/doc/notmuch/NEWS for more details.
+
+ -- David Bremner <bremner@debian.org>  Thu, 29 Oct 2015 20:04:42 -0300
+
+notmuch (0.21~rc3-3) experimental; urgency=medium
+
+  * Build-conflict with gdb-minimal. gdb python scripts are needed for
+    the test suite
+
+ -- David Bremner <bremner@debian.org>  Sun, 25 Oct 2015 22:08:56 -0300
+
+notmuch (0.21~rc3-2) experimental; urgency=medium
+
+  * Bug fix: "reply-to encrypted messages in tree view fails to quote
+    and defaults to unencrypted message", thanks to Vagrant Cascadian
+    (Closes: #795243).
+  * Bug fix: "install/notmuch-emacs may interact with console, fail
+    emacs24 upgrade", thanks to Hilko Bengen (Closes: #802952).
+
+ -- David Bremner <bremner@debian.org>  Sun, 25 Oct 2015 13:42:57 -0300
+
+notmuch (0.21~rc3-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 22 Oct 2015 09:19:02 -0300
+
+notmuch (0.21~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Mon, 19 Oct 2015 07:25:10 -0300
+
+notmuch (0.21~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Thu, 15 Oct 2015 08:08:17 -0300
+
+notmuch (0.20.2-2) unstable; urgency=medium
+
+  * Fix linking in emacsen-install script. The previous version can
+    break an emacs upgrade.
+
+ -- David Bremner <bremner@debian.org>  Sat, 26 Sep 2015 09:26:41 -0300
+
+notmuch (0.20.2-1) unstable; urgency=medium
+
+  * Bug fix: "notmuch-tree does not mark messages as read", thanks to
+    Raúl Benencia (Closes: #789693).
+
+ -- David Bremner <bremner@debian.org>  Sat, 27 Jun 2015 15:03:33 +0200
+
+notmuch (0.20.1-1) unstable; urgency=medium
+
+  * Bug fix: "FTBFS on arm64", thanks to Edmund Grimley Evans (Closes:
+    #787341).
+
+ -- David Bremner <bremner@debian.org>  Mon, 01 Jun 2015 21:58:54 +0200
+
+notmuch (0.20-1) unstable; urgency=medium
+
+  * New upstream release
+    - new mimetype search prefix
+    - improvements to emacs, vim, and mutt front-ends
+    - undeprecate single message mboxes.
+    - reduced noise on stderr from the library
+    - improved API for notmuch_query_search_{messages, thread}
+
+ -- David Bremner <bremner@debian.org>  Sun, 31 May 2015 11:21:14 +0200
+
+notmuch (0.20~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate.
+  * Fix breakage of python bindings under python3
+
+ -- David Bremner <bremner@debian.org>  Sat, 23 May 2015 21:05:03 +0200
+
+notmuch (0.20~rc1-1) experimental; urgency=medium
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Mon, 04 May 2015 08:08:00 +0200
+
+notmuch (0.19-1) experimental; urgency=medium
+
+  * New upstream release.
+    - Improvements to reliability of 'notmuch dump' and the error
+    handling for 'notmuch insert'.
+    - The new 'notmuch address' command is intended to make searching
+    for email addresses more convenient.
+    - At the library level the revised handling of missing messages
+    fixes at least one bug in threading.
+    - Interface improvements to the emacs interface, most notably the
+    ability to bindkeyboard shortcuts to saved searches.
+
+ -- David Bremner <bremner@debian.org>  Fri, 14 Nov 2014 19:34:12 +0100
+
+notmuch (0.19~rc2-1) experimental; urgency=medium
+
+  * New upstream release candidate
+  * Updated defaults for "notmuch address"
+  * Assert compliance with policy 3.9.6
+
+ -- David Bremner <bremner@debian.org>  Sun, 09 Nov 2014 16:46:31 +0100
+
+notmuch (0.19~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate
+  * Bump libnotmuch SONAME because of API changes
+
+ -- David Bremner <bremner@debian.org>  Thu, 06 Nov 2014 00:30:39 +0100
+
+notmuch (0.18.2-1) unstable; urgency=medium
+
+  * Rebuild for unstable.
+  * Translate T380-atomicity to use gdb/python
+  * Emacs 24.4 related bug fixes
+  * Simplify T360-symbol-hiding, port to ppc64el
+
+ -- David Bremner <bremner@debian.org>  Sat, 25 Oct 2014 18:19:53 +0200
+
+notmuch (0.18.2~rc1-1) experimental; urgency=medium
+
+  * Test suite bug and portability fix release.
+
+ -- David Bremner <bremner@debian.org>  Sat, 25 Oct 2014 10:48:16 +0200
+
+notmuch (0.18.1-2) unstable; urgency=medium
+
+  * Update build-deps to use emacs24 on buildd (Closes: #756085)
+  * Disable gdb atomicity test for arm64 as gdb is currently broken on
+    the (unofficial) buildds
+  * Re-enable atomicity test on armhf; upstream fix seems to have
+    worked.
+
+ -- David Bremner <bremner@debian.org>  Sat, 09 Aug 2014 11:48:10 -0300
+
+notmuch (0.18.1-1) unstable; urgency=medium
+
+  * New upstream bug fix release
+    - Re-enable support for single-message mbox files
+    - Fix for phrase indexing
+    - Make tagging empty query in Emacs harmless
+
+ -- David Bremner <bremner@debian.org>  Wed, 25 Jun 2014 07:20:45 -0300
+
+notmuch (0.18.1~rc0-1) experimental; urgency=medium
+
+  * New upstream bug fix release (candidate)
+  * Tighten dependence of python packages on libnotmuch
+    (Closes: #749881).
+  * Redo emacsen-install script from sample in emacsen-common
+    (Closes: #739839).
+
+ -- David Bremner <bremner@debian.org>  Sat, 14 Jun 2014 07:50:28 -0300
+
+notmuch (0.18-3) unstable; urgency=medium
+
+  * Disable atomicity tests on armel.
+
+ -- David Bremner <bremner@debian.org>  Thu, 08 May 2014 14:26:45 +0900
+
+notmuch (0.18-2) unstable; urgency=medium
+
+  * Disable atomicity tests on armhf. These should be re-enabled when
+    upstream releases a fix for this (in progress).
+
+ -- David Bremner <bremner@debian.org>  Thu, 08 May 2014 08:28:33 +0900
+
+notmuch (0.18-1) unstable; urgency=medium
+
+  * New upstream release. For detailed release notes see
+    see /usr/share/doc/notmuch/NEWS.gz. Some highlights:
+    - Changes/enhancements to searching for messages by filesystem
+      location ('folder:' and 'path:' prefixes).
+    - Saved searches in Emacs have also been enhanced to allow
+      distinct search orders for each one.
+    - Another enhancement to the Emacs interface is that replies to
+      encrypted messages are now encrypted, reducing the risk of
+      unintentional information disclosure.
+    - The default dump output format has changed to the more robust
+      'batch-tag' format.
+    - The previously deprecated parsing of single message mboxes has
+      been removed.
+
+ -- David Bremner <bremner@debian.org>  Tue, 06 May 2014 16:20:39 +0900
+
+notmuch (0.18~rc1-1) experimental; urgency=low
+
+  * Upstream release candidate
+    - include encoding fix for vim client.
+
+ -- David Bremner <bremner@debian.org>  Sun, 04 May 2014 07:29:51 +0900
+
+notmuch (0.18~rc0-1) experimental; urgency=low
+
+  * Upstream release candidate.
+  * Bug fix: "insufficient sanitization of arguments to notmuch CLI",
+    thanks to Antoine Beaupré (Closes: #737496).
+  * Bug fix: "notmuch(1) manpage: typo: int -> in", thanks to Jakub
+    Wilk (Closes: #739556).
+  * Bug fix: "get a quiet option", thanks to Joerg Jaspert (Closes:
+    #666027).
+  * Bug fix: "Please remove me from Uploaders", thanks to martin f
+    krafft (Closes: #719100).
+  * Bug fix: "M-x notmuch-show-reply on an encrypted message should
+    insert encryption tags into the mml buffer", thanks to Daniel Kahn
+    Gillmor (Closes: #704648).
+
+ -- David Bremner <bremner@debian.org>  Tue, 22 Apr 2014 09:27:29 +0900
+
+notmuch (0.17-5) unstable; urgency=medium
+
+  * Bug fix: "unowned directory after purge: /0755/", thanks to
+    Andreas Beckmann (Closes: #740325).
+
+ -- David Bremner <bremner@debian.org>  Mon, 03 Mar 2014 07:29:06 -0400
+
+notmuch (0.17-4) unstable; urgency=medium
+
+  * Bug fix: "Please update ruby binary extension install path",
+    thanks to Christian Hofstaedtler (Closes: #739120).
+
+ -- David Bremner <bremner@debian.org>  Tue, 18 Feb 2014 21:37:44 -0400
+
+notmuch (0.17-3) unstable; urgency=medium
+
+  * update notmuch-emacs for debian emacs policy 2.0.6
+  * Update emacs test suite for Hurd compatibility
+
+ -- David Bremner <bremner@debian.org>  Sun, 12 Jan 2014 17:07:16 -0400
+
+notmuch (0.17-2) unstable; urgency=medium
+
+  * Bug fix: "package should warn in a NEWS.Debian file about possible
+    pre-upgrade action", thanks to Jonas Smedegaard (Closes: #733853).
+
+ -- David Bremner <bremner@debian.org>  Wed, 01 Jan 2014 07:44:25 -0400
+
+notmuch (0.17-1) unstable; urgency=low
+
+  * New upstream feature release. See /usr/share/doc/notmuch/NEWS.gz
+    for details.  Highlights include:
+    - notmuch compact command (Closes: #720543).
+    - emacs "tree" view
+  * Remove madduck from uploaders (Closes: #719100).
+
+ -- David Bremner <bremner@debian.org>  Mon, 30 Dec 2013 20:28:20 -0400
+
+notmuch (0.17~rc4-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 28 Dec 2013 18:30:06 -0400
+
+notmuch (0.17~rc3-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Sat, 07 Dec 2013 17:05:11 +0800
+
+notmuch (0.17~rc2-1) experimental; urgency=low
+
+  * New upstream release candidate
+  * Remove gdb as build-dep on s390x. This implicitly disables failing
+    atomicity test.  For more information, see #728705
+
+ -- David Bremner <bremner@debian.org>  Sun, 24 Nov 2013 19:34:28 -0400
+
+notmuch (0.17~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org>  Wed, 20 Nov 2013 19:27:48 -0400
+
+notmuch (0.16-1) unstable; urgency=low
+
+  * New upstream feature release
+    - 'notmuch insert' command replaces notmuch-deliver (Closes: #692889).
+    - New ruby based vim interface (Closes: 616692, 636707).
+  * Provide a notmuch-dbg package, thanks to Daniel Kahn Gillmor
+    (Closes: #717339).
+  * Include alot to the list of recommended interfaces, thanks to
+    Simon Chopin (Closes: #709832).
+
+ -- David Bremner <bremner@debian.org>  Sat, 03 Aug 2013 08:28:39 -0300
+
+notmuch (0.15.2-2) unstable; urgency=low
+
+  * Bug fix: tighten dependence of notmuch-mutt on notmuch,
+    thanks to Philippe Gimmel and Jameson Rollins (Closes: #703608).
+  * Bump Standards-Version to 3.9.4; no changes.
+
+ -- David Bremner <bremner@debian.org>  Sat, 25 May 2013 18:37:23 -0300
+
+notmuch (0.15.2-1) experimental; urgency=low
+
+  * Upstream bug fix release.
+    - Improve support for parallel building
+    - Update Emacs tests for portability, fix FTBFS on hurd-i386
+
+ -- David Bremner <bremner@debian.org>  Fri, 22 Mar 2013 20:42:42 -0400
+
+notmuch (0.15.1-1) experimental; urgency=low
+
+  * Upstream bug fix release: set default TERM for running tests.
+  * Re-enable build time self-tests.
+
+ -- David Bremner <bremner@debian.org>  Thu, 24 Jan 2013 07:19:45 -0400
+
+notmuch (0.15-2) experimental; urgency=low
+
+  * Disable tests until a proper fix for running tests without a
+    proper TERM value is developed (again).
+
+ -- David Bremner <bremner@debian.org>  Sun, 20 Jan 2013 18:36:16 -0400
+
+notmuch (0.15-1) experimental; urgency=low
+
+  * New upstream release.
+    - Date range search support
+    - Empty tag names and tags beginning with "-" are deprecated
+    - Support for single message mboxes is deprecated
+    - Fixed `notmuch new` to skip ignored broken symlinks
+    - New dump/restore format and tagging interface
+    - Emacs Interface
+      - Removal of the deprecated `notmuch-folders` variable
+      - Visibility of MIME parts can be toggled
+      - Emacs now buttonizes mid: links
+      - Improved text/calendar content handling
+      - Disabled coding conversions when reading
+      - Fixed errors with HTML email containing images in Emacs 24
+      - Fixed handling of tags with unusual characters in them
+      - Fixed buttonization of id: links without quote characters
+      - Automatic tag changes are now unified and customizable
+      - Support for stashing the thread id in show view
+      - New add-on tool: notmuch-pick
+
+ -- David Bremner <bremner@debian.org>  Fri, 18 Jan 2013 21:23:36 -0400
+
+notmuch (0.15~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate.
+  * Change priority to optional (Closes: #687217).
+  * Remove Dm-Upload-Allowed field, as this is no longer used by
+    Debian.
+  * Add python3 bindings, thanks to Jakub Wilk (Closes: #683515).
+  * Bug fix: ".ical attachment problem", (Closes: #688747).
+
+ -- David Bremner <bremner@debian.org>  Wed, 16 Jan 2013 08:28:27 -0400
+
+notmuch (0.14-1) experimental; urgency=low
+
+  [ Stefano Zacchiroli ]
+  * notmuch-mutt: fix tag action invocation (Closes: #678012)
+  * Use notmuch-search-terms manpage in notmuch-mutt (Closes: #675073).
+
+  [ David Bremner ]
+  * Do a better job of cleaning up after configuration and testing
+    (Closes: #683505)
+  * Alternately depend on emacs24 instead of emacs23 (Closes: #677900).
+  * New upstream version
+    - incompatible changes to dump/restore syntax
+    - bug fixes for maildir synchronization
+
+ -- David Bremner <bremner@debian.org>  Tue, 21 Aug 2012 10:39:33 +0200
+
+notmuch (0.13.2-1) unstable; urgency=low
+
+  * Upstream bugfix release. No changes to binary packages.
+
+ -- David Bremner <bremner@debian.org>  Sat, 02 Jun 2012 18:16:01 -0300
+
+notmuch (0.13.1-1) unstable; urgency=low
+
+  * Upstream bugfix release.
+    - fix for encoding problems with reply in emacs
+    - notmuch_database_(get_directory|find_message_by_filename) now
+      work for read-only databases.
+
+ -- David Bremner <bremner@debian.org>  Fri, 25 May 2012 21:19:06 -0300
+
+notmuch (0.13-1) unstable; urgency=low
+
+  * New upstream release. See /usr/share/doc/notmuch/NEWS.gz for changes.
+
+  [ Stefano Zacchiroli ]
+  * Recommend all notmuch UI (including notmuch-mutt) as alternatives,
+    to avoid unneeded vim/emacs installation. Thanks Matteo F. Vescovi
+    for the patch. (Closes: #673011)
+
+ -- David Bremner <bremner@debian.org>  Tue, 15 May 2012 18:19:32 -0300
+
+notmuch (0.13~rc1-2) experimental; urgency=low
+
+  * New upstream pre-release
+  * new binary package "notmuch-mutt" for Mutt integration
+  * Bump libnotmuch SONAME because of API changes
+
+ -- David Bremner <bremner@debian.org>  Sat, 05 May 2012 10:26:47 -0300
+
+notmuch (0.12-1) unstable; urgency=low
+
+  * New upstream release
+    - Python 3.2 support
+    - GMime 2.6 support
+    - Many updates to emacs interface (see /usr/share/doc/notmuch/NEWS)
+    - Optionally ignore some files/directories within mail hierarchy
+
+ -- David Bremner <bremner@debian.org>  Tue, 20 Mar 2012 18:45:22 -0300
+
+notmuch (0.12~rc2-1) experimental; urgency=low
+
+  * Upstream pre-release
+  * New bug fixes since ~rc1
+    - fix for uninitialized variable
+    - fix for python bindings type signatures
+
+ -- David Bremner <bremner@debian.org>  Sun, 18 Mar 2012 08:10:35 -0300
+
+notmuch (0.12~rc1-1) experimental; urgency=low
+
+  * Upstream pre-release.
+  * Bump standards version to 3.9.3; no changes.
+
+ -- David Bremner <bremner@debian.org>  Thu, 01 Mar 2012 07:51:45 -0400
+
+notmuch (0.11.1-1) unstable; urgency=low
+
+  * Upstream bugfix release
+    - Fix error handling bug in python bindings
+    - Fix vulnerability in emacs reply handling
+
+ -- David Bremner <bremner@debian.org>  Fri, 03 Feb 2012 08:35:41 -0400
+
+notmuch (0.11-1) unstable; urgency=low
+
+  * New upstream release.
+    - New 'hook' feature for notmuch-new
+    - performance and memory use improvements
+    - new add-on tool notmuch-deliver
+
+ -- David Bremner <bremner@debian.org>  Fri, 13 Jan 2012 19:59:23 -0400
+
+notmuch (0.11~rc3-1) experimental; urgency=low
+
+  * New upstream release candidate
+    - Fix for uninitialized variable(s) in notmuch-reply
+
+ -- David Bremner <bremner@debian.org>  Mon, 09 Jan 2012 07:07:46 -0400
+
+notmuch (0.11~rc2-1) experimental; urgency=low
+
+  * New upstream release candidate.
+    - Includes fix for one python bindings segfault.
+
+ -- David Bremner <bremner@debian.org>  Mon, 02 Jan 2012 06:57:29 -0400
+
+notmuch (0.11~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate.
+
+ -- David Bremner <bremner@debian.org>  Sun, 25 Dec 2011 23:07:08 -0400
+
+notmuch (0.10.2-1) unstable; urgency=low
+
+  * Upstream bug fix release
+    - Fix segfault in python bindings due to missing g_type_init call.
+
+ -- David Bremner <bremner@debian.org>  Sun, 04 Dec 2011 22:06:46 -0400
+
+notmuch (0.10.1-1) unstable; urgency=low
+
+  * Upstream bug fix release.
+    - Fix segfault on "notmuch --help"
+
+ -- David Bremner <bremner@debian.org>  Fri, 25 Nov 2011 12:11:30 -0500
+
+notmuch (0.10-1) unstable; urgency=low
+
+  * New upstream release
+    - search performance improvements
+    - emacs UI improvements
+    - new dump/restore features
+    - new script contrib/nmbug for sharing tags
+    - see /usr/share/doc/notmuch/NEWS for details
+
+ -- David Bremner <bremner@debian.org>  Wed, 23 Nov 2011 07:44:01 -0400
+
+notmuch (0.10~rc2-1) experimental; urgency=low
+
+  * New upstream release candidate
+    - includes patch to avoid long unix domain socket paths in tests
+
+ -- David Bremner <bremner@debian.org>  Sat, 19 Nov 2011 08:21:39 -0400
+
+notmuch (0.10~rc1-1) experimental; urgency=low
+
+  * New upstream release candidate.
+
+ -- David Bremner <bremner@debian.org>  Tue, 15 Nov 2011 19:46:55 -0400
+
+notmuch (0.9-1) unstable; urgency=low
+
+  * New upstream release.
+  * Only doc changes since last release candidate.
+  * Upload to unstable.
+
+ -- David Bremner <bremner@debian.org>  Tue, 11 Oct 2011 21:51:29 -0300
+
+notmuch (0.9~rc2-1) experimental; urgency=low
+
+  * Upstream release candidate
+  * API changes for n_d_find_message and n_d_find_message_by_filename.
+    - New SONAME for libnotmuch
+    - bindings changes for ruby and python
+  * Less non-text parts reported in replies.
+  * emacs: provide button action to fetch unknown gpg keys
+
+ -- David Bremner <bremner@debian.org>  Fri, 07 Oct 2011 18:53:04 -0300
+
+notmuch (0.9~rc1-1) experimental; urgency=low
+
+  * Upstream release candidate
+    - Atomicity improvements, thanks to Austin Clements
+    - Add missing call to g_type_init, thanks to Aaron Ecay
+  * notmuch-emacs: add versioned dependency on notmuch.
+    (Closes: #642240).
+
+ -- David Bremner <bremner@debian.org>  Sun, 25 Sep 2011 11:26:01 -0300
+
+notmuch (0.8-1) unstable; urgency=low
+
+  * New upstream version.
+    - Improved handling of message/rfc822 parts
+    - Improved Build system portability
+    - Documentation update for Ruby bindings
+    - Unicode, iterator, PEP8 changes for python bindings
+
+ -- David Bremner <bremner@debian.org>  Sat, 10 Sep 2011 08:53:55 -0300
+
+notmuch (0.8~rc1-1) experimental; urgency=low
+
+  * Upstream release candidate.
+
+ -- David Bremner <bremner@debian.org>  Tue, 06 Sep 2011 22:24:24 -0300
+
+notmuch (0.7-1) unstable; urgency=low
+
+  * New upstream release (no changes since 0.7~rc1).
+  * Upload to unstable.
+
+ -- David Bremner <bremner@debian.org>  Mon, 01 Aug 2011 21:46:26 +0200
+
+notmuch (0.7~rc1-1) experimental; urgency=low
+
+  * Upstream release candidate.
+  * Fix for notmuch.sym and parallel build (Thanks to
+    Thomas Jost)
+  * Bug fixes from Jason Woofenden for vim interface:
+    -  Fix "space (in show mode) mostly adds tag:inbox and tag:unread
+       instead of removing them"  (Closes: #633009).
+    -  Fix "says press 's'; to toggle signatures, but it's
+       really 'i'",  (Closes: #633115).
+    -  Fix "fix for from list on search pages", (Closes: #633045).
+  * Bug fixes for vim interface from Uwe Kleine-König
+    - use full path for sendmail/doc fix
+    - fix compose temp file name
+  * Python tag encoding fixes from Sebastian Spaeth.
+
+ -- David Bremner <bremner@debian.org>  Fri, 29 Jul 2011 12:16:56 +0200
+
+notmuch (0.6.1-1) unstable; urgency=low
+
+  * Properly install README.Debian in notmuch-vim (Closes: #632992).
+    Thanks to Jason Woofenden for the report.
+  * Force notmuch to depend on the same version of libnotmuch. Thanks to
+    Uwe Kleine-König for the patch.
+  * Export typeinfo for Xapian exceptions from libnotmuch. This fixes
+    certain mysterious uncaught exception problems.
+
+ -- David Bremner <bremner@debian.org>  Sun, 17 Jul 2011 10:20:42 -0300
+
+notmuch (0.6) unstable; urgency=low
+
+  * New upstream release; see /usr/share/doc/notmuch/NEWS for
+    details. Highlights include:
+    - Folder-based search (Closes: #597222)
+    - PGP/MIME decryption and verification
+  * Document strict dependency on emacs23 (Closes: #631994).
+
+ -- David Bremner <bremner@debian.org>  Fri, 01 Jul 2011 11:45:22 -0300
+
+notmuch (0.6~rc1) experimental; urgency=low
+
+  * Git snapshot 3f777b2
+  * Upstream release candidate.
+  * Fix description of notmuch-vim to mention vim, not emacs
+    (Closes: #631974)
+  * Install zsh completion as an example instead of into /usr/share/zsh to
+    avoid file conflict with zsh.
+
+ -- David Bremner <bremner@debian.org>  Thu, 30 Jun 2011 10:02:05 -0300
+
+notmuch (0.6~254) experimental; urgency=low
+
+  [David Bremner]
+  * Git snapshot fba968d
+  * Upstream release candidate
+  * Build binary package python-notmuch from upstream notmuch.
+  * Split off emacs and vim interfaces into their own packages.
+    (Closes: #578199)
+  * Hide Xapian exception symbols
+  * Build-depend on emacs23-nox instead of emacs
+
+  [ Jameson Rollins ]
+  * Bump standards version to 3.9.2 (No changes).
+
+ -- David Bremner <bremner@debian.org>  Thu, 23 Jun 2011 07:50:05 -0300
+
+notmuch (0.6~237) experimental; urgency=low
+
+  * Git snapshot 12d6f90
+  * Emacs: hide original message in top posted replies, isearch fix,
+    message display/hiding fixes/improvements.
+  * CLI: received header fixes.
+  * python: Improve docs, Remove Messages().__len__, Implement
+    Message.__cmp__ and __hash__, Message.tags_to_maildir_flags
+
+ -- David Bremner <bremner@debian.org>  Sat, 18 Jun 2011 11:14:51 -0300
+
+notmuch (0.6~215) experimental; urgency=low
+
+  * Git snapshot 5143e5e
+  * GMime: improve password handling, prevent premature closing stdout
+  * Emacs: sender address UI tweaks
+  * lib/message-file: plug three memleaks.
+  * Updated python bindings
+  * Sanitize "Subject:" and "Author:" fields in notmuch-search
+  * vim: new delete command, update mark as read command
+
+ -- David Bremner <bremner@debian.org>  Sat, 04 Jun 2011 08:37:36 -0300
+
+notmuch (0.6~180) experimental; urgency=low
+
+  * Git snapshot 1a96c40
+  * Fix corruption of binary parts
+    (see ML id:"874o4a1m74.fsf@yoom.home.cworth.org")
+
+ -- David Bremner <bremner@debian.org>  Tue, 31 May 2011 21:16:35 -0300
+
+notmuch (0.6~171) experimental; urgency=low
+
+  * Git snapshot cb8418784c2
+  * PGP/MIME handling in CLI and emacs front end.
+  * cli: Rewrite of multipart handling.
+  * emacs: Make the queries used in the all-tags section configurable
+  * emacs: Choose from address when composing/replying
+  * emacs: add notmuch-before- and notmuch-after-tag-hook
+  * notmuch reply: Avoid segmentation fault when printing multiple parts
+  * notmuch show: New part output handling.
+  * emacs: Show cleaner `From:' addresses in the summary line.
+  * emacs: Add custom `notmuch-show-elide-same-subject',
+    `notmuch-show-always-show-subject'
+  * emacs: Render text/x-vcalendar parts.
+  * emacs: Add `notmuch-show-multipart/alternative-discouraged'.
+  * vim: parse 'from' address, use sendmail directly, implement archive in
+    show view, refactor tagging stuff
+  * Eager metadata optimizations
+  * emacs: Fix notmuch-search-process-filter to handle incomplete lines
+  * Fix installation of zsh completion
+  * new: Enhance progress reporting
+  * Do not defer maildir flag synchronization for new messages
+  * vim: Get user email address from notmuch config file.
+  * lib: Save and restore term position in message while indexing.
+  * notmuch search: Clean up some memory leaks during search loop.
+  * New bindings for Go
+  * ruby: Add wrapper for message_get_filenames,  maildir sync. interface
+    query_get_s{ort,tring}
+  * Add support for folder-based searching.
+  * compatibility fixes for emacs22
+
+ -- David Bremner <bremner@debian.org>  Sat, 28 May 2011 07:25:49 -0300
+
+notmuch (0.5+nmu3) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * Upload to unstable.
+
+ -- David Bremner <bremner@debian.org>  Sun, 01 May 2011 15:09:09 -0300
+
+notmuch (0.5+nmu2) experimental; urgency=low
+
+  * Non-maintainer upload.
+  * Second try at timeout for test. Put timeouts at top level.
+
+ -- David Bremner <bremner@debian.org>  Sun, 19 Dec 2010 21:40:08 -0400
+
+notmuch (0.5+nmu1) experimental; urgency=low
+
+  * Non-maintainer upload.
+  * Add a timeout to emacs tests to hopefully work around build failures.
+
+ -- David Bremner <bremner@debian.org>  Tue, 14 Dec 2010 22:23:51 -0400
+
+notmuch (0.5) unstable; urgency=low
+
+  * new: maildir-flag synchronization
+  * new: New "notmuch show --format=raw" (enables local emacs interface,
+    for example, to use remote notmuch via ssh)
+  * lib: Support for multiple files for a message
+    (notmuch_message_get_filenames)
+  * lib: Support for maildir-flag synchronization
+    (notmuch_message_tags_to_maildir_flags and
+    notmuch_message_maildir_flags_to_tags)
+  * emacs: Incompatible change to format of notmuch-fcc-dirs variable (for
+    users using the "fancy" configuration)
+  * emacs: Cleaner display of subject lines in thread views
+
+ -- Carl Worth <cworth@debian.org>  Thu, 11 Nov 2010 20:49:11 -0800
+
+notmuch (0.4) unstable; urgency=low
+
+  * new: notmuch search --output=(summary|threads|messages|tags|files)
+  * new: notmuch show --format=mbox <search-specification>
+  * new: notmuch config [get|set] <section>.<item> [value ...]
+  * lib: Add notmuch_query_get_query_string and notmuch_query_get_sort
+  * emacs: Enable Fcc of all sent messages by default (to "sent" directory)
+  * emacs: Ability to all open messages in a thread to a pipe
+  * emacs: Optional support for detecting inline patches
+  * emacs: Automatically tag messages as "replied" when sending a reply
+  * emacs: Allow search-result color specifications to overlay each other
+  * emacs: Make hidden author names still available for incremental search.
+  * emacs: New binding of Control-TAB (works like TAB in reverse)
+  * test: New modularization of test suite.
+  * test: New testing of emacs interface.
+  * bugfix: Avoid setting Bcc header in "notmuch reply"
+  * bugfix: Avoid corruption of database when "notmuch new " is interrupted.
+  * bugfix: Fix failure with extremely long message ID headers.
+  * bugfix: Fix for messages with "charset=unknown-8bit"
+  * bugfix: Fix notmuch_query_search_threads to return NULL on any exception
+  * bugfix: Fix "notmuch search" to return non-zero on any exception
+  * emacs bugfix: Fix for message with a subject containing, "[1234]"
+  * emacs bugfix: Fix to correctly handle message IDs containing ".."
+  * emacs bugfix: Fix initialization so "M-x notmuch" works by default.
+
+ -- Carl Worth <cworth@debian.org>  Mon, 01 Nov 2010 16:23:47 -0700
+
+notmuch (0.3.1) unstable; urgency=low
+
+  * Fix an infinite loop in "notmuch reply"
+  * Fix a potential SEGV in "notmuch search"
+  * emacs: Fix calculations for line wrapping in the "notmuch" view.
+  * emacs: Fix Fcc support to prompt to create a directory if necessary
+
+ -- Carl Worth <cworth@debian.org>  Tue, 27 Apr 2010 17:02:07 -0700
+
+notmuch (0.3) unstable; urgency=low
+
+  * User-configurable tags for new messages
+  * Threads search results named based on subjects that match search
+  * Faster operation of "notmuch tag" (avoid unneeded sorting)
+  * Even Better guessing of From: header for "notmuch reply"
+  * Indication of author names that match a search
+  * emacs: An entirely new initial view for notmuch, (friendly yet powerful)
+  * emacs: Full-featured "customize" support for configuring notmuch
+  * emacs: Support for doing tab-completion of email addresses
+  * emacs: Support for file-based (Fcc) delivery of sent messages
+  * emacs: New 'G' key binding to trigger mail refresh (G == "Get new mail")
+  * emacs: Implement emacs message display with the JSON output from notmuch
+  * emacs: Better handling of HTML/MIME attachments (inline images!)
+  * emacs: Customizable support for tidying of text/plain message content
+  * emacs: New support for searchable citations (even when hidden)
+  * emacs: More flexible handling of header visibility
+  * emacs: The Return key now toggles message visibility anywhere
+  * emacs: Customizable formatting of search results
+  * emacs: Generate nicer names for search buffers when using a saved search.
+  * emacs: Add a notmuch User-Agent header when sending mail from notmuch/emacs
+  * emacs: New keybinding (M-Ret) to open all collapsed messages in a thread
+  * libnotmuch1: Provide a new NOTMUCH_SORT_UNSORTED value for queries
+
+ -- Carl Worth <cworth@debian.org>  Tue, 27 Apr 2010 02:07:29 -0700
+
+notmuch (0.2) unstable; urgency=low
+
+  * Better guessing of From: header.
+  * Make "notmuch count" with no arguments count all messages
+  * Provide a new special-case search term of "*" to match all messages.
+  * Detect thread connections when a parent message is missing.
+  * Fix potential data loss in "notmuch new" with SIGINT
+  * Fix segfault when a message includes a MIME part that is empty.
+  * Fix handling of non-ASCII characters with --format=json
+  * Fix headers to be properly decoded in "notmuch reply"
+  * emacs: Show the last few lines of citations as well as the first few lines.
+  * emacs: The '+' and '-' commands can now add and remove tags by region.
+  * emacs: More meaningful buffer names for thread-view buffers.
+  * emacs: Customized colors of threads in search view based on tags.
+
+ -- Carl Worth <cworth@debian.org>  Fri, 16 Apr 2010 10:20:23 -0700
+
+notmuch (0.1-1) unstable; urgency=low
+
+  [ martin f. krafft ]
+  * Add suggestion to vim-addon-manager.
+
+  [ Carl Worth ]
+  * Improve package description (closes: #566282).
+  * New upstream version (0.1) (closes: #576647).
+  * New versioning to track upstream version scheme.
+  * Split packaging into notmuch, libnotmuch1, and libnotmuch-dev.
+  * Update to advertise conformance with policy 3.8.4 (no changes).
+  * Add a debian/watch file to notice upstream tar files.
+
+ -- Carl Worth <cworth@debian.org>  Tue, 06 Apr 2010 18:27:49 -0700
+
+notmuch (0.0+201001211401) unstable; urgency=low
+
+  * Upload to Debian (closes: #557354).
+  * New versioning scheme.
+  * Added emacs build dependency.
+  * Added Vcs-Browser field to debian/control.
+  * Downgrade recommendation for emacs to suggestion.
+  * Add vim to suggestions and enhancements.
+  * Put debian/* under separate copyright.
+  * Make Carl the maintainer.
+  * Add myself to uploaders.
+  * Install the vim plugin (using vim-addons).
+
+ -- martin f. krafft <madduck@debian.org>  Thu, 21 Jan 2010 14:00:54 +1300
+
+notmuch (0.0-1) unstable; urgency=low
+
+  * New Debian package.
+
+ -- Jameson Graef Rollins <jrollins@finestructure.net>  Fri, 27 Nov 2009 13:39:09 -0500
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b4de394
--- /dev/null
@@ -0,0 +1 @@
+11
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..922f6d8
--- /dev/null
@@ -0,0 +1,166 @@
+Source: notmuch
+Section: mail
+Priority: optional
+Maintainer: Carl Worth <cworth@debian.org>
+Uploaders:
+ Jameson Graef Rollins <jrollins@finestructure.net>,
+ David Bremner <bremner@debian.org>
+Build-Conflicts: ruby1.8, gdb-minimal, gdb [ia64 mips mips64el]
+Build-Depends:
+ dpkg-dev (>= 1.17.14),
+ debhelper (>= 11~),
+ pkg-config,
+ libxapian-dev,
+ libgmime-3.0-dev (>= 3.0.3~) | libgmime-2.6-dev (>= 2.6.7~),
+ libtalloc-dev,
+ libz-dev,
+ python-all (>= 2.6.6-3~),
+ python3-all (>= 3.1.2-7~),
+ dh-python,
+ dh-elpa (>= 1.3),
+ python3-sphinx,
+ ruby, ruby-dev (>>1:1.9.3~),
+ emacs-nox | emacs-gtk | emacs-lucid |
+ emacs25-nox | emacs25 (>=25~) | emacs25-lucid (>=25~) |
+ emacs24-nox | emacs24 (>=24~) | emacs24-lucid (>=24~),
+ gdb [!ia64 !mips !mips64el !kfreebsd-any !alpha] <!nocheck>,
+ dtach (>= 0.8) <!nocheck>,
+ gpgsm <!nocheck>,
+ gnupg <!nocheck>,
+ bash-completion (>=1.9.0~),
+ texinfo
+Standards-Version: 4.1.3
+Homepage: https://notmuchmail.org/
+Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release
+Vcs-Browser: https://git.notmuchmail.org/git/notmuch
+
+Package: notmuch
+Architecture: any
+Depends: libnotmuch5 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
+Recommends: elpa-notmuch | notmuch-vim | notmuch-mutt | alot,  gnupg-agent, gpgsm
+Description: thread-based email index, search and tagging
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package contains the notmuch command-line interface
+
+Package: libnotmuch5
+Section: libs
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Pre-Depends: ${misc:Pre-Depends}
+Description: thread-based email index, search and tagging (runtime)
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package contains the runtime library, necessary to run
+ applications using libnotmuch.
+
+Package: libnotmuch-dev
+Section: libdevel
+Architecture: any
+Depends: ${misc:Depends}, libnotmuch5 (= ${binary:Version})
+Description: thread-based email index, search and tagging (development)
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides the necessary development libraries and header
+ files to allow you to develop new software using libnotmuch.
+
+Package: python-notmuch
+Architecture: all
+Section: python
+Depends: ${misc:Depends}, ${python:Depends}, libnotmuch5 (>= ${source:Version})
+Description: Python interface to the notmuch mail search and index library
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides a Python interface to the notmuch
+ functionality, directly interfacing with a shared notmuch library.
+
+Package: python3-notmuch
+Architecture: all
+Section: python
+Depends: ${misc:Depends}, ${python3:Depends}, libnotmuch5 (>= ${source:Version})
+Description: Python 3 interface to the notmuch mail search and index library
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides a Python 3 interface to the notmuch
+ functionality, directly interfacing with a shared notmuch library.
+
+Package: ruby-notmuch
+Architecture: any
+Section: ruby
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Ruby interface to the notmuch mail search and index library
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides a Ruby interface to the notmuch
+ functionality, directly interfacing with a shared notmuch library.
+
+Package: notmuch-emacs
+Section: oldlibs
+Architecture: all
+Depends: elpa-notmuch, ${misc:Depends}
+Description: thread-based email index, search and tagging (transitional package)
+ This dummy package help ease transition to the new package elpa-notmuch
+
+Package: elpa-notmuch
+Architecture: all
+Depends: ${misc:Depends}, ${elpa:Depends}
+Description: thread-based email index, search and tagging (emacs interface)
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides an emacs based mail user agent based on
+ notmuch.
+
+Package: notmuch-vim
+Architecture: all
+Breaks: notmuch (<<0.6~254~)
+Replaces: notmuch (<<0.6~254~)
+Depends: ${misc:Depends}, notmuch, vim-addon-manager, vim-ruby, ruby-notmuch
+Recommends: ruby-mail
+Description: thread-based email index, search and tagging (vim interface)
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages in maildir or mh format. It uses
+ the Xapian library to provide fast, full-text search with a very
+ convenient search syntax.
+ .
+ This package provides a vim based mail user agent based on
+ notmuch.
+
+Package: notmuch-mutt
+Architecture: all
+Depends:
+ notmuch (>= 0.4),
+ libmail-box-perl, libmailtools-perl,
+ libstring-shellquote-perl, libterm-readline-gnu-perl,
+ ${misc:Depends}
+Recommends: mutt
+Enhances: notmuch, mutt
+Description: thread-based email index, search and tagging (Mutt interface)
+ notmuch-mutt provides integration among the Mutt mail user agent and
+ the Notmuch mail indexer.
+ .
+ notmuch-mutt offer two main integration features. The first one is
+ the ability of stating a search query interactively and then jump to
+ a fresh Maildir containing its search results only. The second one is
+ the ability to reconstruct threads on the fly starting from the
+ current highlighted mail.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..0931d9b
--- /dev/null
@@ -0,0 +1,48 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: notmuch
+Source: git://notmuchmail.org/git/notmuch
+Upstream-Contact: Notmuch Mailing List <notmuch@notmuchmail.org>
+
+Files: *
+Copyright:  Copyright 2009 Carl Worth <cworth@cworth.org>
+ Bart Trojanowski <bart@jukie.net>
+ Keith Packard <keithp@keithp.com>
+ Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+ Ingmar Vanhassel <ingmar@exherbo.org>
+ Jed Brown <jed@59A2.org>
+ Jan Janak <jan@ryngle.com>
+ Chris Wilson <chris@chris-wilson.co.uk>
+ Keith Amidon <keith@nicira.com>
+ Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ Mikhail Gusarov <dottedmag@dottedmag.net>
+ Jeffrey C. Ollie <jeff@ocjtech.us>
+ Jameson Graef Rollins <jrollins@finestructure.net>
+ Stewart Smith <stewart@flamingspork.com>
+ Adrian Perez <aperez@igalia.com>
+ Kan-Ru Chen <kanru@kanru.info>
+ James Rowe <jnrowe@gmail.com>
+ Eric Anholt <eric@anholt.net>
+ Alec Berryman <alec@thened.net>
+ Tassilo Horn <tassilo@member.fsf.org>
+ Stefan Schmidt <stefan@datenfreihafen.org>
+ Rolland Santimano <rollandsantimano@yahoo.com>
+ Peter Wang <novalazy@gmail.com>
+ Lars Kellogg-Stedman <lars@seas.harvard.edu>
+ Holger Freyther <zecke@selfish.org>
+ David Bremner <bremner@unb.ca>
+ Alexander Botero-Lowry <alexbl@fortitudo.(none)>
+License: GPL-3+
+
+Files: debian/*
+Copyright:  Copyright 2010 Jameson Graef Rollins <jrollins@finestructure.net>
+ martin f. krafft <madduck@debian.org>
+License: GPL-3+
+
+License: GPL-3+
+ This package 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.
+ .
+ On Debian systems, the complete text of the GNU General Public License
+ version 3 can be found in file "/usr/share/common-licenses/GPL-3".
diff --git a/debian/elpa-notmuch.elpa b/debian/elpa-notmuch.elpa
new file mode 100644 (file)
index 0000000..a070ae5
--- /dev/null
@@ -0,0 +1,2 @@
+emacs/*.el
+debian/tmp/usr/share/info/*
diff --git a/debian/elpa-test b/debian/elpa-test
new file mode 100644 (file)
index 0000000..e3346c1
--- /dev/null
@@ -0,0 +1 @@
+disable=true
diff --git a/debian/gbp.conf b/debian/gbp.conf
new file mode 100644 (file)
index 0000000..a1c8735
--- /dev/null
@@ -0,0 +1,14 @@
+# Configuration file for git-buildpackage
+
+[DEFAULT]
+# The default branch for upstream sources
+upstream-branch = master
+
+# The default branch for the debian patch (no patch in our case)
+debian-branch = master
+
+# Format for upstream tags
+upstream-tag = %(version)s
+
+# Format for the debian tag
+debian-tag = debian/%(version)s
diff --git a/debian/libnotmuch-dev.install b/debian/libnotmuch-dev.install
new file mode 100644 (file)
index 0000000..96bbd63
--- /dev/null
@@ -0,0 +1,2 @@
+usr/include
+usr/lib/*/libnotmuch.so
diff --git a/debian/libnotmuch5.install b/debian/libnotmuch5.install
new file mode 100644 (file)
index 0000000..a513b47
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/libnotmuch.so.*
diff --git a/debian/libnotmuch5.symbols b/debian/libnotmuch5.symbols
new file mode 100644 (file)
index 0000000..308567b
--- /dev/null
@@ -0,0 +1,140 @@
+libnotmuch.so.5 libnotmuch5 #MINVER#
+ notmuch_built_with@Base 0.23~rc0
+ notmuch_config_list_destroy@Base 0.23~rc0
+ notmuch_config_list_key@Base 0.23~rc0
+ notmuch_config_list_move_to_next@Base 0.23~rc0
+ notmuch_config_list_valid@Base 0.23~rc0
+ notmuch_config_list_value@Base 0.23~rc0
+ notmuch_database_add_message@Base 0.3
+ notmuch_database_begin_atomic@Base 0.9~rc1
+ notmuch_database_close@Base 0.13~rc1
+ notmuch_database_compact@Base 0.17~rc1
+ notmuch_database_create@Base 0.3
+ notmuch_database_create_verbose@Base 0.20~rc1
+ notmuch_database_destroy@Base 0.13~rc1
+ notmuch_database_end_atomic@Base 0.9~rc1
+ notmuch_database_find_message@Base 0.9~rc2
+ notmuch_database_find_message_by_filename@Base 0.9~rc2
+ notmuch_database_get_all_tags@Base 0.3
+ notmuch_database_get_config@Base 0.23~rc0
+ notmuch_database_get_config_list@Base 0.23~rc0
+ notmuch_database_get_default_indexopts@Base 0.26~rc0
+ notmuch_database_get_directory@Base 0.3
+ notmuch_database_get_path@Base 0.3
+ notmuch_database_get_revision@Base 0.21~rc1
+ notmuch_database_get_version@Base 0.3
+ notmuch_database_index_file@Base 0.26~rc0
+ notmuch_database_needs_upgrade@Base 0.3
+ notmuch_database_open@Base 0.3
+ notmuch_database_open_verbose@Base 0.20~rc1
+ notmuch_database_remove_message@Base 0.3
+ notmuch_database_set_config@Base 0.23~rc0
+ notmuch_database_status_string@Base 0.20~rc1
+ notmuch_database_upgrade@Base 0.3
+ notmuch_directory_delete@Base 0.21~rc1
+ notmuch_directory_destroy@Base 0.3
+ notmuch_directory_get_child_directories@Base 0.3
+ notmuch_directory_get_child_files@Base 0.3
+ notmuch_directory_get_mtime@Base 0.3
+ notmuch_directory_set_mtime@Base 0.3
+ notmuch_filenames_destroy@Base 0.3
+ notmuch_filenames_get@Base 0.3
+ notmuch_filenames_move_to_next@Base 0.3
+ notmuch_filenames_valid@Base 0.3
+ notmuch_indexopts_destroy@Base 0.26~rc0
+ notmuch_indexopts_get_decrypt_policy@Base 0.26~rc0
+ notmuch_indexopts_set_decrypt_policy@Base 0.26~rc0
+ notmuch_message_add_property@Base 0.23~rc0
+ notmuch_message_add_tag@Base 0.3
+ notmuch_message_count_files@Base 0.26~rc0
+ notmuch_message_count_properties@Base 0.27~rc0
+ notmuch_message_destroy@Base 0.3
+ notmuch_message_freeze@Base 0.3
+ notmuch_message_get_database@Base 0.27~rc0
+ notmuch_message_get_date@Base 0.3
+ notmuch_message_get_filename@Base 0.3
+ notmuch_message_get_filenames@Base 0.5
+ notmuch_message_get_flag@Base 0.3
+ notmuch_message_get_header@Base 0.3
+ notmuch_message_get_message_id@Base 0.3
+ notmuch_message_get_properties@Base 0.23~rc0
+ notmuch_message_get_property@Base 0.23~rc0
+ notmuch_message_get_replies@Base 0.3
+ notmuch_message_get_tags@Base 0.3
+ notmuch_message_get_thread_id@Base 0.3
+ notmuch_message_has_maildir_flag@Base 0.26~rc0
+ notmuch_message_maildir_flags_to_tags@Base 0.5
+ notmuch_message_properties_destroy@Base 0.23~rc0
+ notmuch_message_properties_key@Base 0.23~rc0
+ notmuch_message_properties_move_to_next@Base 0.23~rc0
+ notmuch_message_properties_valid@Base 0.23~rc0
+ notmuch_message_properties_value@Base 0.23~rc0
+ notmuch_message_reindex@Base 0.26~rc0
+ notmuch_message_remove_all_properties@Base 0.23~rc0
+ notmuch_message_remove_all_properties_with_prefix@Base 0.26~rc0
+ notmuch_message_remove_all_tags@Base 0.3
+ notmuch_message_remove_property@Base 0.23~rc0
+ notmuch_message_remove_tag@Base 0.3
+ notmuch_message_set_flag@Base 0.3
+ notmuch_message_tags_to_maildir_flags@Base 0.5
+ notmuch_message_thaw@Base 0.3
+ notmuch_messages_collect_tags@Base 0.3
+ notmuch_messages_destroy@Base 0.3
+ notmuch_messages_get@Base 0.3
+ notmuch_messages_move_to_next@Base 0.3
+ notmuch_messages_valid@Base 0.3
+ notmuch_query_add_tag_exclude@Base 0.12~rc1
+ notmuch_query_count_messages@Base 0.3
+ notmuch_query_count_messages_st@Base 0.21~rc1
+ notmuch_query_count_threads@Base 0.10~rc1
+ notmuch_query_count_threads_st@Base 0.21~rc1
+ notmuch_query_create@Base 0.3
+ notmuch_query_destroy@Base 0.3
+ notmuch_query_get_database@Base 0.21~rc1
+ notmuch_query_get_query_string@Base 0.4
+ notmuch_query_get_sort@Base 0.4
+ notmuch_query_search_messages@Base 0.3
+ notmuch_query_search_messages_st@Base 0.20~rc1
+ notmuch_query_search_threads@Base 0.3
+ notmuch_query_search_threads_st@Base 0.20~rc1
+ notmuch_query_set_omit_excluded@Base 0.13~rc1
+ notmuch_query_set_sort@Base 0.3
+ notmuch_status_to_string@Base 0.3
+ notmuch_tags_destroy@Base 0.3
+ notmuch_tags_get@Base 0.3
+ notmuch_tags_move_to_next@Base 0.3
+ notmuch_tags_valid@Base 0.3
+ notmuch_thread_destroy@Base 0.3
+ notmuch_thread_get_authors@Base 0.3
+ notmuch_thread_get_matched_messages@Base 0.3
+ notmuch_thread_get_messages@Base 0.16
+ notmuch_thread_get_newest_date@Base 0.3
+ notmuch_thread_get_oldest_date@Base 0.3
+ notmuch_thread_get_subject@Base 0.3
+ notmuch_thread_get_tags@Base 0.3
+ notmuch_thread_get_thread_id@Base 0.3
+ notmuch_thread_get_toplevel_messages@Base 0.3
+ notmuch_thread_get_total_files@Base 0.26~rc0
+ notmuch_thread_get_total_messages@Base 0.3
+ notmuch_threads_destroy@Base 0.3
+ notmuch_threads_get@Base 0.3
+ notmuch_threads_move_to_next@Base 0.3
+ notmuch_threads_valid@Base 0.3
+ (c++)"typeinfo for Xapian::LogicError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::RuntimeError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::DocNotFoundError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::InvalidArgumentError@Base" 0.6.1
+ (c++)"typeinfo for Xapian::Error@Base" 0.6.1
+ (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
+ (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
+ (c++)"typeinfo for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
+ (c++)"typeinfo name for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
+ (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::InvalidArgumentError@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::Error@Base" 0.6.1
+ (c++)"typeinfo name for Xapian::DatabaseError@Base" 0.24~rc0
+ (c++)"typeinfo name for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++|optional=present with Xapian 1.4)"typeinfo name for Xapian::QueryParserError@Base" 0.23~rc0
diff --git a/debian/notmuch-emacs.README.Debian b/debian/notmuch-emacs.README.Debian
new file mode 100644 (file)
index 0000000..33f2d99
--- /dev/null
@@ -0,0 +1,4 @@
+* For help in getting started with notmuch and the emacs interface,
+  see /usr/share/doc/notmuch/README.
+
+ -- David Bremner <bremner@debian.org>, Wed, 27 Nov 2013 08:17:11 -0400
diff --git a/debian/notmuch-emacs.maintscript b/debian/notmuch-emacs.maintscript
new file mode 100644 (file)
index 0000000..6f93feb
--- /dev/null
@@ -0,0 +1 @@
+rm_conffile /etc/emacs/site-start.d/50notmuch.el
diff --git a/debian/notmuch-mutt.docs b/debian/notmuch-mutt.docs
new file mode 100644 (file)
index 0000000..f3d25cd
--- /dev/null
@@ -0,0 +1 @@
+contrib/notmuch-mutt/README
diff --git a/debian/notmuch-mutt.install b/debian/notmuch-mutt.install
new file mode 100644 (file)
index 0000000..9b468bd
--- /dev/null
@@ -0,0 +1,2 @@
+usr/bin/notmuch-mutt
+etc/Muttrc.d/notmuch-mutt.rc
diff --git a/debian/notmuch-mutt.manpages b/debian/notmuch-mutt.manpages
new file mode 100644 (file)
index 0000000..a14ed69
--- /dev/null
@@ -0,0 +1 @@
+usr/share/man/man1/notmuch-mutt.1
diff --git a/debian/notmuch-vim.README.Debian b/debian/notmuch-vim.README.Debian
new file mode 100644 (file)
index 0000000..ec052ee
--- /dev/null
@@ -0,0 +1,8 @@
+notmuch for Debian
+==================
+
+To use the vim plugin, please install it using vim-addons(1)
+
+See also /usr/share/doc/notmuch/README.
+
+ -- David Bremner <bremner@debian.org>, Fri,  1 Jul 2011 11:34:39 -0300
diff --git a/debian/notmuch-vim.dirs b/debian/notmuch-vim.dirs
new file mode 100644 (file)
index 0000000..c6373e4
--- /dev/null
@@ -0,0 +1,4 @@
+usr/share/vim/registry
+usr/share/vim/addons/plugin
+usr/share/vim/addons/doc
+usr/share/vim/addons/syntax
diff --git a/debian/notmuch-vim.docs b/debian/notmuch-vim.docs
new file mode 100644 (file)
index 0000000..d1496b5
--- /dev/null
@@ -0,0 +1 @@
+vim/README
diff --git a/debian/notmuch-vim.install b/debian/notmuch-vim.install
new file mode 100644 (file)
index 0000000..a1af708
--- /dev/null
@@ -0,0 +1,4 @@
+vim/notmuch.vim usr/share/vim/addons/plugin
+vim/notmuch.txt usr/share/vim/addons/doc
+vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
+vim/notmuch.yaml usr/share/vim/registry
diff --git a/debian/notmuch.dirs b/debian/notmuch.dirs
new file mode 100644 (file)
index 0000000..e772481
--- /dev/null
@@ -0,0 +1 @@
+usr/bin
diff --git a/debian/notmuch.docs b/debian/notmuch.docs
new file mode 100644 (file)
index 0000000..50bd824
--- /dev/null
@@ -0,0 +1,2 @@
+NEWS
+README
diff --git a/debian/notmuch.install b/debian/notmuch.install
new file mode 100644 (file)
index 0000000..2514ca6
--- /dev/null
@@ -0,0 +1,4 @@
+usr/bin/notmuch
+usr/bin/notmuch-emacs-mua
+usr/share/bash-completion
+usr/share/zsh/vendor-completions
diff --git a/debian/notmuch.maintscript b/debian/notmuch.maintscript
new file mode 100644 (file)
index 0000000..6f93feb
--- /dev/null
@@ -0,0 +1 @@
+rm_conffile /etc/emacs/site-start.d/50notmuch.el
diff --git a/debian/notmuch.manpages b/debian/notmuch.manpages
new file mode 100644 (file)
index 0000000..f9fcb54
--- /dev/null
@@ -0,0 +1,18 @@
+usr/share/man/man5/notmuch-hooks.5.gz
+usr/share/man/man1/notmuch-dump.1.gz
+usr/share/man/man1/notmuch-count.1.gz
+usr/share/man/man1/notmuch-compact.1.gz
+usr/share/man/man1/notmuch-emacs-mua.1.gz
+usr/share/man/man1/notmuch-new.1.gz
+usr/share/man/man1/notmuch.1.gz
+usr/share/man/man1/notmuch-reindex.1.gz
+usr/share/man/man1/notmuch-address.1.gz
+usr/share/man/man1/notmuch-tag.1.gz
+usr/share/man/man1/notmuch-reply.1.gz
+usr/share/man/man1/notmuch-search.1.gz
+usr/share/man/man1/notmuch-restore.1.gz
+usr/share/man/man1/notmuch-insert.1.gz
+usr/share/man/man1/notmuch-show.1.gz
+usr/share/man/man1/notmuch-config.1.gz
+usr/share/man/man7/notmuch-properties.7.gz
+usr/share/man/man7/notmuch-search-terms.7.gz
diff --git a/debian/python-notmuch.install b/debian/python-notmuch.install
new file mode 100644 (file)
index 0000000..b2cc136
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/python2*
diff --git a/debian/python3-notmuch.install b/debian/python3-notmuch.install
new file mode 100644 (file)
index 0000000..4606faa
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/python3*
diff --git a/debian/ruby-notmuch.install b/debian/ruby-notmuch.install
new file mode 100644 (file)
index 0000000..d3f2105
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/*/*ruby/*/*/notmuch.so
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..d056edb
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/make -f
+
+python3_all = py3versions -s | xargs -n1 | xargs -t -I {} env {}
+
+%:
+       dh $@ --with python2,python3,elpa
+
+override_dh_auto_configure:
+       BASHCMD=/bin/bash ./configure --prefix=/usr \
+               --libdir=/usr/lib/$$(dpkg-architecture -q DEB_TARGET_MULTIARCH) \
+               --includedir=/usr/include \
+               --mandir=/usr/share/man \
+               --infodir=/usr/share/info \
+               --sysconfdir=/etc \
+               --zshcompletiondir=/usr/share/zsh/vendor-completions \
+               --localstatedir=/var
+
+override_dh_auto_build:
+       dh_auto_build -- V=1
+       dh_auto_build --sourcedirectory bindings/python
+       cd bindings/python && $(python3_all) setup.py build
+       $(MAKE) -C contrib/notmuch-mutt
+
+override_dh_auto_clean:
+       dh_auto_clean
+       dh_auto_clean --sourcedirectory bindings/python
+       cd bindings/python && $(python3_all) setup.py clean -a
+       dh_auto_clean --sourcedirectory bindings/ruby
+       $(MAKE) -C contrib/notmuch-mutt clean
+
+override_dh_auto_install:
+       dh_auto_install
+       dh_auto_install --sourcedirectory bindings/python
+       cd bindings/python && $(python3_all) setup.py install --install-layout=deb --root=$(CURDIR)/debian/tmp
+       $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install
+       dh_auto_install --sourcedirectory bindings/ruby
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/source/options b/debian/source/options
new file mode 100644 (file)
index 0000000..cc76e91
--- /dev/null
@@ -0,0 +1,3 @@
+single-debian-patch
+tar-ignore=.git
+tar-ignore=performance-test/download/*.tar.xz
diff --git a/debugger.c b/debugger.c
new file mode 100644 (file)
index 0000000..5cb38ac
--- /dev/null
@@ -0,0 +1,47 @@
+/* debugger.c - Some debugger utilities for the notmuch mail library
+ *
+ * Copyright © 2009 Chris Wilson
+ *
+ * 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/ .
+ *
+ * Author: Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#include "notmuch-client.h"
+
+#include <libgen.h>
+
+#if HAVE_VALGRIND
+#include <valgrind.h>
+#else
+#define RUNNING_ON_VALGRIND 0
+#endif
+
+bool
+debugger_is_active (void)
+{
+    char buf[1024];
+
+    if (RUNNING_ON_VALGRIND)
+       return true;
+
+    sprintf (buf, "/proc/%d/exe", getppid ());
+    if (readlink (buf, buf, sizeof (buf)) != -1 &&
+       strncmp (basename (buf), "gdb", 3) == 0)
+    {
+       return true;
+    }
+
+    return false;
+}
diff --git a/devel/RELEASING b/devel/RELEASING
new file mode 100644 (file)
index 0000000..88dab04
--- /dev/null
@@ -0,0 +1,113 @@
+Here are the steps to follow to create a new notmuch release.
+
+These steps assume that a process (not described here) has already
+been followed to determine the features and bug fixes to be included
+in a release, and that adequate testing by the community has already
+been performed. The little bit of testing performed here is a safety
+check, and not a substitute for wider testing.
+
+OK, so the code to be released is present and committed to your git
+repository. From here, there are just a few steps to release:
+
+1) Verify that the NEWS file is up to date.
+
+       Read through the entry at the top of the NEWS file and see if
+       you are aware of any major features recently added that are
+       not mentioned there. If so, please add them, (and ask the
+       authors of the commits to update NEWS in the future).
+
+2) Verify that the library version in lib/Makefile.local is correct
+
+       See the instructions there for how to increment it.
+
+       The version should have been updated with any commits that
+       added API _in a non-upwardly compatible_ way, but do check
+       that that is the case. The command below can be useful for
+       inspecting header-file changes since the last release X.Y:
+
+               git diff X.Y..HEAD -- lib/notmuch.h
+
+       Commit this change, if any.
+
+3) Update the debian/libnotmuchX.symbols file
+
+       If the library version changed at all (step 2) it probably
+       means that symbols have changed/been added, in which case the
+       debian symbols file also needs to be updated:
+
+              dpkg-buildpackage -uc -us
+              dpkg-gensymbols -plibnotmuchX | patch -p0
+
+       Carefully review the changes to debian/libnotmuch1.symbols to
+       make sure there are no unexpected changes.  Remove any debian
+       versions from symbols.
+
+       Commit this change, if any.
+
+4) Upgrade the version in the file "version"
+
+       The scheme for the release number is as follows:
+
+       A major milestone in usability causes an increase in the major
+       number, yielding a two-component version with a minor number
+       of 0, (such as "1.0" or "2.0").
+
+       Otherwise, releases with changes in features cause an increase
+       in the minor number, yielding a two-component version, (such
+       as "1.1" or "1.2").
+
+       Finally, releases that do not change "features" but are merely
+       bug fixes either increase the micro number or add it (starting
+       at ".1" if not present). So a bug-fix release from "1.0" would
+       be "1.0.1" and a subsequent bug-fix release would be "1.0.2"
+       etc.
+
+       When you are happy with the file 'version', run
+
+            make update-versions
+
+       to propagate the version to the other places needed.
+
+       Commit these changes.
+
+5) Create an entry for the new release in debian/changelog
+
+       The syntax of this file is tightly restricted, but the
+       available emacs mode (see the dpkg-dev-el package) helps.
+       The entries here will be the Debian-relevant single-line
+       description of changes from the NEWS entry. And the version
+       must match the version in the next step.
+
+       Commit this change.
+
+       XXX: It would be great if this step were automated as part of
+       release, (taking entries from NEWS and the version from the
+       version file, and creating a new commit, etc.)
+
+6) Run "make release" which will perform the following steps.
+
+   Note: in order to really upload anything, set the make variable
+   REALLY_UPLOAD=yes
+
+       * Ensure that the version consists only of digits and periods
+       * Ensure that version and debian/changelog have the same version
+       * Verify that the source tree is clean
+       * Compile the current notmuch code (aborting release if it fails)
+       * Run the notmuch test suite (aborting release if it fails)
+       * Check that no release exists with the current version
+       * Make a signed tag
+       * Generate a tar file from this tag
+       * Generate a .sha1 sum file for the tar file and GPG sign it.
+       * Commit a (delta for a) copy of the tar file using pristine-tar
+       * Tag for the debian version
+       * if REALLY_UPLOAD=yes
+         - push the signed tag to the origin
+           XXX FIXME push debian tag
+         - scp tarball to web site
+       * Provide some text for the release announcement (see below).
+
+7) Send a message to notmuch@notmuchmail.org to announce the release.
+
+       Use the text provided from "make release" above, (if for some
+       reason you lose this message, "make release-message" prints
+       it again for you.
diff --git a/devel/STYLE b/devel/STYLE
new file mode 100644 (file)
index 0000000..da65312
--- /dev/null
@@ -0,0 +1,110 @@
+C/C++ coding style
+==================
+
+Tools
+-----
+
+There is a file uncrustify.cfg in this directory that can be used to
+approximate the prevailing code style. You can run it with e.g.
+
+   uncrustify --replace -c devel/uncrustify.cfg foo.c
+
+You still have to use your judgement about accepting or rejecting the
+changes uncrustify makes. With a nice git frontend, you can add the
+lines you agree with and reject the rest.
+
+For Emacs users, the file .dir-locals.el in the top level source
+directory will configure c-mode to automatically meet most of the
+basic layout rules.  I
+
+Indentation, Whitespace, and Layout
+-----------------------------------
+
+The following nonsense code demonstrates many aspects of the style:
+
+static some_type
+function (param_type param, param_type param)
+{
+   for (int i = 0; i < 10; i++) {
+       int j;
+
+       j = i + 10;
+
+       some_other_func (j, i);
+   }
+}
+
+* Indent is 4 spaces with mixed tab/spaces and a tab width of 8.
+  (Specifically, a line should begin with zero or more tabs followed
+  by fewer than eight spaces.)
+
+* Use copious whitespace.  In particular
+   - there is a space between the function name and the open paren in a call.
+   - likewise, there is a space following keywords such as if and while
+   - every binary operator should have space on either side.
+
+* No trailing whitespace. Please enable the standard pre-commit hook in git
+  (or an equivalent hook). The standard pre-commit hook is enabled by simply
+  renaming file '.git/hooks/pre-commit.sample' to '.git/hooks/pre-commit' .
+
+* The name in a function prototype should start at the beginning of a line.
+
+* Opening braces "cuddle" (they are on the same line as the
+  if/for/while test) and are preceded by a space. The opening brace of
+  functions is the exception, and starts on a new line.
+
+* Comments are always C-style /* */ block comments.  They should start
+  with a capital letter and generally be written in complete
+  sentences.  Public library functions are documented immediately
+  before their prototype in lib/notmuch.h.  Internal functions are
+  typically documented immediately before their definition.
+
+* Code lines should be less than 80 columns and comments should be
+  wrapped at 70 columns.
+
+* Variable declarations should be at the top of a block; C99 style
+  control variable declarations in for loops are also OK.
+
+Naming
+------
+
+* Use lowercase_with_underscores for function, variable, and type
+  names.
+
+* Except for variables with extremely small scope, and perhaps loop
+  indices, when naming variables and functions, err on the side of
+  verbosity.
+
+* All structs should be typedef'd to a name ending with _t.  If the
+  struct has a tag, it should be the same as the typedef name, minus
+  the trailing _t.
+
+CLI conventions
+---------------
+
+* Any changes to the JSON output format of search or show need an
+  accompanying change to devel/schemata.
+
+libnotmuch conventions
+----------------------------------
+
+* Functions starting with notmuch_ in lib/notmuch.h are public and are
+  automatically exported from the shared library.  Private library
+  functions should generally either be static or, if they are shared
+  between compilation units, start with _notmuch.
+
+* Functions in libnotmuch must not access user configuration files
+  (i.e. .notmuch-config)
+
+* Code which needs to be accessed from both the CLI and from
+  libnotmuch should be factored out into libutil (under util/).
+
+* Deprecated functions should be marked with the NOTMUCH_DEPRECATED
+  macro which generates run time warnings with gcc and clang. In order
+  not to confuse doxygen this should go at the beginning of the
+  declaration like:
+
+  NOTMUCH_DEPRECATED(major,minor) notmuch_status_t notmuch_dwim(void *arg);
+
+  The @deprecated doxygen command can be used to generate markup in
+  the API docs.
diff --git a/devel/TODO b/devel/TODO
new file mode 100644 (file)
index 0000000..116194d
--- /dev/null
@@ -0,0 +1,255 @@
+Fix the things that are causing the most pain to new users
+----------------------------------------------------------
+1. A new import is tagging all messages as "inbox" -- total pain
+
+Emacs interface (notmuch.el)
+----------------------------
+Add notmuch-bcc and notmuch-cc for setting default Bcc and Cc values,
+(should affect the message-setup-hook).
+
+Switch the notmuch-search view to use "notmuch search --format=json"
+to fix large classes of bugs regarding poorly-escaped output and lame
+regular expressions. (The most recently found, unfixed example is the
+sender's name containing ';' which causes emacs to drop a search
+result.) This may require removing the outer array from the current
+"notmuch search --format=json" results.
+
+Add a global keybinding table for notmuch, and then view-specific
+tables that add to it.
+       
+Add a '|' binding from the search view.
+
+Add support for choosing from one of the user's configured email
+addresses for the From line.
+
+Make 'notmuch-show-pipe-message have a private history.
+
+Add support for a delete keybinding that adds a "deleted" tag to the
+current message/thread and make searches not return deleted messages
+by default, (unless the user asks explicitly for deleted messages in
+the search query).
+
+Add support to "mute" a thread (add a "muted" tag and then don't
+display threads in searches by default where any message of the thread
+has the "muted" tag).
+
+Make '=' count from the end rather than from the beginning if more
+than half-way through the buffer.
+
+Fix to automatically wrap long headers (for RFC compliance) before
+sending. This should probably just be fixed in message-mode itself,
+(but perhaps we can have a notmuch-message-mode that layers this on
+top).
+
+Stop hiding the headers so much in the thread-view mode.
+
+Allow opening a message in thread-view mode by clicking on either
+line.
+
+Automatically open a message when navigating to it with N or P.
+
+Change 'a' command in thread-view mode to only archive open messages.
+
+notmuch command-line tool
+-------------------------
+Add support to "notmuch search" and "notmuch show" to allow for
+listing of duplicate messages, (distinct filenames with the same
+Message-ID). I'm not sure what the option should be named. Perhaps
+--with-duplicates ?
+
+Replace "notmuch reply" with "notmuch compose --reply <search-terms>".
+This would enable a plain "notmuch compose" to be used to construct an
+initial message, (which would then have the properly configured name
+and email address in the From: line. We could also then easily support
+"notmuch compose --from <something>" to support getting at alternate
+email addresses.
+
+Implement "notmuch search --exclude-threads=<search-terms>" to allow
+for excluding muted threads, (and any other negative, thread-based
+filtering that the user wants to do).
+
+Fix "notmuch show" so that the UI doesn't fail to show a thread that
+is visible in a search buffer, but happens to no longer match the
+current search. (Perhaps add a --matching=<secondary-search-terms>
+option (or similar) to "notmuch show".) For now, this is being worked
+around in the emacs interface by noticing that "notmuch show" returns
+nothing and re-rerunning the command without the extra arguments.
+
+Add a "--format" option to "notmuch search", (something printf-like
+for selecting what gets printed).
+
+Give "notmuch restore" some progress indicator.
+
+Fix "notmuch restore" to operate in a single pass much like "notmuch
+dump" does, rather than doing N searches into the database, each
+matching 1/N messages.
+
+Allow configuration for filename patterns that should be ignored when
+indexing.
+
+Fix to avoid this ugly message:
+
+       (process:17197): gmime-CRITICAL **: g_mime_message_get_mime_part: assertion `GMIME_IS_MESSAGE (message)' failed
+       Warning: Not indexing empty mime part.
+
+  This probably means adding a test case to generate that message,
+  filing an upstream bug against GMime, and then silencing the
+  notmuch-generated portion of the warning (so that once GMime is
+  fixed, this is all silent).
+
+Simplify notmuch-reply to simply print the headers (we have the
+original values) rather than calling GMime (which encodes) and adding
+the confusing gmime-filter-headers.c code (which decodes).
+
+Properly handle replying to multiple messages. Currently, the JSON
+reply format only supports a single message, but the default reply
+format accepts searches returning multiple messages. The expected
+behavior of replying to multiple messages is not obvious, and there
+are multiple ideas that might make sense. Some consensus needs to be
+reached on this issue, and then both reply formats should be updated
+to be consistent.
+
+Return docid-based queries in thread search results, instead of
+message-id-based queries.  These are much more compact and much faster
+to later query.  This will require support from the library, both for
+retrieving docids (probably as an opaque "get a query that identifies
+this message") and for docid queries.
+
+notmuch library
+---------------
+Add support for custom flag<->tag mappings. In the notmuch
+configuration file this could be
+
+       [maildir]
+       synchronize_flags = R:replied; D*:deleted; S:~unread;
+
+In the library interface this could be implemented with an array of
+structures to define the mapping (flag character, tag name,
+inverse-sense bit (~ above), and tag-when-any-file-flagged
+vs. tag-when-all-files-flagged (* above)).
+
+Add an interface to accept a "key" and a byte stream, rather than a
+filename.
+
+Improve syntax for date ranges queries. date:expr should be
+interpreted as date:expr..expr so that, for example, "date:2013-01-22"
+would cover the whole of the specified day (currently that's not even
+recognized as a date range expression). It might be nice to be able to
+use things like "since:2013-01-22" and "until:2013-01-22" as synonyms
+to "date:2013-01-22.." and "date:..2013-01-22", respectively. To do
+any of this we're probably going to need to break down and write our
+own parser for the query string rather than using Xapian's QueryParser
+class.
+
+Make failure to read a file (such as a permissions problem) a warning
+rather than an error (should be similar to the existing warning for a
+non-mail file).
+
+Fix to use the *last* Message-ID header if multiple such headers are
+encountered, (I noticed this is one thing that kept me from seeing the
+same message-ID values as sup).
+
+Add support for configuring "virtual tags" which are a tuple of
+(tag-name, search-specification). The database is responsible for
+ensuring that the virtual tag is always consistent.
+
+Indicate to the user if two files with the same message ID have
+content that is actually different in some interesting way. Perhaps
+notmuch initially sees all changes as interesting, and quickly learns
+from the user which changes are not interesting (such as the very
+common mailing-list footer).
+
+Fix notmuch_query_count_messages to share code with
+notmuch_query_search_messages rather than duplicating code. (And
+consider renaming it as well.)
+
+Provide a mechanism for doing automatic address completion based on
+notmuch searches. Here was one proposal made in IRC:
+
+       <cworth> I guess all it would really have to be would be a way
+                to configure a series of searches to try in turn,
+                (presenting ambiguities at a given single level, and
+                advancing to the next level only if one level
+                returned no matches).
+       <cworth> So then I might have a series that looks like this:
+       <cworth> notmuch search --output=address_from tag:address_book_alias
+       <cworth> notmuch search --output=address_to tag:sent
+       <cworth> notmuch search --output=address_from
+       <cworth> I think I might like that quite a bit.
+       <cworth> And then we have a story for an address book for
+                non-emacs users.
+
+Provide a ~me Xapian synonym for all of the user's configured email
+addresses.
+
+Add symbol hiding so that we don't risk leaking any private symbols
+into the shared-library interface.
+
+Audit all libnotmuch entry points to ensure that all Xapian calls are
+wrapped in a try/catch block.
+
+Fix the threading of a message that has a References: header but no
+In-Reply-To: header (see id:"87lixxnxpb.fsf@yoom.home.cworth.org").
+
+Search syntax
+-------------
+Implement support for "tag:*" to expand to all tags.
+
+Fix "notmuch search to:" to be less confusing. Many users expect this
+to search for all messages with a To: header, but it instead searches
+for all messages with the word "to". If we don't provide the first
+behavior, perhaps we should exit on an error when a configured prefix
+is provided with no value?
+
+Support "*" in all cases and not just as a special case. That is, "* "
+should also work, as well as "* and tag:inbox".
+
+Implement a syntax for requesting set-theoertic operations on results
+of multiple searches. For example, I would like to do:
+
+       "tag:inbox" SET-SUBTRACT "tag:muted"
+
+    as well as:
+
+       "tag:notmuch and <date-range>" SET-INTERSECT
+       "tag:notmuch and not (tag:merged or tag:postponed)"
+
+    See id:3wdpr282yz2.fsf@testarossa.amd.com for more details on the
+    use cases of the above.
+
+Database changes
+----------------
+Store a reference term for every message-id that appears in
+References. We just started doing this for newly-added documents, but
+at the next convenient database-schema upgrade, we should go back and
+fix old messages to be consistent.
+
+Start indexing the List-Id header, (and re-index this header for
+existing messages at the next database upgrade).
+
+Add support for the user to specify custom headers to be indexed (and
+re-index these for existing messages at the next database upgrade).
+
+Save filenames for files detected as "not an email file" in the
+database. This would allow for two things: 1. Optimizing "notmuch new"
+to not have to look at these files again (since they are potentially
+large so the detection could be potentially slow). 2. A "notmuch
+search" syntax could be added to allow the user to find these files,
+(and perhaps delete them or move them away as appropriate).
+
+Fix filesystem/notmuch-new race condition by not updating database
+mtime for a directory if it is the same as the current mtime.
+
+Test suite
+----------
+Achieve 100% test coverage with the test suite.
+
+General
+-------
+Audit everything for dealing with out-of-memory (and drop xutil.c).
+
+Investigate why the notmuch database is slightly larger than the sup
+database for the same corpus of email.
+
+Makefile should print message teaching user about LD_LIBRARY_PATH (or
+similar) if libdir is not set to a directory examined by ldconfig.
diff --git a/devel/check-out-of-tree-build.sh b/devel/check-out-of-tree-build.sh
new file mode 100755 (executable)
index 0000000..3e443ea
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# test out-of-tree builds in a temp directory
+# passes all args to make
+
+set -eu
+
+srcdir=$(cd "$(dirname "$0")"/.. && pwd)
+builddir=$(mktemp -d)
+
+cd "$builddir"
+
+"$srcdir"/configure
+make "$@"
+
+rm -rf "$builddir"
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               |
+|-----------+----------------------------------------+-------------------------------------------------------+-----------------------------------------|
diff --git a/devel/gen-testdb.sh b/devel/gen-testdb.sh
new file mode 100755 (executable)
index 0000000..61ae48a
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/env bash
+#
+# NAME
+#      gen-testdb.sh - generate test databases
+#
+# SYNOPSIS
+#      gen-testdb.sh -v NOTMUCH-VERSION [-c CORPUS-PATH] [-s TAR-SUFFIX]
+#
+# DESCRIPTION
+#      Generate a tarball containing the specified test corpus and
+#      the corresponding notmuch database, indexed using a specific
+#      version of notmuch, resulting in a specific version of the
+#      database.
+#
+#      The specific version of notmuch will be built on the fly.
+#      Therefore the script must be run within a git repository to be
+#      able to build the old versions of notmuch.
+#
+#      This script reuses the test infrastructure, and the script
+#      must be run from within the test directory.
+#
+#      The output tarballs, named database-<TAR-SUFFIX>.tar.gz, are
+#      placed in the test/test-databases directory.
+#
+# OPTIONS
+#      -v NOTMUCH-VERSION
+#              Notmuch version in terms of a git tag or commit to use
+#              for generating the database. Required.
+#
+#      -c CORPUS-PATH
+#              Path to a corpus to use for generating the
+#              database. Due to CWD changes within the test
+#              infrastructure, use absolute paths. Defaults to the
+#              test corpus.
+#
+#      -s TAR-SUFFIX
+#              Suffix for the tarball basename. Empty by default.
+#
+# EXAMPLE
+#
+#      Generate a database indexed with notmuch 0.17. Use the default
+#      test corpus. Name the tarball database-v1.tar.gz to reflect
+#      the fact that notmuch 0.17 used database version 1.
+#
+#      $ cd test
+#      $ ../devel/gen-testdb.sh -v 0.17 -s v1
+#
+# CAVEATS
+#      Test infrastructure options won't work.
+#
+#      Any existing databases with the same name will be overwritten.
+#
+#      It may not be possible to build old versions of notmuch with
+#      the set of dependencies that satisfy building the current
+#      version of notmuch.
+#
+# AUTHOR
+#      Jani Nikula <jani@nikula.org>
+#
+# LICENSE
+#      Same as notmuch test infrastructure (GPLv2+).
+#
+
+test_description="database generation abusing test infrastructure"
+
+# immediate exit on subtest failure; see test_failure_ in test-lib.sh
+immediate=t
+
+VERSION=
+CORPUS=
+SUFFIX=
+
+while getopts v:c:s: opt; do
+    case "$opt" in
+       v) VERSION="$OPTARG";;
+       c) CORPUS="$OPTARG";;
+       s) SUFFIX="-$OPTARG";;
+    esac
+done
+shift `expr $OPTIND - 1`
+
+. ./test-lib.sh || exit 1
+
+SHORT_CORPUS=$(basename ${CORPUS:-database})
+DBNAME=${SHORT_CORPUS}${SUFFIX}
+TARBALLNAME=${DBNAME}.tar.xz
+
+CORPUS=${CORPUS:-${TEST_DIRECTORY}/corpus}
+
+test_expect_code 0 "notmuch version specified on the command line" \
+    "test -n ${VERSION}"
+
+test_expect_code 0 "the specified version ${VERSION} refers to a commit" \
+    "git show ${VERSION} >/dev/null 2>&1"
+
+BUILD_DIR="notmuch-${VERSION}"
+test_expect_code 0 "generate snapshot of notmuch version ${VERSION}" \
+    "git -C $TEST_DIRECTORY/.. archive --prefix=${BUILD_DIR}/ --format=tar ${VERSION} | tar x"
+
+# force version string
+git describe --match '[0-9.]*' ${VERSION} > ${BUILD_DIR}/version
+
+test_expect_code 0 "configure and build notmuch version ${VERSION}" \
+    "make -C ${BUILD_DIR}"
+
+# use the newly built notmuch
+export PATH=./${BUILD_DIR}:$PATH
+
+test_begin_subtest "verify the newly built notmuch version"
+test_expect_equal "`notmuch --version`" "notmuch `cat ${BUILD_DIR}/version`"
+
+# replace the existing mails, if any, with the specified corpus
+rm -rf ${MAIL_DIR}
+cp -a ${CORPUS} ${MAIL_DIR}
+
+test_expect_code 0 "index the corpus" \
+    "notmuch new"
+
+# wrap the resulting mail store and database in a tarball
+
+cp -a ${MAIL_DIR} ${TMP_DIRECTORY}/${DBNAME}
+tar Jcf ${TMP_DIRECTORY}/${TARBALLNAME} -C ${TMP_DIRECTORY} ${DBNAME}
+mkdir -p  ${TEST_DIRECTORY}/test-databases
+cp -a ${TMP_DIRECTORY}/${TARBALLNAME} ${TEST_DIRECTORY}/test-databases
+test_expect_code 0 "create the output tarball ${TARBALLNAME}" \
+    "test -f ${TEST_DIRECTORY}/test-databases/${TARBALLNAME}"
+
+# generate a checksum file
+test_expect_code 0 "compute checksum" \
+    "(cd ${TEST_DIRECTORY}/test-databases/ && sha256sum ${TARBALLNAME} > ${TARBALLNAME}.sha256)"
+test_done
diff --git a/devel/man-to-mdwn.pl b/devel/man-to-mdwn.pl
new file mode 100755 (executable)
index 0000000..a3c4069
--- /dev/null
@@ -0,0 +1,205 @@
+#!/usr/bin/perl
+#
+# Author: Tomi Ollila
+# License: same as notmuch
+#
+# This program is used to generate mdwn-formatted notmuch manual pages
+# for notmuch wiki. Example run:
+#
+# $ ./devel/man-to-mdwn.pl doc/_build/man ../notmuch-wiki
+#
+# In case taken into more generic use, modify these comments and examples.
+
+use 5.10.1;
+use strict;
+use warnings;
+
+unless (@ARGV == 2) {
+    warn "\n$0 <source-directory> <destination-directory>\n\n";
+    # Remove/edit this comment if this script is taken into generic use.
+    warn "Example: ./devel/man-to-mdwn.pl doc/_build/man ../notmuch-wiki\n\n";
+    exit 1;
+}
+
+die "'$ARGV[0]': no such source directory\n" unless -d $ARGV[0];
+die "'$ARGV[1]': no such destination directory\n" unless -d $ARGV[1];
+
+#die "'manpages' exists\n" if -e 'manpages';
+#die "'manpages.mdwn' exists\n" if -e 'manpages.mdwn';
+
+die "Expecting '$ARGV[1]/manpages' to exist.\n" .
+  "Please create it first or adjust <destination-directory>.\n"
+  unless -d $ARGV[1] . '/manpages';
+
+my $ev = 0;
+my %fhash;
+
+open P, '-|', 'find', $ARGV[0], qw/-name *.[0-9] -print/;
+while (<P>)
+{
+    chomp;
+    next unless -f $_; # follows symlink.
+    $ev = 1, warn "'$_': no such file\n" unless -f $_;
+    my ($in, $on) = ($_, $_);
+    $on =~ s|.*/||; $on =~ tr/./-/;
+    my $f = $fhash{$on};
+    $ev = 1, warn "'$in' collides with '$f' ($on.mdwn)\n" if defined $f;
+    $fhash{$on} = $in;
+}
+close P;
+
+my %htmlqh = qw/& &amp;   < &lt;   > &gt;   ' &apos;   " &quot;/;
+# do html quotation to $_[0] (which is an alias to the given arg)
+sub htmlquote($)
+{
+    $_[0] =~ s/([&<>'"])/$htmlqh{$1}/ge;
+}
+
+sub maymakelink($);
+sub mayconvert($$);
+
+#warn keys %fhash, "\n";
+
+while (my ($k, $v) = each %fhash)
+{
+    #next if -l $v; # skip symlinks here. -- not... references there may be.
+
+    my @lines;
+    open I, '-|', qw/env -i/, "PATH=$ENV{PATH}",
+       qw/TERM=vt100 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8/,
+       qw/GROFF_NO_SGR=1 MAN_KEEP_FORMATTING=1 MANWIDTH=80/,
+       qw/man/, $v or die "$!";
+    binmode I, ':utf8';
+
+    my ($emptyline, $pre, $hl) = (0, 0, 'h1');
+    while (<I>) {
+       if (/^\s*$/) {
+           $emptyline = 1;
+           next;
+       }
+       # keep only leftmost in lines like 'NOTMUCH(1)   notmuch   NOTMUCH(1)'
+       s/\S\K\s{8,}\S.+\s{8,}\S.*//; # $hl = 'h1' if s/(?<=\S)\s{8,}.*//;
+       s/[_&]\010&/&/g;
+       s/((?:_\010[^_])+)/\001u\002$1\001\/u\002/g;
+       s/_\010(.)/$1/g;
+       s/((?:.\010.)+)/\001b\002$1\001\/b\002/g;
+       s/.\010(.)/$1/g;
+       htmlquote $_;
+       s/\001/</g; s/\002/>/g;
+
+       if (/^\S/) {
+           $pre = 0, push @lines, "</pre>\n" if $pre;
+           s/<\/?b>//g;
+           chomp;
+           $_ = "\n<$hl>$_</$hl>\n";
+           $hl = 'h2';
+           $emptyline = 0;
+       }
+       elsif (/^\s\s\s\S/) {
+           $pre = 0, push @lines, "</pre>\n" if $pre;
+           s/(?:^\s+)?<\/?b>//g;
+           chomp;
+           $_ = "\n<h3> &nbsp; $_</h3>\n";
+           $emptyline = 0;
+       }
+       else {
+           $pre = 1, push @lines, "<pre>\n" unless $pre;
+           $emptyline = 0, push @lines, "\n" if $emptyline;
+       }
+       push @lines, $_;
+    }
+    $lines[0] =~ s/^\n//;
+    $k = "$ARGV[1]/manpages/$k.mdwn";
+    open O, '>', $k or die;
+    binmode O, ':utf8';
+    print STDOUT 'Writing ', "'$k'\n";
+    select O;
+    my ($pe, $hyphen) = ('', '');
+    foreach (@lines) {
+       #print $_; next;
+       if ($pe) {
+           if (s/^(\s+)<b>([^<]+)\((\d+)\)<\/b>//) {
+               my $link = maymakelink "$pe-$2-$3";
+               $link = maymakelink "$pe$2-$3" unless $link;
+               if ($link) {
+                   print "<a href='$link'>$pe$hyphen</a>\n";
+                   print "$1<a href='$link'>$2</a>($3)";
+               }
+               else {
+                   print "<b>$pe-</b>\n";
+                   print "$1<b>$2</b>($3)";
+               }
+           } else {
+               print "<b>$pe-</b>\n";
+           }
+           $pe = '';
+       }
+       s/<b>([^<]+)\((\d+)\)<\/b>/mayconvert($1, $2)/ge;
+       ($pe, $hyphen) = ($1, $2) if s/<b>([^<]+)([-\x{2010}])<\/b>\s*$//;
+       print $_;
+    }
+}
+
+sub maymakelink($)
+{
+#    warn "$_[0]\n";
+    return "../$_[0]/" if exists $fhash{$_[0]};
+    return '';
+}
+
+sub mayconvert($$)
+{
+    my $f = "$_[0]-$_[1]";
+#    warn "$f\n";
+    return "<a href='../$f/'>$_[0]</a>($_[1])" if exists $fhash{$f};
+    return "<b>$_[0]</b>($_[1])";
+}
+
+# Finally, make manpages.mdwn
+
+open O, '>', $ARGV[1] . '/manpages.mdwn' or die $!;
+print STDOUT "Writing '$ARGV[1]/manpages.mdwn'\n";
+select O;
+print "Manual page index\n";
+print "=================\n\n";
+
+sub srt { my ($x, $y) = ($a, $b); $x =~ tr/./-/; $y =~ tr/./-/; $x cmp $y; }
+
+foreach (sort srt values %fhash)
+{
+    my $in = $_;
+    open I, '<', $in or die $!;
+    my $s;
+    while (<I>) {
+       if (/^\s*[.]TH\s+\S+\s+"?(\S+?)"?\s/) {
+           $s = $1;
+           last;
+       }
+    }
+    while (<I>) {
+       last if /^\s*[.]SH NAME/
+    }
+    my $line = '';
+    while (<I>) {
+       tr/\\//d;
+       if (/\s*(\S+)\s+(.*)/) {
+           my $e = $2;
+           # Ignoring the NAME in file, get from file name instead.
+           #my $on = (-l $in)? readlink $in: $in;
+           my $on = $in;
+           $on =~ tr/./-/; $on =~ s|.*/||;
+           my $n = $in; $n =~ s|.*/||; $n =~ tr/./-/; $n =~ s/-[^-]+$//;
+           $line = "<a href='$on/'>$n</a>($s) $e\n";
+           last;
+       }
+    }
+    die "No NAME in '$in'\n" unless $line;
+    print "* $line";
+    #warn $line;
+}
+print <<'EOF';
+
+The manual pages are licensed under
+[the GNU General Public License](https://www.gnu.org/licenses/gpl.txt),
+either version 3.0 or at your option any later version.
+EOF
diff --git a/devel/news2wiki.pl b/devel/news2wiki.pl
new file mode 100755 (executable)
index 0000000..d966bab
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+#
+# Author: Tomi Ollila
+# License: same as notmuch
+
+# This program is used to split NEWS file to separate (mdwn) files
+# for notmuch wiki. Example run:
+#
+# $ ./devel/news2wiki.pl NEWS ../notmuch-wiki/news
+#
+# In case taken into more generic use, modify these comments and examples.
+
+use strict;
+use warnings;
+
+unless (@ARGV == 2) {
+    warn "\n$0 <source-file> <destination-directory>\n\n";
+    warn "Example: ./devel/news2wiki.pl NEWS ../notmuch-wiki/news\n\n";
+    exit 1;
+}
+
+die "'$ARGV[0]': no such file\n" unless -f $ARGV[0];
+die "'$ARGV[1]': no such directory\n" unless -d $ARGV[1];
+
+open I, '<', $ARGV[0] or die "Cannot open '$ARGV[0]': $!\n";
+
+open O, '>', '/dev/null' or die $!;
+my @emptylines = ();
+my $cln;
+print "\nWriting to $ARGV[1]:\n";
+while (<I>)
+{
+    warn "$ARGV[0]:$.: tab(s) in line!\n" if /\t/;
+    warn "$ARGV[0]:$.: trailing whitespace\n" if /\s\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";
+       print O "[[!meta date=\"$2\"]]\n\n";
+       @emptylines = ();
+    }
+
+    last if /^<!--\s*$/; # Local variables block at the end (as of now).
+
+    # Buffer "trailing" empty lines -- dropped at end of file.
+    push(@emptylines, $_), next if s/^\s*$/\n/;
+    if (@emptylines) {
+       print O @emptylines;
+       @emptylines = ();
+    }
+
+    # Convert '*' to '`*`' and "*" to "`*`" so that * is not considered
+    # as starting emphasis character there. We're a bit opportunistic
+    # there -- some single * does not cause problems and, on the other
+    # hand, this would not regognize already 'secured' *:s.
+    s/'[*]'/'`*`'/g; s/"[*]"/"`*`"/g;
+
+    # Convert nonindented lines that aren't already headers or
+    # don't contain periods (.) or '!'s to level 4 header.
+    if ( /^[^\s-]/ ) {
+       my $tbc = ! /[.!]\s/;
+       chomp;
+       my @l = $_;
+       $cln = $.;
+       while (<I>) {
+           last if /^\s*$/;
+           #$cln = 0 if /^---/ or /^===/; # used for debugging.
+           $tbc = 0 if /[.!]\s/ or /^---/ or /^===/;
+           chomp; s/^\s+//;
+           push @l, $_;
+       }
+       if ($tbc) {
+           print O "### ", (join ' ', @l), "\n";
+       }
+       else {
+           #print "$ARGV[0]:$cln: skip level 4 header conversion\n" if $cln;
+           print O (join "\n", @l), "\n";
+       }
+       @emptylines = ( "\n" );
+       next;
+    }
+
+    # Markdown doc specifies that list item may have paragraphs if those
+    # are indented by 4 spaces (or a tab) from current list item marker
+    # indentation (paragraph meaning there is empty line in between).
+    # If there is empty line and next line is not indented 4 chars then
+    # that should end the above list. This doesn't happen in all markdown
+    # implementations.
+    # In our NEWS case this problem exists in release 0.6 documentation.
+    # It can be avoided by removing 2 leading spaces in lines that are not
+    # list items and requiring all that indents are 0, 2, and 4+ (to make
+    # regexp below work).
+    # Nested lists are supported but one needs to be more careful with
+    # markup there (as the hack below works only on first level).
+
+    s/^[ ][ ]// unless /^[ ][ ](?:[\s*+-]|\d+\.)\s/;
+
+    print O $_;
+}
+print "\ndone.\n";
+close O;
diff --git a/devel/nmbug/doc/.gitignore b/devel/nmbug/doc/.gitignore
new file mode 100644 (file)
index 0000000..f25d695
--- /dev/null
@@ -0,0 +1,2 @@
+*.pyc
+/_build
diff --git a/devel/nmbug/doc/Makefile b/devel/nmbug/doc/Makefile
new file mode 100644 (file)
index 0000000..7ea3ae7
--- /dev/null
@@ -0,0 +1,38 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+DOCBUILDDIR   := _build
+
+SRCDIR ?= .
+ALLSPHINXOPTS := -d $(DOCBUILDDIR)/doctrees $(SPHINXOPTS) $(SRCDIR)
+
+MAN_RST_FILES := $(shell find $(SRCDIR)/man* -name '*.rst')
+MAN_ROFF_FILES := $(patsubst $(SRCDIR)/man%.rst,$(DOCBUILDDIR)/man/man%,$(MAN_RST_FILES))
+MAN_GZIP_FILES := $(addsuffix .gz,$(MAN_ROFF_FILES))
+
+.PHONY: build-man
+build-man: $(MAN_GZIP_FILES)
+
+%.gz: %
+       rm -f $@ && gzip --stdout $^ > $@
+
+$(MAN_ROFF_FILES): $(DOCBUILDDIR)/.roff.stamp
+
+# By using $(DOCBUILDDIR)/.roff.stamp instead of $(MAN_ROFF_FILES), we
+# convey to make that a single invocation of this recipe builds all
+# of the roff files.  This prevents parallel make from starting an
+# instance of this recipe for each roff file.
+$(DOCBUILDDIR)/.roff.stamp $(MAN_ROFF_FILES): $(MAN_RST_FILES)
+       mkdir -p $(DOCBUILDDIR)
+       touch $(DOCBUILDDIR)/.roff.stamp
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(DOCBUILDDIR)/man
+       for section in 1 5; do \
+           mkdir -p $(DOCBUILDDIR)/man/man$${section}; \
+           mv $(DOCBUILDDIR)/man/*.$${section} $(DOCBUILDDIR)/man/man$${section}; \
+       done
+
+clean:
+       rm -rf $(DOCBUILDDIR) $(SRCDIR)/conf.pyc
diff --git a/devel/nmbug/doc/conf.py b/devel/nmbug/doc/conf.py
new file mode 100644 (file)
index 0000000..29379d0
--- /dev/null
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+import os.path
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'notmuch'
+authors = 'Carl Worth and many others'
+copyright = '2009-2015, {0}'.format(authors)
+
+location = os.path.dirname(__file__)
+
+dirname = location
+while True:
+    version_file = os.path.join(dirname, 'version')
+    if os.path.exists(version_file):
+        with open(version_file,'r') as f:
+            version = f.read().strip()
+            break
+    if dirname == '/':
+        raise ValueError(
+            'no version file found in this directory or its ancestors')
+    dirname = os.path.dirname(dirname)
+
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+
+man_pages = [
+    ('man1/notmuch-report.1', 'notmuch-report',
+     'generate reports from notmuch queries', [authors], 1),
+    ('man5/notmuch-report.json.5', 'notmuch-report.json',
+     'configure notmuch-report', [authors], 5),
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+texinfo_no_detailmenu = True
+
+texinfo_documents = [
+    ('man1/notmuch-report.1', 'notmuch-report',
+     'generate reports from notmuch queries', authors, 'notmuch-report',
+     'generate reports from notmuch queries', 'Miscellaneous'),
+    ('man5/notmuch-report.json.5', 'notmuch-report.json',
+     'configure notmuch-report', authors, 'notmuch-report.json',
+     'configure notmuch-report', 'Miscellaneous'),
+]
diff --git a/devel/nmbug/doc/index.rst b/devel/nmbug/doc/index.rst
new file mode 100644 (file)
index 0000000..51ac59e
--- /dev/null
@@ -0,0 +1,17 @@
+Welcome to notmuch's dev-tool documentation!
+============================================
+
+Contents:
+
+.. toctree::
+   :titlesonly:
+
+   man1/notmuch-report.1
+   man5/notmuch-report.json.5
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/devel/nmbug/doc/man1/notmuch-report.1.rst b/devel/nmbug/doc/man1/notmuch-report.1.rst
new file mode 100644 (file)
index 0000000..dfd82df
--- /dev/null
@@ -0,0 +1,54 @@
+==============
+notmuch-report
+==============
+
+SYNOPSIS
+========
+
+**notmuch-report** [options ...]
+
+DESCRIPTION
+===========
+
+Generate HTML or plain-text reports showing query results.
+
+OPTIONS
+=======
+
+  ``-h``, ``--help``
+
+    Show a help message, including a list of available options, and
+    exit.
+
+  ``--text``
+    Output plain text instead of HTML.
+
+  ``--config`` <PATH>
+    Load config from given file.  The format is described in
+    **notmuch-report.json(5)**.  If this option is not set,
+    **notmuch-report** loads the config from the Git repository at
+    ``NMBGIT``.  See :ref:`NMBGIT <NMBGIT>` for details.
+
+  ``--list-views``
+    List available views (by title) and exit.
+
+  ``--get-query`` <VIEW>
+    Print the configured query for view matching the given title.
+
+ENVIRONMENT
+===========
+
+.. _NMBGIT:
+
+  ``NMBGIT``
+    If ``--config PATH`` is not set, **notmuch-report** will attempt
+    to load a config file named ``notmuch-report.json`` from the
+    ``config`` branch of the ``NMBGIT`` repository (defaulting to
+    ``~/.nmbug``).
+
+SEE ALSO
+========
+
+**notmuch(1)**, **notmuch-report.json(5)**, **notmuch-search(1)**,
+ **notmuch-tag(1)**
+
diff --git a/devel/nmbug/doc/man5/notmuch-report.json.5.rst b/devel/nmbug/doc/man5/notmuch-report.json.5.rst
new file mode 100644 (file)
index 0000000..98d2d2e
--- /dev/null
@@ -0,0 +1,129 @@
+==============
+notmuch-report
+==============
+
+NAME
+====
+
+notmuch-report.json - configure output for **notmuch-report(1)**
+
+DESCRIPTION
+===========
+
+The config file is JSON_ with the following fields:
+
+meta
+  An object with page-wide information
+
+  title
+    Page title used in the default header.
+
+  blurb
+    Introduction paragraph used in the default header.
+
+  header
+    `Python format string`_ for the HTML header.  Optional.  It is
+    formatted with the following context:
+
+    date
+      The current UTC date.
+
+    datetime
+      The current UTC date-time.
+
+    title
+      The **meta.title** value.
+
+    blurb
+      The **meta.blurb** value.
+
+    encoding
+      The encoding used for the output file.
+
+    inter_message_padding
+      0.25em, for consistent CSS generation.
+
+    border_radius
+      0.5em, for consistent CSS generation.
+
+  footer
+    `Python format string`_ for the HTML footer.  It is formatted with
+    the same context used for **meta.header**.  Optional.
+
+  message-url
+    `Python format string`_ for message-linking URLs.  Optional.
+    Defaults to linking Gmane_.  It is formatted with the following
+    context:
+
+    message-id
+      The quoted_ message ID.
+
+    subject
+      The message subject.
+
+views
+  An array of view objects, where each object has the following
+  fields:
+
+  title
+    Header text for the view.
+
+  comment
+    Paragraph describing the view in more detail.  Optional.
+
+  id
+    Anchor string for the view.  Optional, defaulting to a slugged
+    form of the view title
+
+  query
+    An array of strings, which will be joined with 'and' to form the
+    view query.
+
+.. _Gmane: https://gmane.org/
+.. _JSON: https://json.org/
+.. _Python format string: https://docs.python.org/3/library/string.html#formatstrings
+.. _quoted: https://docs.python.org/3/library/urllib.parse.html#urllib.parse.quote
+
+EXAMPLE
+=======
+
+::
+
+  {
+    "meta": {
+      "title": "Notmuch Patches",
+      "blurb": "For more information see <a href=\"https://notmuchmail.org/nmbug\">nmbug</a>",
+      "header": "<html><head></head><body><h1>{title}</h1><p>{blurb}</p><h2>Views</h2>",
+      "footer": "<hr><p>Generated: {datetime}</p></html>",
+      "message-url": "https://mid.gmane.org/{message-id}"
+    },
+    "views": [
+      {
+        "title": "Bugs",
+        "comment": "Unresolved bugs.",
+        "query": [
+          "tag:notmuch::bug",
+          "not tag:notmuch::fixed",
+          "not tag:notmuch::wontfix"
+        ]
+      },
+      {
+        "title": "Review",
+        "comment": "These patches are under review, or waiting for feedback.",
+        "id": "under-review",
+        "query": [
+          "tag:notmuch::patch",
+          "not tag:notmuch::pushed",
+          "not tag:notmuch::obsolete",
+          "not tag:notmuch::stale",
+          "not tag:notmuch::wontfix",
+          "(tag:notmuch::moreinfo or tag:notmuch::needs-review)"
+        ]
+      }
+    ]
+  }
+
+SEE ALSO
+========
+
+**notmuch(1)**, **notmuch-report(1)**, **notmuch-search(1)**, **notmuch-tag(1)**
diff --git a/devel/nmbug/nmbug b/devel/nmbug/nmbug
new file mode 100755 (executable)
index 0000000..c35dd75
--- /dev/null
@@ -0,0 +1,852 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2011-2014 David Bremner <david@tethera.net>
+#                         W. Trevor King <wking@tremily.us>
+#
+# 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/ .
+
+"""
+Manage notmuch tags with Git
+
+Environment variables:
+
+* NMBGIT specifies the location of the git repository used by nmbug.
+  If not specified $HOME/.nmbug is used.
+* NMBPREFIX specifies the prefix in the notmuch database for tags of
+  interest to nmbug. If not specified 'notmuch::' is used.
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import codecs as _codecs
+import collections as _collections
+import functools as _functools
+import inspect as _inspect
+import locale as _locale
+import logging as _logging
+import os as _os
+import re as _re
+import shutil as _shutil
+import subprocess as _subprocess
+import sys as _sys
+import tempfile as _tempfile
+import textwrap as _textwrap
+try:  # Python 3
+    from urllib.parse import quote as _quote
+    from urllib.parse import unquote as _unquote
+except ImportError:  # Python 2
+    from urllib import quote as _quote
+    from urllib import unquote as _unquote
+
+
+__version__ = '0.3'
+
+_LOG = _logging.getLogger('nmbug')
+_LOG.setLevel(_logging.WARNING)
+_LOG.addHandler(_logging.StreamHandler())
+
+NMBGIT = _os.path.expanduser(
+    _os.getenv('NMBGIT', _os.path.join('~', '.nmbug')))
+_NMBGIT = _os.path.join(NMBGIT, '.git')
+if _os.path.isdir(_NMBGIT):
+    NMBGIT = _NMBGIT
+
+TAG_PREFIX = _os.getenv('NMBPREFIX', 'notmuch::')
+_HEX_ESCAPE_REGEX = _re.compile('%[0-9A-F]{2}')
+_TAG_DIRECTORY = 'tags/'
+_TAG_FILE_REGEX = _re.compile(_TAG_DIRECTORY + '(?P<id>[^/]*)/(?P<tag>[^/]*)')
+
+# magic hash for Git (git hash-object -t blob /dev/null)
+_EMPTYBLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
+
+
+try:
+    getattr(_tempfile, 'TemporaryDirectory')
+except AttributeError:  # Python < 3.2
+    class _TemporaryDirectory(object):
+        """
+        Fallback context manager for Python < 3.2
+
+        See PEP 343 for details on context managers [1].
+
+        [1]: https://www.python.org/dev/peps/pep-0343/
+        """
+        def __init__(self, **kwargs):
+            self.name = _tempfile.mkdtemp(**kwargs)
+
+        def __enter__(self):
+            return self.name
+
+        def __exit__(self, type, value, traceback):
+            _shutil.rmtree(self.name)
+
+
+    _tempfile.TemporaryDirectory = _TemporaryDirectory
+
+
+def _hex_quote(string, safe='+@=:,'):
+    """
+    quote('abc def') -> 'abc%20def'.
+
+    Wrap urllib.parse.quote with additional safe characters (in
+    addition to letters, digits, and '_.-') and lowercase hex digits
+    (e.g. '%3a' instead of '%3A').
+    """
+    uppercase_escapes = _quote(string, safe)
+    return _HEX_ESCAPE_REGEX.sub(
+        lambda match: match.group(0).lower(),
+        uppercase_escapes)
+
+
+_ENCODED_TAG_PREFIX = _hex_quote(TAG_PREFIX, safe='+@=,')  # quote ':'
+
+
+def _xapian_quote(string):
+    """
+    Quote a string for Xapian's QueryParser.
+
+    Xapian uses double-quotes for quoting strings.  You can escape
+    internal quotes by repeating them [1,2,3].
+
+    [1]: https://trac.xapian.org/ticket/128#comment:2
+    [2]: https://trac.xapian.org/ticket/128#comment:17
+    [3]: https://trac.xapian.org/changeset/13823/svn
+    """
+    return '"{0}"'.format(string.replace('"', '""'))
+
+
+def _xapian_unquote(string):
+    """
+    Unquote a Xapian-quoted string.
+    """
+    if string.startswith('"') and string.endswith('"'):
+        return string[1:-1].replace('""', '"')
+    return string
+
+
+class SubprocessError(RuntimeError):
+    "A subprocess exited with a nonzero status"
+    def __init__(self, args, status, stdout=None, stderr=None):
+        self.status = status
+        self.stdout = stdout
+        self.stderr = stderr
+        msg = '{args} exited with {status}'.format(args=args, status=status)
+        if stderr:
+            msg = '{msg}: {stderr}'.format(msg=msg, stderr=stderr)
+        super(SubprocessError, self).__init__(msg)
+
+
+class _SubprocessContextManager(object):
+    """
+    PEP 343 context manager for subprocesses.
+
+    'expect' holds a tuple of acceptable exit codes, otherwise we'll
+    raise a SubprocessError in __exit__.
+    """
+    def __init__(self, process, args, expect=(0,)):
+        self._process = process
+        self._args = args
+        self._expect = expect
+
+    def __enter__(self):
+        return self._process
+
+    def __exit__(self, type, value, traceback):
+        for name in ['stdin', 'stdout', 'stderr']:
+            stream = getattr(self._process, name)
+            if stream:
+                stream.close()
+                setattr(self._process, name, None)
+        status = self._process.wait()
+        _LOG.debug(
+            'collect {args} with status {status} (expected {expect})'.format(
+                args=self._args, status=status, expect=self._expect))
+        if status not in self._expect:
+            raise SubprocessError(args=self._args, status=status)
+
+    def wait(self):
+        return self._process.wait()
+
+
+def _spawn(args, input=None, additional_env=None, wait=False, stdin=None,
+           stdout=None, stderr=None, encoding=_locale.getpreferredencoding(),
+           expect=(0,), **kwargs):
+    """Spawn a subprocess, and optionally wait for it to finish.
+
+    This wrapper around subprocess.Popen has two modes, depending on
+    the truthiness of 'wait'.  If 'wait' is true, we use p.communicate
+    internally to write 'input' to the subprocess's stdin and read
+    from it's stdout/stderr.  If 'wait' is False, we return a
+    _SubprocessContextManager instance for fancier handling
+    (e.g. piping between processes).
+
+    For 'wait' calls when you want to write to the subprocess's stdin,
+    you only need to set 'input' to your content.  When 'input' is not
+    None but 'stdin' is, we'll automatically set 'stdin' to PIPE
+    before calling Popen.  This avoids having the subprocess
+    accidentally inherit the launching process's stdin.
+    """
+    _LOG.debug('spawn {args} (additional env. var.: {env})'.format(
+        args=args, env=additional_env))
+    if not stdin and input is not None:
+        stdin = _subprocess.PIPE
+    if additional_env:
+        if not kwargs.get('env'):
+            kwargs['env'] = dict(_os.environ)
+        kwargs['env'].update(additional_env)
+    p = _subprocess.Popen(
+        args, stdin=stdin, stdout=stdout, stderr=stderr, **kwargs)
+    if wait:
+        if hasattr(input, 'encode'):
+            input = input.encode(encoding)
+        (stdout, stderr) = p.communicate(input=input)
+        status = p.wait()
+        _LOG.debug(
+            'collect {args} with status {status} (expected {expect})'.format(
+                args=args, status=status, expect=expect))
+        if stdout is not None:
+            stdout = stdout.decode(encoding)
+        if stderr is not None:
+            stderr = stderr.decode(encoding)
+        if status not in expect:
+            raise SubprocessError(
+                args=args, status=status, stdout=stdout, stderr=stderr)
+        return (status, stdout, stderr)
+    if p.stdin and not stdin:
+        p.stdin.close()
+        p.stdin = None
+    if p.stdin:
+        p.stdin = _codecs.getwriter(encoding=encoding)(stream=p.stdin)
+    stream_reader = _codecs.getreader(encoding=encoding)
+    if p.stdout:
+        p.stdout = stream_reader(stream=p.stdout)
+    if p.stderr:
+        p.stderr = stream_reader(stream=p.stderr)
+    return _SubprocessContextManager(args=args, process=p, expect=expect)
+
+
+def _git(args, **kwargs):
+    args = ['git', '--git-dir', NMBGIT] + list(args)
+    return _spawn(args=args, **kwargs)
+
+
+def _get_current_branch():
+    """Get the name of the current branch.
+
+    Return 'None' if we're not on a branch.
+    """
+    try:
+        (status, branch, stderr) = _git(
+            args=['symbolic-ref', '--short', 'HEAD'],
+            stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, wait=True)
+    except SubprocessError as e:
+        if 'not a symbolic ref' in e:
+            return None
+        raise
+    return branch.strip()
+
+
+def _get_remote():
+    "Get the default remote for the current branch."
+    local_branch = _get_current_branch()
+    (status, remote, stderr) = _git(
+        args=['config', 'branch.{0}.remote'.format(local_branch)],
+        stdout=_subprocess.PIPE, wait=True)
+    return remote.strip()
+
+
+def get_tags(prefix=None):
+    "Get a list of tags with a given prefix."
+    if prefix is None:
+        prefix = TAG_PREFIX
+    (status, stdout, stderr) = _spawn(
+        args=['notmuch', 'search', '--output=tags', '*'],
+        stdout=_subprocess.PIPE, wait=True)
+    return [tag for tag in stdout.splitlines() if tag.startswith(prefix)]
+
+
+def archive(treeish='HEAD', args=()):
+    """
+    Dump a tar archive of the current nmbug tag set.
+
+    Using 'git archive'.
+
+    Each tag $tag for message with Message-Id $id is written to
+    an empty file
+
+      tags/encode($id)/encode($tag)
+
+    The encoding preserves alphanumerics, and the characters
+    "+-_@=.:," (not the quotes).  All other octets are replaced with
+    '%' followed by a two digit hex number.
+    """
+    _git(args=['archive', treeish] + list(args), wait=True)
+
+
+def clone(repository):
+    """
+    Create a local nmbug repository from a remote source.
+
+    This wraps 'git clone', adding some options to avoid creating a
+    working tree while preserving remote-tracking branches and
+    upstreams.
+    """
+    with _tempfile.TemporaryDirectory(prefix='nmbug-clone.') as workdir:
+        _spawn(
+            args=[
+                'git', 'clone', '--no-checkout', '--separate-git-dir', NMBGIT,
+                repository, workdir],
+            wait=True)
+    _git(args=['config', '--unset', 'core.worktree'], wait=True, expect=(0, 5))
+    _git(args=['config', 'core.bare', 'true'], wait=True)
+    _git(args=['branch', 'config', 'origin/config'], wait=True)
+    existing_tags = get_tags()
+    if existing_tags:
+        _LOG.warning(
+            'Not checking out to avoid clobbering existing tags: {}'.format(
+            ', '.join(existing_tags)))
+    else:
+        checkout()
+
+
+def _is_committed(status):
+    return len(status['added']) + len(status['deleted']) == 0
+
+
+def commit(treeish='HEAD', message=None):
+    """
+    Commit prefix-matching tags from the notmuch database to Git.
+    """
+    status = get_status()
+
+    if _is_committed(status=status):
+        _LOG.warning('Nothing to commit')
+        return
+
+    _git(args=['read-tree', '--empty'], wait=True)
+    _git(args=['read-tree', treeish], wait=True)
+    try:
+        _update_index(status=status)
+        (_, tree, _) = _git(
+            args=['write-tree'],
+            stdout=_subprocess.PIPE,
+            wait=True)
+        (_, parent, _) = _git(
+            args=['rev-parse', treeish],
+            stdout=_subprocess.PIPE,
+            wait=True)
+        (_, commit, _) = _git(
+            args=['commit-tree', tree.strip(), '-p', parent.strip()],
+            input=message,
+            stdout=_subprocess.PIPE,
+            wait=True)
+        _git(
+            args=['update-ref', treeish, commit.strip()],
+            stdout=_subprocess.PIPE,
+            wait=True)
+    except Exception as e:
+        _git(args=['read-tree', '--empty'], wait=True)
+        _git(args=['read-tree', treeish], wait=True)
+        raise
+
+def _update_index(status):
+    with _git(
+            args=['update-index', '--index-info'],
+            stdin=_subprocess.PIPE) as p:
+        for id, tags in status['deleted'].items():
+            for line in _index_tags_for_message(id=id, status='D', tags=tags):
+                p.stdin.write(line)
+        for id, tags in status['added'].items():
+            for line in _index_tags_for_message(id=id, status='A', tags=tags):
+                p.stdin.write(line)
+
+
+def fetch(remote=None):
+    """
+    Fetch changes from the remote repository.
+
+    See 'merge' to bring those changes into notmuch.
+    """
+    args = ['fetch']
+    if remote:
+        args.append(remote)
+    _git(args=args, wait=True)
+
+
+def checkout():
+    """
+    Update the notmuch database from Git.
+
+    This is mainly useful to discard your changes in notmuch relative
+    to Git.
+    """
+    status = get_status()
+    with _spawn(
+            args=['notmuch', 'tag', '--batch'], stdin=_subprocess.PIPE) as p:
+        for id, tags in status['added'].items():
+            p.stdin.write(_batch_line(action='-', id=id, tags=tags))
+        for id, tags in status['deleted'].items():
+            p.stdin.write(_batch_line(action='+', id=id, tags=tags))
+
+
+def _batch_line(action, id, tags):
+    """
+    'notmuch tag --batch' line for adding/removing tags.
+
+    Set 'action' to '-' to remove a tag or '+' to add the tags to a
+    given message id.
+    """
+    tag_string = ' '.join(
+        '{action}{prefix}{tag}'.format(
+            action=action, prefix=_ENCODED_TAG_PREFIX, tag=_hex_quote(tag))
+        for tag in tags)
+    line = '{tags} -- id:{id}\n'.format(
+        tags=tag_string, id=_xapian_quote(string=id))
+    return line
+
+
+def _insist_committed():
+    "Die if the the notmuch tags don't match the current HEAD."
+    status = get_status()
+    if not _is_committed(status=status):
+        _LOG.error('\n'.join([
+            'Uncommitted changes to {prefix}* tags in notmuch',
+            '',
+            "For a summary of changes, run 'nmbug status'",
+            "To save your changes,     run 'nmbug commit' before merging/pull",
+            "To discard your changes,  run 'nmbug checkout'",
+            ]).format(prefix=TAG_PREFIX))
+        _sys.exit(1)
+
+
+def pull(repository=None, refspecs=None):
+    """
+    Pull (merge) remote repository changes to notmuch.
+
+    'pull' is equivalent to 'fetch' followed by 'merge'.  We use the
+    Git-configured repository for your current branch
+    (branch.<name>.repository, likely 'origin', and
+    branch.<name>.merge, likely 'master').
+    """
+    _insist_committed()
+    if refspecs and not repository:
+        repository = _get_remote()
+    args = ['pull']
+    if repository:
+        args.append(repository)
+    if refspecs:
+        args.extend(refspecs)
+    with _tempfile.TemporaryDirectory(prefix='nmbug-pull.') as workdir:
+        for command in [
+                ['reset', '--hard'],
+                args]:
+            _git(
+                args=command,
+                additional_env={'GIT_WORK_TREE': workdir},
+                wait=True)
+    checkout()
+
+
+def merge(reference='@{upstream}'):
+    """
+    Merge changes from 'reference' into HEAD and load the result into notmuch.
+
+    The default reference is '@{upstream}'.
+    """
+    _insist_committed()
+    with _tempfile.TemporaryDirectory(prefix='nmbug-merge.') as workdir:
+        for command in [
+                ['reset', '--hard'],
+                ['merge', reference]]:
+            _git(
+                args=command,
+                additional_env={'GIT_WORK_TREE': workdir},
+                wait=True)
+    checkout()
+
+
+def log(args=()):
+    """
+    A simple wrapper for 'git log'.
+
+    After running 'nmbug fetch', you can inspect the changes with
+    'nmbug log HEAD..@{upstream}'.
+    """
+    # we don't want output trapping here, because we want the pager.
+    args = ['log', '--name-status', '--no-renames'] + list(args)
+    with _git(args=args, expect=(0, 1, -13)) as p:
+        p.wait()
+
+
+def push(repository=None, refspecs=None):
+    "Push the local nmbug Git state to a remote repository."
+    if refspecs and not repository:
+        repository = _get_remote()
+    args = ['push']
+    if repository:
+        args.append(repository)
+    if refspecs:
+        args.extend(refspecs)
+    _git(args=args, wait=True)
+
+
+def status():
+    """
+    Show pending updates in notmuch or git repo.
+
+    Prints lines of the form
+
+      ng Message-Id tag
+
+    where n is a single character representing notmuch database status
+
+    * A
+
+      Tag is present in notmuch database, but not committed to nmbug
+      (equivalently, tag has been deleted in nmbug repo, e.g. by a
+      pull, but not restored to notmuch database).
+
+    * D
+
+      Tag is present in nmbug repo, but not restored to notmuch
+      database (equivalently, tag has been deleted in notmuch).
+
+    * U
+
+      Message is unknown (missing from local notmuch database).
+
+    The second character (if present) represents a difference between
+    local and upstream branches. Typically 'nmbug fetch' needs to be
+    run to update this.
+
+    * a
+
+      Tag is present in upstream, but not in the local Git branch.
+
+    * d
+
+      Tag is present in local Git branch, but not upstream.
+    """
+    status = get_status()
+    # 'output' is a nested defaultdict for message status:
+    # * The outer dict is keyed by message id.
+    # * The inner dict is keyed by tag name.
+    # * The inner dict values are status strings (' a', 'Dd', ...).
+    output = _collections.defaultdict(
+        lambda : _collections.defaultdict(lambda : ' '))
+    for id, tags in status['added'].items():
+        for tag in tags:
+            output[id][tag] = 'A'
+    for id, tags in status['deleted'].items():
+        for tag in tags:
+            output[id][tag] = 'D'
+    for id, tags in status['missing'].items():
+        for tag in tags:
+            output[id][tag] = 'U'
+    if _is_unmerged():
+        for id, tag in _diff_refs(filter='A'):
+            output[id][tag] += 'a'
+        for id, tag in _diff_refs(filter='D'):
+            output[id][tag] += 'd'
+    for id, tag_status in sorted(output.items()):
+        for tag, status in sorted(tag_status.items()):
+            print('{status}\t{id}\t{tag}'.format(
+                status=status, id=id, tag=tag))
+
+
+def _is_unmerged(ref='@{upstream}'):
+    try:
+        (status, fetch_head, stderr) = _git(
+            args=['rev-parse', ref],
+            stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, wait=True)
+    except SubprocessError as e:
+        if 'No upstream configured' in e.stderr:
+            return
+        raise
+    (status, base, stderr) = _git(
+        args=['merge-base', 'HEAD', ref],
+        stdout=_subprocess.PIPE, wait=True)
+    return base != fetch_head
+
+
+def get_status():
+    status = {
+        'deleted': {},
+        'missing': {},
+        }
+    index = _index_tags()
+    maybe_deleted = _diff_index(index=index, filter='D')
+    for id, tags in maybe_deleted.items():
+        (_, stdout, stderr) = _spawn(
+            args=['notmuch', 'search', '--output=files', 'id:{0}'.format(id)],
+            stdout=_subprocess.PIPE,
+            wait=True)
+        if stdout:
+            status['deleted'][id] = tags
+        else:
+            status['missing'][id] = tags
+    status['added'] = _diff_index(index=index, filter='A')
+    _os.remove(index)
+    return status
+
+
+def _index_tags():
+    "Write notmuch tags to the nmbug.index."
+    path = _os.path.join(NMBGIT, 'nmbug.index')
+    query = ' '.join('tag:"{tag}"'.format(tag=tag) for tag in get_tags())
+    prefix = '+{0}'.format(_ENCODED_TAG_PREFIX)
+    _git(
+        args=['read-tree', '--empty'],
+        additional_env={'GIT_INDEX_FILE': path}, wait=True)
+    with _spawn(
+            args=['notmuch', 'dump', '--format=batch-tag', '--', query],
+            stdout=_subprocess.PIPE) as notmuch:
+        with _git(
+                args=['update-index', '--index-info'],
+                stdin=_subprocess.PIPE,
+                additional_env={'GIT_INDEX_FILE': path}) as git:
+            for line in notmuch.stdout:
+                if line.strip().startswith('#'):
+                    continue
+                (tags_string, id) = [_.strip() for _ in line.split(' -- id:')]
+                tags = [
+                    _unquote(tag[len(prefix):])
+                    for tag in tags_string.split()
+                    if tag.startswith(prefix)]
+                id = _xapian_unquote(string=id)
+                for line in _index_tags_for_message(
+                        id=id, status='A', tags=tags):
+                    git.stdin.write(line)
+    return path
+
+
+def _index_tags_for_message(id, status, tags):
+    """
+    Update the Git index to either create or delete an empty file.
+
+    Neither 'id' nor the tags in 'tags' should be encoded/escaped.
+    """
+    mode = '100644'
+    hash = _EMPTYBLOB
+
+    if status == 'D':
+        mode = '0'
+        hash = '0000000000000000000000000000000000000000'
+
+    for tag in tags:
+        path = 'tags/{id}/{tag}'.format(
+            id=_hex_quote(string=id), tag=_hex_quote(string=tag))
+        yield '{mode} {hash}\t{path}\n'.format(mode=mode, hash=hash, path=path)
+
+
+def _diff_index(index, filter):
+    """
+    Get an {id: {tag, ...}} dict for a given filter.
+
+    For example, use 'A' to find added tags, and 'D' to find deleted tags.
+    """
+    s = _collections.defaultdict(set)
+    with _git(
+            args=[
+                'diff-index', '--cached', '--diff-filter', filter,
+                '--name-only', 'HEAD'],
+            additional_env={'GIT_INDEX_FILE': index},
+            stdout=_subprocess.PIPE) as p:
+        # Once we drop Python < 3.3, we can use 'yield from' here
+        for id, tag in _unpack_diff_lines(stream=p.stdout):
+            s[id].add(tag)
+    return s
+
+
+def _diff_refs(filter, a='HEAD', b='@{upstream}'):
+    with _git(
+            args=['diff', '--diff-filter', filter, '--name-only', a, b],
+            stdout=_subprocess.PIPE) as p:
+        # Once we drop Python < 3.3, we can use 'yield from' here
+        for id, tag in _unpack_diff_lines(stream=p.stdout):
+            yield id, tag
+
+
+def _unpack_diff_lines(stream):
+    "Iterate through (id, tag) tuples in a diff stream."
+    for line in stream:
+        match = _TAG_FILE_REGEX.match(line.strip())
+        if not match:
+            message = 'non-tag line in diff: {!r}'.format(line.strip())
+            if line.startswith(_TAG_DIRECTORY):
+                raise ValueError(message)
+            _LOG.info(message)
+            continue
+        id = _unquote(match.group('id'))
+        tag = _unquote(match.group('tag'))
+        yield (id, tag)
+
+
+def _help(parser, command=None):
+    """
+    Show help for an nmbug command.
+
+    Because some folks prefer:
+
+      $ nmbug help COMMAND
+
+    to
+
+      $ nmbug COMMAND --help
+    """
+    if command:
+        parser.parse_args([command, '--help'])
+    else:
+        parser.parse_args(['--help'])
+
+
+if __name__ == '__main__':
+    import argparse
+
+    parser = argparse.ArgumentParser(
+        description=__doc__.strip(),
+        formatter_class=argparse.RawDescriptionHelpFormatter)
+    parser.add_argument(
+        '-v', '--version', action='version',
+        version='%(prog)s {}'.format(__version__))
+    parser.add_argument(
+        '-l', '--log-level',
+        choices=['critical', 'error', 'warning', 'info', 'debug'],
+        help='Log verbosity.  Defaults to {!r}.'.format(
+            _logging.getLevelName(_LOG.level).lower()))
+
+    help = _functools.partial(_help, parser=parser)
+    help.__doc__ = _help.__doc__
+    subparsers = parser.add_subparsers(
+        title='commands',
+        description=(
+            'For help on a particular command, run: '
+            "'%(prog)s ... <command> --help'."))
+    for command in [
+            'archive',
+            'checkout',
+            'clone',
+            'commit',
+            'fetch',
+            'help',
+            'log',
+            'merge',
+            'pull',
+            'push',
+            'status',
+            ]:
+        func = locals()[command]
+        doc = _textwrap.dedent(func.__doc__).strip().replace('%', '%%')
+        subparser = subparsers.add_parser(
+            command,
+            help=doc.splitlines()[0],
+            description=doc,
+            formatter_class=argparse.RawDescriptionHelpFormatter)
+        subparser.set_defaults(func=func)
+        if command == 'archive':
+            subparser.add_argument(
+                'treeish', metavar='TREE-ISH', nargs='?', default='HEAD',
+                help=(
+                    'The tree or commit to produce an archive for.  Defaults '
+                    "to 'HEAD'."))
+            subparser.add_argument(
+                'args', metavar='ARG', nargs='*',
+                help=(
+                    "Argument passed through to 'git archive'.  Set anything "
+                    'before <tree-ish>, see git-archive(1) for details.'))
+        elif command == 'clone':
+            subparser.add_argument(
+                'repository',
+                help=(
+                    'The (possibly remote) repository to clone from.  See the '
+                    'URLS section of git-clone(1) for more information on '
+                    'specifying repositories.'))
+        elif command == 'commit':
+            subparser.add_argument(
+                'message', metavar='MESSAGE', default='', nargs='?',
+                help='Text for the commit message.')
+        elif command == 'fetch':
+            subparser.add_argument(
+                'remote', metavar='REMOTE', nargs='?',
+                help=(
+                    'Override the default configured in branch.<name>.remote '
+                    'to fetch from a particular remote repository (e.g. '
+                    "'origin')."))
+        elif command == 'help':
+            subparser.add_argument(
+                'command', metavar='COMMAND', nargs='?',
+                help='The command to show help for.')
+        elif command == 'log':
+            subparser.add_argument(
+                'args', metavar='ARG', nargs='*',
+                help="Additional argument passed through to 'git log'.")
+        elif command == 'merge':
+            subparser.add_argument(
+                'reference', metavar='REFERENCE', default='@{upstream}',
+                nargs='?',
+                help=(
+                    'Reference, usually other branch heads, to merge into '
+                    "our branch.  Defaults to '@{upstream}'."))
+        elif command == 'pull':
+            subparser.add_argument(
+                'repository', metavar='REPOSITORY', default=None, nargs='?',
+                help=(
+                    'The "remote" repository that is the source of the pull.  '
+                    'This parameter can be either a URL (see the section GIT '
+                    'URLS in git-pull(1)) or the name of a remote (see the '
+                    'section REMOTES in git-pull(1)).'))
+            subparser.add_argument(
+                'refspecs', metavar='REFSPEC', default=None, nargs='*',
+                help=(
+                    'Refspec (usually a branch name) to fetch and merge.  See '
+                    'the <refspec> entry in the OPTIONS section of '
+                    'git-pull(1) for other possibilities.'))
+        elif command == 'push':
+            subparser.add_argument(
+               'repository', metavar='REPOSITORY', default=None, nargs='?',
+                help=(
+                    'The "remote" repository that is the destination of the '
+                    'push.  This parameter can be either a URL (see the '
+                    'section GIT URLS in git-push(1)) or the name of a remote '
+                    '(see the section REMOTES in git-push(1)).'))
+            subparser.add_argument(
+                'refspecs', metavar='REFSPEC', default=None, nargs='*',
+                help=(
+                    'Refspec (usually a branch name) to push.  See '
+                    'the <refspec> entry in the OPTIONS section of '
+                    'git-push(1) for other possibilities.'))
+
+    args = parser.parse_args()
+
+    if args.log_level:
+        level = getattr(_logging, args.log_level.upper())
+        _LOG.setLevel(level)
+
+    if not getattr(args, 'func', None):
+        parser.print_usage()
+        _sys.exit(1)
+
+    if args.func == help:
+        arg_names = ['command']
+    else:
+        (arg_names, varargs, varkw) = _inspect.getargs(args.func.__code__)
+    kwargs = {key: getattr(args, key) for key in arg_names if key in args}
+    try:
+        args.func(**kwargs)
+    except SubprocessError as e:
+        if _LOG.level == _logging.DEBUG:
+            raise  # don't mask the traceback
+        _LOG.error(str(e))
+        _sys.exit(1)
diff --git a/devel/nmbug/notmuch-report b/devel/nmbug/notmuch-report
new file mode 100755 (executable)
index 0000000..eaceb2c
--- /dev/null
@@ -0,0 +1,440 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2011-2012 David Bremner <david@tethera.net>
+#
+# 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 https://www.gnu.org/licenses/ .
+
+"""Generate text and/or HTML for one or more notmuch searches.
+
+Messages matching each search are grouped by thread.  Each message
+that contains both a subject and message-id will have the displayed
+subject link to an archive view of the message (defaulting to Gmane).
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import codecs
+import collections
+import datetime
+import email.utils
+try:  # Python 3
+    from urllib.parse import quote
+except ImportError:  # Python 2
+    from urllib import quote
+import json
+import argparse
+import os
+import re
+import sys
+import subprocess
+import xml.sax.saxutils
+
+
+_ENCODING = 'UTF-8'
+_PAGES = {}
+
+
+if not hasattr(collections, 'OrderedDict'):  # Python 2.6 or earlier
+    class _OrderedDict (dict):
+        "Just enough of a stub to get through Page._get_threads"
+        def __init__(self, *args, **kwargs):
+            super(_OrderedDict, self).__init__(*args, **kwargs)
+            self._keys = []  # record key order
+
+        def __setitem__(self, key, value):
+            super(_OrderedDict, self).__setitem__(key, value)
+            self._keys.append(key)
+
+        def values(self):
+            for key in self._keys:
+                yield self[key]
+
+
+    collections.OrderedDict = _OrderedDict
+
+
+class ConfigError (Exception):
+    """Errors with config file usage
+    """
+    pass
+
+
+def read_config(path=None, encoding=None):
+    "Read config from json file"
+    if not encoding:
+        encoding = _ENCODING
+    if path:
+        try:
+            with open(path, 'rb') as f:
+                config_bytes = f.read()
+        except IOError as e:
+            raise ConfigError('Could not read config from {}'.format(path))
+    else:
+        nmbhome = os.getenv('NMBGIT', os.path.expanduser('~/.nmbug'))
+        branch = 'config'
+        filename = 'notmuch-report.json'
+
+        # read only the first line from the pipe
+        sha1_bytes = subprocess.Popen(
+            ['git', '--git-dir', nmbhome, 'show-ref', '-s', '--heads', branch],
+            stdout=subprocess.PIPE).stdout.readline()
+        sha1 = sha1_bytes.decode(encoding).rstrip()
+        if not sha1:
+            raise ConfigError(
+                ("No local branch '{branch}' in {nmbgit}.  "
+                 'Checkout a local {branch} branch or explicitly set --config.'
+                ).format(branch=branch, nmbgit=nmbhome))
+
+        p = subprocess.Popen(
+            ['git', '--git-dir', nmbhome, 'cat-file', 'blob',
+             '{}:{}'.format(sha1, filename)],
+            stdout=subprocess.PIPE)
+        config_bytes, err = p.communicate()
+        status = p.wait()
+        if status != 0:
+            raise ConfigError(
+                ("Missing {filename} in branch '{branch}' of {nmbgit}.  "
+                 'Add the file or explicitly set --config.'
+                ).format(filename=filename, branch=branch, nmbgit=nmbhome))
+
+    config_json = config_bytes.decode(encoding)
+    try:
+        return json.loads(config_json)
+    except ValueError as e:
+        if not path:
+            path = "{} in branch '{}' of {}".format(
+                filename, branch, nmbhome)
+        raise ConfigError(
+            'Could not parse JSON from the config file {}:\n{}'.format(
+                path, e))
+
+
+class Thread (list):
+    def __init__(self):
+        self.running_data = {}
+
+
+class Page (object):
+    def __init__(self, header=None, footer=None):
+        self.header = header
+        self.footer = footer
+
+    def write(self, database, views, stream=None):
+        if not stream:
+            try:  # Python 3
+                byte_stream = sys.stdout.buffer
+            except AttributeError:  # Python 2
+                byte_stream = sys.stdout
+            stream = codecs.getwriter(encoding=_ENCODING)(stream=byte_stream)
+        self._write_header(views=views, stream=stream)
+        for view in views:
+            self._write_view(database=database, view=view, stream=stream)
+        self._write_footer(views=views, stream=stream)
+
+    def _write_header(self, views, stream):
+        if self.header:
+            stream.write(self.header)
+
+    def _write_footer(self, views, stream):
+        if self.footer:
+            stream.write(self.footer)
+
+    def _write_view(self, database, view, stream):
+        # sort order, default to oldest-first
+        sort_key = view.get('sort', 'oldest-first')
+        # dynamically accept all values in Query.SORT
+        sort_attribute = sort_key.upper().replace('-', '_')
+        try:
+            sort = getattr(notmuch.Query.SORT, sort_attribute)
+        except AttributeError:
+            raise ConfigError('Invalid sort setting for {}: {!r}'.format(
+                view['title'], sort_key))
+        if 'query-string' not in view:
+            query = view['query']
+            view['query-string'] = ' and '.join(
+                '( {} )'.format(q) for q in query)
+        q = notmuch.Query(database, view['query-string'])
+        q.set_sort(sort)
+        threads = self._get_threads(messages=q.search_messages())
+        self._write_view_header(view=view, stream=stream)
+        self._write_threads(threads=threads, stream=stream)
+
+    def _get_threads(self, messages):
+        threads = collections.OrderedDict()
+        for message in messages:
+            thread_id = message.get_thread_id()
+            if thread_id in threads:
+                thread = threads[thread_id]
+            else:
+                thread = Thread()
+                threads[thread_id] = thread
+            thread.running_data, display_data = self._message_display_data(
+                running_data=thread.running_data, message=message)
+            thread.append(display_data)
+        return list(threads.values())
+
+    def _write_view_header(self, view, stream):
+        pass
+
+    def _write_threads(self, threads, stream):
+        for thread in threads:
+            for message_display_data in thread:
+                stream.write(
+                    ('{date:10.10s} {from:20.20s} {subject:40.40s}\n'
+                     '{message-id-term:>72}\n'
+                     ).format(**message_display_data))
+            if thread != threads[-1]:
+                stream.write('\n')
+
+    def _message_display_data(self, running_data, message):
+        headers = ('thread-id', 'message-id', 'date', 'from', 'subject')
+        data = {}
+        for header in headers:
+            if header == 'thread-id':
+                value = message.get_thread_id()
+            elif header == 'message-id':
+                value = message.get_message_id()
+                data['message-id-term'] = 'id:"{0}"'.format(value)
+            elif header == 'date':
+                value = str(datetime.datetime.utcfromtimestamp(
+                    message.get_date()).date())
+            else:
+                value = message.get_header(header)
+            if header == 'from':
+                (value, addr) = email.utils.parseaddr(value)
+                if not value:
+                    value = addr.split('@')[0]
+            data[header] = value
+        next_running_data = data.copy()
+        for header, value in data.items():
+            if header in ['message-id', 'subject']:
+                continue
+            if value == running_data.get(header, None):
+                data[header] = ''
+        return (next_running_data, data)
+
+
+class HtmlPage (Page):
+    _slug_regexp = re.compile('\W+')
+
+    def __init__(self, message_url_template, **kwargs):
+        self.message_url_template = message_url_template
+        super(HtmlPage, self).__init__(**kwargs)
+
+    def _write_header(self, views, stream):
+        super(HtmlPage, self)._write_header(views=views, stream=stream)
+        stream.write('<ul>\n')
+        for view in views:
+            if 'id' not in view:
+                view['id'] = self._slug(view['title'])
+            stream.write(
+                '<li><a href="#{id}">{title}</a></li>\n'.format(**view))
+        stream.write('</ul>\n')
+
+    def _write_view_header(self, view, stream):
+        stream.write('<h3 id="{id}">{title}</h3>\n'.format(**view))
+        stream.write('<p>\n')
+        if 'comment' in view:
+            stream.write(view['comment'])
+            stream.write('\n')
+        for line in [
+                'The view is generated from the following query:',
+                '</p>',
+                '<p>',
+                '  <code>',
+                view['query-string'],
+                '  </code>',
+                '</p>',
+                ]:
+            stream.write(line)
+            stream.write('\n')
+
+    def _write_threads(self, threads, stream):
+        if not threads:
+            return
+        stream.write('<table>\n')
+        for thread in threads:
+            stream.write('  <tbody>\n')
+            for message_display_data in thread:
+                stream.write((
+                    '    <tr class="message-first">\n'
+                    '      <td>{date}</td>\n'
+                    '      <td><code>{message-id-term}</code></td>\n'
+                    '    </tr>\n'
+                    '    <tr class="message-last">\n'
+                    '      <td>{from}</td>\n'
+                    '      <td>{subject}</td>\n'
+                    '    </tr>\n'
+                    ).format(**message_display_data))
+            stream.write('  </tbody>\n')
+            if thread != threads[-1]:
+                stream.write(
+                    '  <tbody><tr><td colspan="2"><br /></td></tr></tbody>\n')
+        stream.write('</table>\n')
+
+    def _message_display_data(self, *args, **kwargs):
+        running_data, display_data = super(
+            HtmlPage, self)._message_display_data(
+                *args, **kwargs)
+        if 'subject' in display_data and 'message-id' in display_data:
+            d = {
+                'message-id': quote(display_data['message-id']),
+                'subject': xml.sax.saxutils.escape(display_data['subject']),
+                }
+            d['url'] = self.message_url_template.format(**d)
+            display_data['subject'] = (
+                '<a href="{url}">{subject}</a>'
+                ).format(**d)
+        for key in ['message-id', 'from']:
+            if key in display_data:
+                display_data[key] = xml.sax.saxutils.escape(display_data[key])
+        return (running_data, display_data)
+
+    def _slug(self, string):
+        return self._slug_regexp.sub('-', string)
+
+parser = argparse.ArgumentParser(description=__doc__)
+parser.add_argument(
+    '--text', action='store_true', help='output plain text format')
+parser.add_argument(
+    '--config', metavar='PATH',
+    help='load config from given file.  '
+        'The format is described in notmuch-report.json(5).')
+parser.add_argument(
+    '--list-views', action='store_true', help='list views')
+parser.add_argument(
+    '--get-query', metavar='VIEW', help='get query for view')
+
+
+args = parser.parse_args()
+
+try:
+    config = read_config(path=args.config)
+except ConfigError as e:
+    print(e, file=sys.stderr)
+    sys.exit(1)
+
+header_template = config['meta'].get('header', '''<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
+  <title>{title}</title>
+  <style media="screen" type="text/css">
+    h1 {{
+      font-size: 1.5em;
+    }}
+    h2 {{
+      font-size: 1.17em;
+    }}
+    h3 {{
+      font-size: 100%;
+    }}
+    table {{
+      border-spacing: 0;
+    }}
+    tr.message-first td {{
+      padding-top: {inter_message_padding};
+    }}
+    tr.message-last td {{
+      padding-bottom: {inter_message_padding};
+    }}
+    td {{
+      padding-left: {border_radius};
+      padding-right: {border_radius};
+    }}
+    tr:first-child td:first-child {{
+      border-top-left-radius: {border_radius};
+    }}
+    tr:first-child td:last-child {{
+      border-top-right-radius: {border_radius};
+    }}
+    tr:last-child td:first-child {{
+      border-bottom-left-radius: {border_radius};
+    }}
+    tr:last-child td:last-child {{
+      border-bottom-right-radius: {border_radius};
+    }}
+    tbody:nth-child(4n+1) tr td {{
+      background-color: #ffd96e;
+    }}
+    tbody:nth-child(4n+3) tr td {{
+      background-color: #bce;
+    }}
+    hr {{
+      border: 0;
+      height: 1px;
+      color: #ccc;
+      background-color: #ccc;
+    }}
+  </style>
+</head>
+<body>
+<h1>{title}</h1>
+<p>
+{blurb}
+</p>
+<h2>Views</h2>
+''')
+
+footer_template = config['meta'].get('footer', '''
+<hr>
+<p>Generated: {datetime}</p>
+</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),
+    message_url_template=config['meta'].get(
+        'message-url', 'https://mid.gmane.org/{message-id}'),
+    )
+
+if args.list_views:
+    for view in config['views']:
+        print(view['title'])
+    sys.exit(0)
+elif args.get_query != None:
+    for view in config['views']:
+        if args.get_query == view['title']:
+            print(' and '.join('( {} )'.format(q) for q in view['query']))
+    sys.exit(0)
+else:
+    # only import notmuch if needed
+    import notmuch
+
+if args.text:
+    page = _PAGES['text']
+else:
+    page = _PAGES['html']
+
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+page.write(database=db, views=config['views'])
diff --git a/devel/nmbug/notmuch-report.json b/devel/nmbug/notmuch-report.json
new file mode 100644 (file)
index 0000000..c5b35b1
--- /dev/null
@@ -0,0 +1,70 @@
+{
+    "meta": {
+        "title": "Notmuch Patches",
+        "blurb": "For more information see <a href=\"https://notmuchmail.org/nmbug\">nmbug</a>"
+    },
+
+    "views": [
+       {
+           "comment": "Unresolved bugs (or just need tag updating).",
+           "query": [
+               "tag:notmuch::bug",
+               "not tag:notmuch::fixed",
+               "not tag:notmuch::wontfix"
+           ],
+           "title": "Bugs"
+       },
+       {
+           "comment": "These patches are under consideration for pushing.",
+           "query": [
+               "tag:notmuch::patch and not tag:notmuch::pushed",
+               "not tag:notmuch::obsolete and not tag:notmuch::wip",
+               "not tag:notmuch::stale and not tag:notmuch::contrib",
+               "not tag:notmuch::moreinfo",
+               "not tag:notmuch::python",
+               "not tag:notmuch::vim",
+               "not tag:notmuch::wontfix",
+               "not tag:notmuch::needs-review"
+           ],
+           "title": "Maybe Ready (Core and Emacs)"
+       },
+       {
+           "comment": "These python related patches might be ready to push, or they might just need updated tags.",
+           "query": [
+               "tag:notmuch::patch and not tag:notmuch::pushed",
+               "not tag:notmuch::obsolete and not tag:notmuch::wip",
+               "not tag:notmuch::stale and not tag:notmuch::contrib",
+               "not tag:notmuch::moreinfo",
+               "not tag:notmuch::wontfix",
+               " tag:notmuch::python",
+               "not tag:notmuch::needs-review"
+           ],
+           "title": "Maybe Ready (Python)"
+       },
+       {
+           "comment": "These vim related patches might be ready to push, or they might just need updated tags.",
+           "query": [
+               "tag:notmuch::patch and not tag:notmuch::pushed",
+               "not tag:notmuch::obsolete and not tag:notmuch::wip",
+               "not tag:notmuch::stale and not tag:notmuch::contrib",
+               "not tag:notmuch::moreinfo",
+               "not tag:notmuch::wontfix",
+               "tag:notmuch::vim",
+               "not tag:notmuch::needs-review"
+           ],
+           "title": "Maybe Ready (vim)"
+       },
+       {
+           "comment": "These patches are under review, or waiting for feedback.",
+           "query": [
+               "tag:notmuch::patch",
+               "not tag:notmuch::pushed",
+               "not tag:notmuch::obsolete",
+               "not tag:notmuch::stale",
+               "not tag:notmuch::wontfix",
+               "tag:notmuch::moreinfo or tag:notmuch::needs-review"
+           ],
+           "title": "Review"
+       }
+    ]
+}
diff --git a/devel/printmimestructure b/devel/printmimestructure
new file mode 100755 (executable)
index 0000000..70e0a5c
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+# License: GPLv3+
+
+# This script reads a MIME message from stdin and produces a treelike
+# representation on it stdout.
+
+# Example:
+#
+# 0 dkg@alice:~$ printmimestructure < 'Maildir/cur/1269025522.M338697P12023.monkey,S=6459,W=6963:2,Sa'
+# └┬╴multipart/signed 6546 bytes
+#  ├─╴text/plain inline 895 bytes
+#  └─╴application/pgp-signature inline [signature.asc] 836 bytes
+# 0 dkg@alice:~$
+
+
+# If you want to number the parts, i suggest piping the output through
+# something like "cat -n"
+
+from __future__ import print_function
+
+import email
+import sys
+
+def print_part(z, prefix):
+    fname = '' if z.get_filename() is None else ' [' + z.get_filename() + ']'
+    cset = '' if z.get_charset() is None else ' (' + z.get_charset() + ')'
+    disp = z.get_params(None, header='Content-Disposition')
+    if (disp is None):
+        disposition = ''
+    else:
+        disposition = ''
+        for d in disp:
+            if d[0] in [ 'attachment', 'inline' ]:
+                disposition = ' ' + d[0]
+    if z.is_multipart():
+        nbytes = len(z.as_string())
+    else:
+        nbytes = len(z.get_payload())
+
+    print('{}{}{}{}{} {:d} bytes'.format(
+        prefix,
+        z.get_content_type(),
+        cset,
+        disposition,
+        fname,
+        nbytes,
+    ))
+
+def test(z, prefix=''):
+    if (z.is_multipart()):
+        print_part(z, prefix+'┬╴')
+        if prefix.endswith('└'):
+            prefix = prefix.rpartition('└')[0] + ' '
+        if prefix.endswith('├'):
+            prefix = prefix.rpartition('├')[0] + '│'
+        parts = z.get_payload()
+        i = 0
+        while (i < parts.__len__()-1):
+            test(parts[i], prefix + '├')
+            i += 1
+        test(parts[i], prefix + '└')
+        # FIXME: show epilogue?
+    else:
+        print_part(z, prefix+'─╴')
+
+test(email.message_from_file(sys.stdin), '└')
diff --git a/devel/release-checks.sh b/devel/release-checks.sh
new file mode 100755 (executable)
index 0000000..7ba9482
--- /dev/null
@@ -0,0 +1,211 @@
+#!/usr/bin/env bash
+
+set -eu
+#set -x # or enter bash -x ... on command line
+
+if [ x"${BASH_VERSION-}" = x ]
+then   echo
+       echo "Please execute this script using 'bash' interpreter"
+       echo
+       exit 1
+fi
+
+set -o posix
+set -o pipefail # bash feature
+
+readonly DEFAULT_IFS="$IFS" # Note: In this particular case quotes are needed.
+
+# Avoid locale-specific differences in output of executed commands
+LANG=C LC_ALL=C; export LANG LC_ALL
+
+readonly PV_FILE='bindings/python/notmuch/version.py'
+
+# Using array here turned out to be unnecessarily complicated
+emsgs=''
+emsg_count=0
+append_emsg ()
+{
+       emsg_count=$((emsg_count + 1))
+       emsgs="${emsgs:+$emsgs\n}  $1"
+}
+
+for f in ./version debian/changelog NEWS "$PV_FILE"
+do
+       if   [ ! -f "$f" ]; then append_emsg "File '$f' is missing"
+       elif [ ! -r "$f" ]; then append_emsg "File '$f' is unreadable"
+       elif [ ! -s "$f" ]; then append_emsg "File '$f' is empty"
+       fi
+done
+
+if [ -n "$emsgs" ]
+then
+       echo 'Release files problems; fix these and try again:'
+       echo -e "$emsgs"
+       exit 1
+fi
+
+if read VERSION
+then
+       if read rest
+       then    echo "'version' file contains more than one line"
+               exit 1
+       fi
+else
+       echo "Reading './version' file failed (surprisingly!)"
+       exit 1
+fi < ./version
+
+readonly VERSION
+
+# In the rest of this file, tests collect list of errors to be fixed
+
+echo -n "Checking that git working directory is clean... "
+git_status=`git status --porcelain`
+if [ "$git_status" = '' ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "Git working directory is not clean (git status --porcelain)."
+fi
+unset git_status
+
+verfail ()
+{
+       echo No.
+       append_emsg "$@"
+       append_emsg "  Please follow the instructions in RELEASING to choose a version"
+}
+
+echo -n "Checking that '$VERSION' is good with digits and periods... "
+case $VERSION in
+       *[!0-9.]*)
+               verfail "'$VERSION' contains other characters than digits and periods" ;;
+       .*)     verfail "'$VERSION' begins with a period" ;;
+       *.)     verfail "'$VERSION' ends with a period" ;;
+       *..*)   verfail "'$VERSION' contains two consecutive periods" ;;
+       *.*)    echo Yes. ;;
+       *)      verfail "'$VERSION' is a single number" ;;
+esac
+
+echo -n "Checking that this is Debian package for notmuch... "
+read deb_notmuch deb_version rest < debian/changelog
+if [ "$deb_notmuch" = 'notmuch' ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "Package name '$deb_notmuch' is not 'notmuch' in debian/changelog"
+fi
+
+echo -n "Checking that Debian package version is $VERSION-1... "
+
+if [ "$deb_version" = "($VERSION-1)" ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "Version '$deb_version' is not '($VERSION-1)' in debian/changelog"
+fi
+
+echo -n "Checking that python bindings version is $VERSION... "
+py_version=`python -c "with open('$PV_FILE') as vf: exec(vf.read()); print(__VERSION__)"`
+if [ "$py_version" = "$VERSION" ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "Version '$py_version' is not '$VERSION' in $PV_FILE"
+fi
+
+echo -n "Checking that NEWS header is tidy... "
+if [ "`exec sed 's/./=/g; 1q' NEWS`" = "`exec sed '1d; 2q' NEWS`" ]
+then
+       echo Yes.
+else
+       echo No.
+       if [ "`exec sed '1d; s/=//g; 2q' NEWS`" != '' ]
+       then
+               append_emsg "Line 2 in NEWS file is not all '=':s"
+       else
+               append_emsg "Line 2 in NEWS file does not have the same length as line 1"
+       fi
+fi
+
+echo -n "Checking that this is Notmuch NEWS... "
+read news_notmuch news_version news_date < NEWS
+if [ "$news_notmuch" = "Notmuch" ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "First word '$news_notmuch' is not 'Notmuch' in NEWS file"
+fi
+
+echo -n "Checking that NEWS version is $VERSION... "
+if [ "$news_version" = "$VERSION" ]
+then
+       echo Yes.
+else
+       echo No.
+       append_emsg "Version '$news_version' in NEWS file is not '$VERSION'"
+fi
+
+#eval `date '+year=%Y mon=%m day=%d'`
+today0utc=`date --date=0Z +%s` # gnu date feature
+
+echo -n "Checking that NEWS date is right... "
+case $news_date in
+ '('[2-9][0-9][0-9][0-9]-[01][0-9]-[0123][0-9]')')
+       newsdate0utc=`nd=${news_date#\\(}; date --date="${nd%)} 0Z" +%s`
+       ddiff=$((newsdate0utc - today0utc))
+       if [ $ddiff -lt -86400 ] # since beginning of yesterday...
+       then
+               echo No.
+               append_emsg "Date $news_date in NEWS file is too much in the past"
+       elif [ $ddiff -gt 172800 ] # up to end of tomorrow...
+       then
+               echo No.
+               append_emsg "Date $news_date in NEWS file is too much in the future"
+       else
+               echo Yes.
+       fi ;;
+ *)
+       echo No.
+       append_emsg "Date '$news_date' in NEWS file is not in format (yyyy-mm-dd)"
+esac
+
+year=`exec date +%Y`
+echo -n "Checking that copyright in documentation contains 2009-$year... "
+# Read the value of variable `copyright' defined in 'doc/conf.py'.
+# As __file__ is not defined when python command is given from command line,
+# it is defined before contents of 'doc/conf.py' (which dereferences __file__)
+# is executed.
+copyrightline=`exec python -c "with open('doc/conf.py') as cf: __file__ = ''; exec(cf.read()); print(copyright)"`
+case $copyrightline in
+       *2009-$year*)
+               echo Yes. ;;
+       *)
+               echo No.
+               append_emsg "The copyright in doc/conf.py line '$copyrightline' does not contain '2009-$year'"
+esac
+
+if [ -n "$emsgs" ]
+then
+       echo
+       echo 'Release check failed; check these issues:'
+       echo -e "$emsgs"
+       exit 1
+fi
+
+echo 'All checks this script executed completed successfully.'
+echo 'Make sure that everything else mentioned in RELEASING'
+echo 'file is in order, too.'
+
+
+# Local variables:
+# mode: shell-script
+# sh-basic-offset: 8
+# tab-width: 8
+# End:
+# vi: set sw=8 ts=8
diff --git a/devel/schemata b/devel/schemata
new file mode 100644 (file)
index 0000000..42b1bcf
--- /dev/null
@@ -0,0 +1,210 @@
+This file describes the schemata used for notmuch's structured output
+format (currently JSON and S-Expressions).
+
+[]'s indicate lists.  List items can be marked with a '?', meaning
+they are optional; or a '*', meaning there can be zero or more of that
+item.  {}'s indicate an object that maps from field identifiers to
+values.  An object field marked '?' is optional.  |'s indicate
+alternates (e.g., int|string means something can be an int or a
+string).
+
+For S-Expression output, lists are printed delimited by () instead of
+[]. Objects are printed as p-lists, i.e. lists where the keys and values
+are interleaved. Keys are printed as keywords (symbols preceded by a
+colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
+nil, true as t and false as nil.
+
+This is version 4 of the structured output format.
+
+Version history
+---------------
+
+v1
+- First versioned schema release.
+- Added part.content-length and part.content-transfer-encoding fields.
+
+v2
+- Added the thread_summary.query field.
+
+v3
+- Replaced message.filename string with a list of filenames.
+- Added part.content-disposition field.
+
+v4
+- replace signature error integer bitmask with a set of flags for
+  individual errors.
+
+Common non-terminals
+--------------------
+
+# Number of seconds since the Epoch
+unix_time = int
+
+# Thread ID, sans "thread:"
+threadid = string
+
+# Message ID, sans "id:"
+messageid = string
+
+notmuch show schema
+-------------------
+
+# A top-level set of threads (do_show)
+# Returned by notmuch show without a --part argument
+thread_set = [thread*]
+
+# Top-level messages in a thread (show_messages)
+thread = [thread_node*]
+
+# A message and its replies (show_messages)
+thread_node = [
+    message|null,             # null if not matched and not --entire-thread
+    [thread_node*]            # children of message
+]
+
+# A message (format_part_sprinter)
+message = {
+    # (format_message_sprinter)
+    id:             messageid,
+    match:          bool,
+    filename:      [string*],
+    timestamp:      unix_time, # date header as unix time
+    date_relative:  string,   # user-friendly timestamp
+    tags:           [string*],
+
+    headers:        headers,
+    body?:          [part]    # omitted if --body=false
+}
+
+# A MIME part (format_part_sprinter)
+part = {
+    id:             int|string, # part id (currently DFS part number)
+
+    encstatus?:     encstatus,
+    sigstatus?:     sigstatus,
+
+    content-type:   string,
+    content-disposition?:       string,
+    content-id?:    string,
+    # if content-type starts with "multipart/":
+    content:        [part*],
+    # if content-type is "message/rfc822":
+    content:        [{headers: headers, body: [part]}],
+    # otherwise (leaf parts):
+    filename?:      string,
+    content-charset?: string,
+    # A leaf part's body content is optional, but may be included if
+    # it can be correctly encoded as a string.  Consumers should use
+    # this in preference to fetching the part content separately.
+    content?:       string,
+    # If a leaf part's body content is not included, the length of
+    # the encoded content (in bytes) may be given instead.
+    content-length?: int,
+    # If a leaf part's body content is not included, its transfer encoding
+    # may be given.  Using this and the encoded content length, it is
+    # possible for the consumer to estimate the decoded content length.
+    content-transfer-encoding?: string
+}
+
+# The headers of a message or part (format_headers_sprinter with reply = FALSE)
+headers = {
+    Subject:        string,
+    From:           string,
+    To?:            string,
+    Cc?:            string,
+    Bcc?:           string,
+    Reply-To?:      string,
+    Date:           string
+}
+
+# Encryption status (format_part_sprinter)
+encstatus = [{status: "good"|"bad"}]
+
+# Signature status (format_part_sigstatus_sprinter)
+sigstatus = [signature*]
+
+signature = {
+    # (signature_status_to_string)
+    status:         "good"|"bad"|"error"|"unknown",
+    # if status is "good":
+    fingerprint?:   string,
+    created?:       unix_time,
+    expires?:       unix_time,
+    userid?:        string
+    # if status is not "good":
+    keyid?:         string
+    errors?:       sig_errors
+}
+
+sig_errors = {
+    key-revoked?: bool,
+    key-expired?: bool,
+    sig-expired?: bool,
+    key-missing?: bool,
+    alg-unsupported?: bool,
+    crl-missing?: bool,
+    crl-too-old?: bool,
+    bad-policy?: bool,
+    sys-error?: bool,
+    tofu-conflict?: bool
+}
+
+notmuch search schema
+---------------------
+
+# --output=summary
+search_summary = [thread_summary*]
+
+# --output=threads
+search_threads = [threadid*]
+
+# --output=messages
+search_messages = [messageid*]
+
+# --output=files
+search_files = [string*]
+
+# --output=tags
+search_tags = [string*]
+
+thread_summary = {
+    thread:         threadid,
+    timestamp:      unix_time,
+    date_relative:  string,   # user-friendly timestamp
+    matched:        int,      # number of matched messages
+    total:          int,      # total messages in thread
+    authors:        string,   # comma-separated names with | between
+                              # matched and unmatched
+    subject:        string,
+    tags:           [string*],
+
+    # Two stable query strings identifying exactly the matched and
+    # unmatched messages currently in this thread.  The messages
+    # matched by these queries will not change even if more messages
+    # arrive in the thread.  If there are no matched or unmatched
+    # messages, the corresponding query will be null (there is no
+    # query that matches nothing).  (Added in schema version 2.)
+    query:          [string|null, string|null],
+}
+
+notmuch reply schema
+--------------------
+
+reply = {
+    # The headers of the constructed reply
+    reply-headers: reply_headers,
+
+    # As in the show format (format_part_sprinter)
+    original: message
+}
+
+# Reply headers (format_headers_sprinter with reply = TRUE)
+reply_headers = {
+    Subject:        string,
+    From:           string,
+    To?:            string,
+    Cc?:            string,
+    Bcc?:           string,
+    In-reply-to:    string,
+    References:     string
+}
diff --git a/devel/try-emacs-mua b/devel/try-emacs-mua
new file mode 100755 (executable)
index 0000000..041f621
--- /dev/null
@@ -0,0 +1,157 @@
+#!/bin/sh
+:; set -x; exec "${EMACS:-emacs}" --debug-init --load "$0" "$@"; exit
+;;
+;; Try the notmuch emacs client located in ../emacs/ directory
+;;
+;; Run this without arguments; emacs window opens with some usage information
+;;
+;; Authors: Tomi Ollila <tomi.ollila@iki.fi>
+;;
+;; https://www.emacswiki.org/emacs/EmacsScripts was a useful starting point...
+;;
+;; Licence: GPLv3+
+;;
+
+(message "Starting '%s'" load-file-name)
+
+(set-buffer "*scratch*")
+
+(setq initial-buffer-choice nil
+      inhibit-startup-screen t)
+
+(when (featurep 'notmuch)
+  (insert "
+Notmuch has been loaded to this emacs (during processing of the init file)
+which means it is (most probably) loaded from different source than expected.
+
+Please run \"" (file-name-nondirectory load-file-name)
+"\" with '-q' (or '-Q') as an argument, to disable
+processing of the init file -- you can load it after emacs has started\n
+exit emacs (y or n)? ")
+  (if (y-or-n-p "exit emacs")
+      (kill-emacs)
+    (error "Stopped reading %s" load-file-name)))
+
+(let ((pdir (file-name-directory
+            (directory-file-name (file-name-directory load-file-name)))))
+  (unless (file-exists-p (concat pdir "emacs/notmuch-lib.el"))
+    (insert "Cannot find notmuch-emacs source directory
+while looking at: " pdir "emacs\n\nexit emacs (y or n)? ")
+    (if (y-or-n-p "exit emacs")
+       (kill-emacs)
+      (error "Stopped reading %s" load-file-name)))
+  (setq try-notmuch-source-directory (directory-file-name pdir)
+       try-notmuch-emacs-directory (concat pdir "emacs/")
+       load-path (cons try-notmuch-emacs-directory load-path)))
+
+;; they say advice doesn't work for primitives (functions from c source)
+;; well, these 'before' advice works for emacs 23.1 - 24.5 (at least)
+;; ...and for our purposes 24.3 is enough (there is no load-prefer-newer there)
+;; note also that the old, "obsolete" defadvice mechanism was used, but that
+;; is the only one available for emacs 23 and 24 up to 24.3.
+
+(if (boundp 'load-prefer-newer)
+    (defadvice require (before before-require activate)
+      (unless (featurep feature)
+       (message "require: %s" feature)))
+  ;; else: special require "short-circuit"; after load feature is provided...
+  ;; ... in notmuch sources we always use require and there are no loops
+  (defadvice require (before before-require activate)
+    (unless (featurep feature)
+      (message "require: %s" feature)
+      (let ((name (symbol-name feature)))
+       (if (and (string-match "^notmuch" name)
+                (file-newer-than-file-p
+                 (concat try-notmuch-emacs-directory name ".el")
+                 (concat try-notmuch-emacs-directory name ".elc")))
+           (load (concat try-notmuch-emacs-directory name ".el") nil nil t t)
+         )))))
+
+(insert "Found notmuch emacs client in " try-notmuch-emacs-directory "\n")
+
+(let ((notmuch-path (executable-find "notmuch")))
+  (insert "Notmuch CLI executable "
+         (if notmuch-path (concat "is " notmuch-path) "not found!") "\n"))
+
+(condition-case err
+;; "opportunistic" load-prefer-newer -- will be effective since emacs 24.4
+    (let ((load-prefer-newer t)
+         (force-load-messages t))
+      (require 'notmuch))
+  ;; specifying `debug' here lets the debugger run
+  ;; if `debug-on-error' is non-nil.
+  ((debug error)
+   (let ((error-message-string (error-message-string err)))
+     (insert "\nLoading notmuch failed: " error-message-string "\n")
+     (message "Loading notmuch failed: %s" error-message-string)
+     (insert "See *Messages* buffer for more information.\n")
+     (if init-file-user
+        (message "Hint: %s -q (or -Q) may help" load-file-name))
+     (pop-to-buffer "*Messages*")
+     (error "Stopped reading %s" load-file-name))))
+
+(insert "
+Go to the end of the following lines and type C-x C-e to evaluate
+(or C-j which is shorter but inserts evaluation results into buffer)
+
+To \"disable\" mail sending, evaluate
+* (setq message-send-mail-function (lambda () t))
+")
+
+(if (file-exists-p (concat try-notmuch-source-directory "/notmuch"))
+    (insert "
+To use accompanied notmuch binary from the same source, evaluate
+* (setq exec-path (cons \"" try-notmuch-source-directory  "\" exec-path))
+Note: Evaluating the above may be followed by unintended database
+upgrade and getting back to old version may require dump & restore.
+"))
+
+(if init-file-user ;; nil, if '-q' or '-Q' is given, but no '-u' 'USER'
+    (insert "
+Your init file was processed during emacs startup. If you want to test
+notmuch emacs mail client without your emacs init file interfering, Run\n\""
+(file-name-nondirectory load-file-name) "\" with '-q' (or '-Q') as an argument.
+")
+  (let ((emacs-init-file-name) (notmuch-init-file-name))
+    ;; determining init file name in startup.el/command-line is too complicated
+    ;; to be duplicated here; these 3 file names covers most of the users
+    (mapc (lambda (fn) (if (file-exists-p fn) (setq emacs-init-file-name fn)))
+         '("~/.emacs.d/init.el" "~/.emacs" "~/.emacs.el"))
+    (setq notmuch-init-file-name "~/.emacs.d/notmuch-config.el")
+    (unless (file-exists-p notmuch-init-file-name)
+       (setq notmuch-init-file-name nil))
+    (if (and emacs-init-file-name notmuch-init-file-name)
+       (insert "
+If you want to load your initialization files now, evaluate\n* (progn")
+      (if (or emacs-init-file-name notmuch-init-file-name)
+         (insert "
+If you want to load your initialization file now, evaluate\n*")))
+    (if emacs-init-file-name
+       (insert " (load \"" emacs-init-file-name "\")"))
+    (if notmuch-init-file-name
+       (insert " (load \"" notmuch-init-file-name "\")"))
+    (if (and emacs-init-file-name notmuch-init-file-name)
+       (insert ")"))
+    (if (or emacs-init-file-name notmuch-init-file-name)
+       (insert "\n")))
+  (if (>= emacs-major-version 24)
+      (insert "
+If you want to use packages (e.g. company from elpa) evaluate
+* (progn (require 'package) (package-initialize))
+")))
+
+(insert "
+To start notmuch (hello) screen, evaluate
+* (notmuch-hello)")
+
+(add-hook 'emacs-startup-hook
+         (lambda ()
+           (with-current-buffer "*scratch*"
+             (lisp-interaction-mode)
+             (goto-char (point-min))
+             (forward-line 2)
+             (set-buffer-modified-p nil))))
+
+;; Local Variables:
+;; mode: emacs-lisp
+;; End:
diff --git a/devel/uncrustify.cfg b/devel/uncrustify.cfg
new file mode 100644 (file)
index 0000000..6a8769c
--- /dev/null
@@ -0,0 +1,119 @@
+#
+# Uncrustify config file for notmuch.
+# Based on uncrustify config file for the linux kernel
+#
+# $Id: linux-indent.cfg 488 2006-09-09 12:44:38Z bengardner $
+# Taken from the uncrustify distribution under license (GPL2+)
+#
+# Sample usage:
+#        uncrustify --replace -c uncrustify.cfg foo.c
+#
+
+indent_with_tabs       = 2             # 1=indent to level only, 2=indent with tabs
+align_with_tabs                = TRUE          # use tabs to align
+align_on_tabstop       = TRUE          # align on tabstops
+input_tab_size         = 8             # original tab size
+output_tab_size                = 8             # new tab size
+indent_columns         = 4
+
+indent_label           = -2            # pos: absolute col, neg: relative column
+
+indent_cmt_with_tabs   = false         # true would align to tabstop always...
+
+#
+# inter-symbol newlines
+#
+
+nl_enum_brace          = remove        # "enum {" vs "enum \n {"
+nl_union_brace         = remove        # "union {" vs "union \n {"
+nl_struct_brace                = remove        # "struct {" vs "struct \n {"
+nl_do_brace             = remove       # "do {" vs "do \n {"
+nl_if_brace             = remove       # "if () {" vs "if () \n {"
+nl_for_brace            = remove       # "for () {" vs "for () \n {"
+nl_else_brace           = remove       # "else {" vs "else \n {"
+nl_while_brace          = remove       # "while () {" vs "while () \n {"
+nl_switch_brace         = remove       # "switch () {" vs "switch () \n {"
+nl_brace_while         = remove        # "} while" vs "} \n while" - cuddle while
+nl_brace_else          = remove        # "} else" vs "} \n else" - cuddle else
+nl_func_var_def_blk    = 1
+nl_fcall_brace         = remove        # "list_for_each() {" vs "list_for_each()\n{"
+nl_fdef_brace          = force         # "int foo() {" vs "int foo()\n{"
+# nl_after_return              = TRUE;
+# nl_before_case       = 1
+
+# Add or remove newline between return type and function name in definition
+nl_func_type_name      = force
+nl_enum_leave_one_liners = True
+nl_enum_brace = Remove
+nl_after_struct = 0
+#
+# Source code modifications
+#
+
+# mod_paren_on_return  = remove        # "return 1;" vs "return (1);"
+# mod_full_brace_if    = remove        # "if (a) a--;" vs "if (a) { a--; }"
+# mod_full_brace_for   = remove        # "for () a--;" vs "for () { a--; }"
+# mod_full_brace_do    = remove        # "do a--; while ();" vs "do { a--; } while ();"
+# mod_full_brace_while = remove        # "while (a) a--;" vs "while (a) { a--; }"
+
+
+# In case some custom types aren't detected properly by uncrustify
+# add those to this section below. For example there are cases where
+# uncrustify doesn't know whether a 'token' is part of pointer type
+# or left operand of a binary multiplication operation.
+
+type FILE
+type GMimeObject GMimeCryptoContext GMimeCipherContext
+type mime_node_t notmuch_message_t notmuch_show_params_t
+type sprinter_t
+
+#
+# inter-character spacing options
+#
+
+sp_before_ptr_star     = force
+sp_between_ptr_star    = remove
+sp_after_ptr_star      = remove
+sp_not                 = force
+sp_pp_concat           = ignore        # XXX 'remove' drops leading space also
+sp_pp_stringify                = remove
+
+# sp _return_paren     = force         # "return (1);" vs "return(1);"
+sp_sizeof_paren                = force         # "sizeof (int)" vs "sizeof(int)"
+sp_before_sparen       = force         # "if (" vs "if("
+sp_after_sparen                = force         # "if () {" vs "if (){"
+sp_sparen_brace                = force
+sp_after_cast          = force         # "(int) a" vs "(int)a"
+sp_inside_braces       = add           # "{ 1 }" vs "{1}"
+sp_inside_braces_struct        = add           # "{ 1 }" vs "{1}"
+sp_inside_braces_enum  = add           # "{ 1 }" vs "{1}"
+sp_assign              = force
+sp_arith               = force
+sp_bool                        = add
+sp_compare             = add
+sp_assign              = add
+sp_after_comma         = add
+sp_func_def_paren      = force         # "int foo (){" vs "int foo(){"
+sp_func_call_paren     = force         # "foo (" vs "foo("
+sp_func_proto_paren    = force         # "int foo ();" vs "int foo();"
+sp_brace_else          = force         # "} else" vs "}else"
+sp_else_brace          = force         # "else {" vs "else{"
+#
+# Aligning stuff
+#
+
+align_enum_equ_span    = 4             # '=' in enum definition
+# align_nl_cont                = TRUE
+# align_var_def_span   = 2
+# align_var_def_inline = TRUE
+# align_var_def_star   = FALSE
+# align_var_def_colon  = TRUE
+# align_assign_span    = 1
+align_struct_init_span = 0             # align stuff in a structure init '= { }'
+align_right_cmt_span   = 8             # align comments span this much in func
+# align_pp_define_span = 8;
+# align_pp_define_gap  = 4;
+
+cmt_star_cont          = true
+
+# indent_brace         = 0
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..bbb749f
--- /dev/null
@@ -0,0 +1,3 @@
+*.pyc
+/_build
+/config.dox
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644 (file)
index 0000000..0585476
--- /dev/null
@@ -0,0 +1,11 @@
+This file contains some more detailed information about building and
+installing the documentation.
+
+- You need sphinx at least version 1.0.
+
+- You can build build and install man pages with 'make install-man'
+
+- You can build man, info, html, and pdf versions of the docs
+  (currently only the man pages) with
+
+     'make install-{man|info|html|pdf}'
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644 (file)
index 0000000..fa25832
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/doc/Makefile.local b/doc/Makefile.local
new file mode 100644 (file)
index 0000000..16459e3
--- /dev/null
@@ -0,0 +1,129 @@
+# -*- makefile -*-
+
+dir := doc
+
+# You can set these variables from the command line.
+SPHINXOPTS    := -q
+SPHINXBUILD   = sphinx-build
+DOCBUILDDIR      := $(dir)/_build
+
+# 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})
+
+MAN1_TEXI := $(patsubst $(srcdir)/doc/man1/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN1_RST))
+MAN5_TEXI := $(patsubst $(srcdir)/doc/man5/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN5_RST))
+MAN7_TEXI := $(patsubst $(srcdir)/doc/man7/%.rst,$(DOCBUILDDIR)/texinfo/%.texi,$(MAN7_RST))
+INFO_TEXI_FILES := $(MAN1_TEXI) $(MAN5_TEXI) $(MAN7_TEXI) $(DOCBUILDDIR)/texinfo/notmuch-emacs.texi
+INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
+
+.PHONY: sphinx-html sphinx-texinfo sphinx-info
+
+.PHONY: install-man build-man apidocs install-apidocs
+
+%.gz: %
+       rm -f $@ && gzip --stdout $^ > $@
+
+sphinx-html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
+
+sphinx-texinfo:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
+
+sphinx-info: sphinx-texinfo
+       make -C $(DOCBUILDDIR)/texinfo info
+
+# 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
+
+# By using $(DOCBUILDDIR)/.roff.stamp instead of ${MAN_ROFF_FILES}, we
+# convey to make that a single invocation of this recipe builds all
+# of the roff files.  This prevents parallel make from starting an
+# instance of this recipe for each roff file.
+$(DOCBUILDDIR)/.roff.stamp: ${MAN_RST_FILES}
+ifeq ($(HAVE_SPHINX),1)
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(DOCBUILDDIR)/man
+       for section in 1 5 7; do \
+           mkdir -p $(DOCBUILDDIR)/man/man$${section}; \
+           mv $(DOCBUILDDIR)/man/*.$${section} $(DOCBUILDDIR)/man/man$${section}; \
+       done
+else
+       @echo "Fatal: build dependency fail."
+       @false
+endif
+       touch $@
+
+install-man: install-apidocs
+
+ifeq ($(HAVE_DOXYGEN),1)
+MAN_GZIP_FILES += ${APIMAN}.gz
+apidocs: $(APIMAN)
+install-apidocs: ${APIMAN}.gz
+       mkdir -p "$(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
+       doxygen $(DOXYFILE)
+       rm -f $(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),0)
+build-man:
+install-man:
+       @echo "No sphinx, will not install man pages."
+else
+build-man: ${MAN_GZIP_FILES}
+install-man: ${MAN_GZIP_FILES}
+       mkdir -m0755 -p "$(DESTDIR)$(mandir)/man1"
+       mkdir -m0755 -p "$(DESTDIR)$(mandir)/man5"
+       mkdir -m0755 -p "$(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
+
+ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO),11)
+build-info:
+       @echo "Missing sphinx or makeinfo, not building info pages"
+else
+build-info: sphinx-info
+endif
+
+ifneq ($(HAVE_SPHINX)$(HAVE_MAKEINFO)$(HAVE_INSTALL_INFO),111)
+install-info:
+       @echo "Missing prerequisites, not installing info pages"
+else
+install-info: build-info
+       mkdir -m0755 -p "$(DESTDIR)$(infodir)"
+       install -m0644 $(INFO_INFO_FILES) $(DESTDIR)$(infodir)
+       for file in $(INFO_INFO_FILES); do install-info $$file $(DESTDIR)$(infodir)/dir; done
+endif
+
+$(dir)/config.dox: version.stamp
+       echo "PROJECT_NAME = \"Notmuch $(VERSION)\"" > $@
+       echo "INPUT=${srcdir}/lib/notmuch.h" >> $@
+
+CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp
+CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644 (file)
index 0000000..0ef7232
--- /dev/null
@@ -0,0 +1,162 @@
+
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'notmuch'
+copyright = u'2009-2019, Carl Worth and many others'
+
+location = os.path.dirname(__file__)
+
+for pathdir in ['.', '..']:
+    version_file = os.path.join(location,pathdir,'version')
+    if os.path.exists(version_file):
+        with open(version_file,'r') as infile:
+            version=infile.read().replace('\n','')
+
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'notmuchdoc'
+
+# Disable SmartyPants, as it mangles command lines.
+# Despite the name, this actually affects manual pages as well.
+html_use_smartypants = False
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+
+notmuch_authors = u'Carl Worth and many others'
+
+man_pages = [
+    ('man1/notmuch', 'notmuch',
+     u'thread-based email index, search, and tagging',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-address', 'notmuch-address',
+     u'output addresses from matching messages',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-compact', 'notmuch-compact',
+     u'compact the notmuch database',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-config', 'notmuch-config',
+     u'access notmuch configuration file',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-count', 'notmuch-count',
+     u'count messages matching the given search terms',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-dump', 'notmuch-dump',
+     u'creates a plain-text dump of the tags of each message',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-emacs-mua', 'notmuch-emacs-mua',
+     u'send mail with notmuch and emacs',
+     [notmuch_authors], 1),
+
+    ('man5/notmuch-hooks', 'notmuch-hooks',
+     u'hooks for notmuch',
+     [notmuch_authors], 5),
+
+    ('man1/notmuch-insert', 'notmuch-insert',
+     u'add a message to the maildir and notmuch database',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-new', 'notmuch-new',
+     u'incorporate new mail into the notmuch database',
+     [notmuch_authors], 1),
+
+    ('man7/notmuch-properties', 'notmuch-properties',
+     u'notmuch message property conventions and documentation',
+     [notmuch_authors], 7),
+
+    ('man1/notmuch-reindex', 'notmuch-reindex',
+     u're-index matching messages',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-reply', 'notmuch-reply',
+     u'constructs a reply template for a set of messages',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-restore', 'notmuch-restore',
+     u'restores the tags from the given file (see notmuch dump)',
+     [notmuch_authors], 1),
+
+    ('man1/notmuch-search', 'notmuch-search',
+     u'search for messages matching the given search terms',
+     [notmuch_authors], 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
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+texinfo_no_detailmenu = True
+
+texinfo_documents = [
+    ('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]
diff --git a/doc/doxygen.cfg b/doc/doxygen.cfg
new file mode 100644 (file)
index 0000000..2ca15d4
--- /dev/null
@@ -0,0 +1,301 @@
+# Doxyfile 1.8.4
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+DOXYFILE_ENCODING      = UTF-8
+@INCLUDE              =  "doc/config.dox"
+PROJECT_NUMBER         =
+PROJECT_BRIEF          =
+PROJECT_LOGO           =
+OUTPUT_DIRECTORY       = doc/_build
+CREATE_SUBDIRS         = NO
+OUTPUT_LANGUAGE        = English
+BRIEF_MEMBER_DESC      = YES
+REPEAT_BRIEF           = YES
+ABBREVIATE_BRIEF       =
+ALWAYS_DETAILED_SEC    = NO
+INLINE_INHERITED_MEMB  = NO
+FULL_PATH_NAMES        = NO
+STRIP_FROM_PATH        =
+STRIP_FROM_INC_PATH    =
+SHORT_NAMES            = NO
+JAVADOC_AUTOBRIEF      = YES
+QT_AUTOBRIEF           = NO
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS           = YES
+SEPARATE_MEMBER_PAGES  = NO
+TAB_SIZE               = 8
+ALIASES                =
+TCL_SUBST              =
+OPTIMIZE_OUTPUT_FOR_C  = YES
+OPTIMIZE_OUTPUT_JAVA   = NO
+OPTIMIZE_FOR_FORTRAN   = NO
+OPTIMIZE_OUTPUT_VHDL   = NO
+EXTENSION_MAPPING      =
+MARKDOWN_SUPPORT       = YES
+AUTOLINK_SUPPORT       = YES
+BUILTIN_STL_SUPPORT    = NO
+CPP_CLI_SUPPORT        = NO
+SIP_SUPPORT            = NO
+IDL_PROPERTY_SUPPORT   = YES
+DISTRIBUTE_GROUP_DOC   = NO
+SUBGROUPING            = YES
+INLINE_GROUPED_CLASSES = NO
+INLINE_SIMPLE_STRUCTS  = NO
+TYPEDEF_HIDES_STRUCT   = YES
+LOOKUP_CACHE_SIZE      = 0
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL            = NO
+EXTRACT_PRIVATE        = NO
+EXTRACT_PACKAGE        = NO
+EXTRACT_STATIC         = NO
+EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_ANON_NSPACES   = NO
+HIDE_UNDOC_MEMBERS     = NO
+HIDE_UNDOC_CLASSES     = NO
+HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_IN_BODY_DOCS      = NO
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = YES
+HIDE_SCOPE_NAMES       = NO
+SHOW_INCLUDE_FILES     = NO
+FORCE_LOCAL_INCLUDES   = NO
+INLINE_INFO            = YES
+SORT_MEMBER_DOCS       = NO
+SORT_BRIEF_DOCS        = NO
+SORT_MEMBERS_CTORS_1ST = NO
+SORT_GROUP_NAMES       = NO
+SORT_BY_SCOPE_NAME     = NO
+STRICT_PROTO_MATCHING  = NO
+GENERATE_TODOLIST      = NO
+GENERATE_TESTLIST      = NO
+GENERATE_BUGLIST       = NO
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS       =
+MAX_INITIALIZER_LINES  = 30
+SHOW_USED_FILES        = NO
+SHOW_FILES             = NO
+SHOW_NAMESPACES        = NO
+FILE_VERSION_FILTER    =
+LAYOUT_FILE            =
+CITE_BIB_FILES         =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET                  = YES
+WARNINGS               = YES
+WARN_IF_UNDOCUMENTED   = YES
+WARN_IF_DOC_ERROR      = YES
+WARN_NO_PARAMDOC       = NO
+WARN_FORMAT            = "$file:$line: $text"
+WARN_LOGFILE           =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT_ENCODING         = UTF-8
+FILE_PATTERNS          =
+RECURSIVE              = NO
+EXCLUDE                =
+EXCLUDE_SYMLINKS       = NO
+EXCLUDE_PATTERNS       =
+EXCLUDE_SYMBOLS        =
+EXAMPLE_PATH           =
+EXAMPLE_PATTERNS       =
+EXAMPLE_RECURSIVE      = NO
+IMAGE_PATH             =
+INPUT_FILTER           =
+FILTER_PATTERNS        =
+FILTER_SOURCE_FILES    = NO
+FILTER_SOURCE_PATTERNS =
+USE_MDFILE_AS_MAINPAGE =
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER         = NO
+INLINE_SOURCES         = NO
+STRIP_CODE_COMMENTS    = YES
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION    = NO
+REFERENCES_LINK_SOURCE = YES
+USE_HTAGS              = NO
+VERBATIM_HEADERS       = NO
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX     = NO
+COLS_IN_ALPHA_INDEX    = 5
+IGNORE_PREFIX          =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML          = NO
+HTML_OUTPUT            = html
+HTML_FILE_EXTENSION    = .html
+HTML_HEADER            =
+HTML_FOOTER            =
+HTML_STYLESHEET        =
+HTML_EXTRA_STYLESHEET  =
+HTML_EXTRA_FILES       =
+HTML_COLORSTYLE_HUE    = 220
+HTML_COLORSTYLE_SAT    = 100
+HTML_COLORSTYLE_GAMMA  = 80
+HTML_TIMESTAMP         = YES
+HTML_DYNAMIC_SECTIONS  = NO
+HTML_INDEX_NUM_ENTRIES = 100
+GENERATE_DOCSET        = NO
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+DOCSET_PUBLISHER_NAME  = Publisher
+GENERATE_HTMLHELP      = NO
+CHM_FILE               =
+HHC_LOCATION           =
+GENERATE_CHI           = NO
+CHM_INDEX_ENCODING     =
+BINARY_TOC             = NO
+TOC_EXPAND             = NO
+GENERATE_QHP           = NO
+QCH_FILE               =
+QHP_NAMESPACE          = org.doxygen.Project
+QHP_VIRTUAL_FOLDER     = doc
+QHP_CUST_FILTER_NAME   =
+QHP_CUST_FILTER_ATTRS  =
+QHP_SECT_FILTER_ATTRS  =
+QHG_LOCATION           =
+GENERATE_ECLIPSEHELP   = NO
+ECLIPSE_DOC_ID         = org.doxygen.Project
+DISABLE_INDEX          = NO
+GENERATE_TREEVIEW      = NO
+ENUM_VALUES_PER_LINE   = 4
+TREEVIEW_WIDTH         = 250
+EXT_LINKS_IN_WINDOW    = NO
+FORMULA_FONTSIZE       = 10
+FORMULA_TRANSPARENT    = YES
+USE_MATHJAX            = NO
+MATHJAX_FORMAT         = HTML-CSS
+MATHJAX_RELPATH        = https://cdn.mathjax.org/mathjax/latest
+MATHJAX_EXTENSIONS     =
+MATHJAX_CODEFILE       =
+SEARCHENGINE           = YES
+SERVER_BASED_SEARCH    = NO
+EXTERNAL_SEARCH        = NO
+SEARCHENGINE_URL       =
+SEARCHDATA_FILE        = searchdata.xml
+EXTERNAL_SEARCH_ID     =
+EXTRA_SEARCH_MAPPINGS  =
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX         = NO
+LATEX_OUTPUT           = latex
+LATEX_CMD_NAME         = latex
+MAKEINDEX_CMD_NAME     = makeindex
+COMPACT_LATEX          = NO
+PAPER_TYPE             = a4
+EXTRA_PACKAGES         =
+LATEX_HEADER           =
+LATEX_FOOTER           =
+LATEX_EXTRA_FILES      =
+PDF_HYPERLINKS         = YES
+USE_PDFLATEX           = YES
+LATEX_BATCHMODE        = NO
+LATEX_HIDE_INDICES     = NO
+LATEX_SOURCE_CODE      = NO
+LATEX_BIB_STYLE        = plain
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF           = NO
+RTF_OUTPUT             = rtf
+COMPACT_RTF            = NO
+RTF_HYPERLINKS         = NO
+RTF_STYLESHEET_FILE    =
+RTF_EXTENSIONS_FILE    =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN           = YES
+MAN_OUTPUT             = man
+MAN_EXTENSION          = .3
+MAN_LINKS              = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML           = NO
+XML_OUTPUT             = xml
+XML_PROGRAMLISTING     = YES
+#---------------------------------------------------------------------------
+# configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+GENERATE_DOCBOOK       = NO
+DOCBOOK_OUTPUT         = docbook
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF   = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD       = NO
+PERLMOD_LATEX          = NO
+PERLMOD_PRETTY         = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING   = YES
+MACRO_EXPANSION        = NO
+EXPAND_ONLY_PREDEF     = NO
+SEARCH_INCLUDES        = NO
+INCLUDE_PATH           =
+INCLUDE_FILE_PATTERNS  =
+PREDEFINED             = __DOXYGEN__
+EXPAND_AS_DEFINED      =
+SKIP_FUNCTION_MACROS   = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES               =
+GENERATE_TAGFILE       =
+ALLEXTERNALS           = NO
+EXTERNAL_GROUPS        = NO
+EXTERNAL_PAGES         = NO
+PERL_PATH              = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS         = NO
+MSCGEN_PATH            =
+HIDE_UNDOC_RELATIONS   = YES
+HAVE_DOT               = NO
+DOT_NUM_THREADS        = 0
+DOT_FONTNAME           = Helvetica
+DOT_FONTSIZE           = 10
+DOT_FONTPATH           =
+CLASS_GRAPH            = YES
+COLLABORATION_GRAPH    = YES
+GROUP_GRAPHS           = YES
+UML_LOOK               = NO
+UML_LIMIT_NUM_FIELDS   = 10
+TEMPLATE_RELATIONS     = NO
+INCLUDE_GRAPH          = NO
+INCLUDED_BY_GRAPH      = NO
+CALL_GRAPH             = NO
+CALLER_GRAPH           = NO
+GRAPHICAL_HIERARCHY    = NO
+DIRECTORY_GRAPH        = NO
+DOT_IMAGE_FORMAT       = png
+INTERACTIVE_SVG        = NO
+DOT_PATH               =
+DOTFILE_DIRS           =
+MSCFILE_DIRS           =
+DOT_GRAPH_MAX_NODES    = 50
+MAX_DOT_GRAPH_DEPTH    = 0
+DOT_TRANSPARENT        = NO
+DOT_MULTI_TARGETS      = YES
+GENERATE_LEGEND        = NO
+DOT_CLEANUP            = YES
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644 (file)
index 0000000..4440d93
--- /dev/null
@@ -0,0 +1,35 @@
+
+Welcome to notmuch's documentation!
+===================================
+
+Contents:
+
+.. toctree::
+   :titlesonly:
+
+   man1/notmuch
+   man1/notmuch-address
+   man1/notmuch-compact
+   man1/notmuch-config
+   man1/notmuch-count
+   man1/notmuch-dump
+   notmuch-emacs
+   man1/notmuch-emacs-mua
+   man5/notmuch-hooks
+   man1/notmuch-insert
+   man1/notmuch-new
+   man7/notmuch-properties
+   man1/notmuch-reindex
+   man1/notmuch-reply
+   man1/notmuch-restore
+   man1/notmuch-search
+   man7/notmuch-search-terms
+   man1/notmuch-show
+   man1/notmuch-tag
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst
new file mode 100644 (file)
index 0000000..12d86e8
--- /dev/null
@@ -0,0 +1,131 @@
+===============
+notmuch-address
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **address** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Search for messages matching the given search terms, and display the
+addresses from them. Duplicate addresses are filtered out.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+Supported options for **address** include
+
+``--format=``\ (**json**\ \|\ **sexp**\ \|\ **text**\ \|\ **text0**)
+    Presents the results in either JSON, S-Expressions, newline
+    character separated plain-text (default), or null character
+    separated plain-text (compatible with **xargs(1)** -0 option where
+    available).
+
+``--format-version=N``
+    Use the specified structured output format version. This is
+    intended for programs that invoke **notmuch(1)** internally. If
+    omitted, the latest supported version will be used.
+
+``--output=(sender|recipients|count|address)``
+    Controls which information appears in the output. This option can
+    be given multiple times to combine different outputs.  When
+    neither ``--output=sender`` nor ``--output=recipients`` is
+    given, ``--output=sender`` is implied.
+
+    **sender**
+        Output all addresses from the *From* header.
+
+        Note: Searching for **sender** should be much faster than
+        searching for **recipients**, because sender addresses are
+        cached directly in the database whereas other addresses need
+        to be fetched from message files.
+
+    **recipients**
+        Output all addresses from the *To*, *Cc* and *Bcc* headers.
+
+    **count**
+        Print the count of how many times was the address encountered
+        during search.
+
+        Note: With this option, addresses are printed only after the
+        whole search is finished. This may take long time.
+
+    **address**
+        Output only the email addresses instead of the full mailboxes
+        with names and email addresses. This option has no effect on
+        the JSON or S-Expression output formats.
+
+``--deduplicate=(no|mailbox|address)``
+    Control the deduplication of results.
+
+    **no**
+        Output all occurrences of addresses in the matching
+        messages. This is not applicable with ``--output=count``.
+
+    **mailbox**
+        Deduplicate addresses based on the full, case sensitive name
+        and email address, or mailbox. This is effectively the same as
+        piping the ``--deduplicate=no`` output to **sort | uniq**, except
+        for the order of results. This is the default.
+
+    **address**
+        Deduplicate addresses based on the case insensitive address
+        part of the mailbox. Of all the variants (with different name
+        or case), print the one occurring most frequently among the
+        matching messages. If ``--output=count`` is specified, include all
+        variants in the count.
+
+``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
+    This option can be used to present results in either chronological
+    order (**oldest-first**) or reverse chronological order
+    (**newest-first**).
+
+    By default, results will be displayed in reverse chronological
+    order, (that is, the newest results will be displayed first).
+
+    However, if either ``--output=count`` or ``--deduplicate=address`` is
+    specified, this option is ignored and the order of the results is
+    unspecified.
+
+``--exclude=(true|false)``
+    A message is called "excluded" if it matches at least one tag in
+    search.tag\_exclude that does not appear explicitly in the search
+    terms. This option specifies whether to omit excluded messages in
+    the search process.
+
+    The default value, **true**, prevents excluded messages from
+    matching the search terms.
+
+    **false** allows excluded messages to match search terms and
+    appear in displayed results.
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status codes
+
+``20``
+    The requested format version is too old.
+
+``21``
+    The requested format version is too new.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**,
+**notmuch-search(1)**
diff --git a/doc/man1/notmuch-compact.rst b/doc/man1/notmuch-compact.rst
new file mode 100644 (file)
index 0000000..b05593e
--- /dev/null
@@ -0,0 +1,60 @@
+===============
+notmuch-compact
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **compact** [--quiet] [--backup=<*directory*>]
+
+DESCRIPTION
+===========
+
+The **compact** command can be used to compact the notmuch database.
+This can both reduce the space required by the database and improve
+lookup performance.
+
+The compacted database is built in a temporary directory and is later
+moved into the place of the origin database. The original uncompacted
+database is discarded, unless the ``--backup=``\ <directory> option is
+used.
+
+Note that the database write lock will be held during the compaction
+process (which may be quite long) to protect data integrity.
+
+Supported options for **compact** include
+
+``--backup=``\ <directory>
+    Save the current database to the given directory before replacing
+    it with the compacted database. The backup directory must not
+    exist and it must reside on the same mounted filesystem as the
+    current database.
+
+``--quiet``
+    Do not report database compaction progress to stdout.
+
+ENVIRONMENT
+===========
+
+The following environment variables can be used to control the behavior
+of notmuch.
+
+**NOTMUCH\_CONFIG**
+    Specifies the location of the notmuch configuration file. Notmuch
+    will use ${HOME}/.notmuch-config if this variable is not set.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst
new file mode 100644 (file)
index 0000000..8990980
--- /dev/null
@@ -0,0 +1,233 @@
+==============
+notmuch-config
+==============
+
+SYNOPSIS
+========
+
+**notmuch** **config** **get** <*section*>.<*item*>
+
+**notmuch** **config** **set** <*section*>.<*item*> [*value* ...]
+
+**notmuch** **config** **list**
+
+DESCRIPTION
+===========
+
+The **config** command can be used to get or set settings in the notmuch
+configuration file and corresponding database.
+
+Items marked **[STORED IN DATABASE]** are only in the database.  They
+should not be placed in the configuration file, and should be accessed
+programmatically as described in the SYNOPSIS above.
+
+**get**
+    The value of the specified configuration item is printed to
+    stdout. If the item has multiple values (it is a list), each value
+    is separated by a newline character.
+
+**set**
+    The specified configuration item is set to the given value. To
+    specify a multiple-value item (a list), provide each value as a
+    separate command-line argument.
+
+    If no values are provided, the specified configuration item will
+    be removed from the configuration file.
+
+**list**
+    Every configuration item is printed to stdout, each on a separate
+    line of the form::
+
+        *section*.\ *item*\ =\ *value*
+
+    No additional whitespace surrounds the dot or equals sign
+    characters. In a multiple-value item (a list), the values are
+    separated by semicolon characters.
+
+The available configuration items are described below.
+
+**database.path**
+    The top-level directory where your mail currently exists and to
+    where mail will be delivered in the future. Files should be
+    individual email messages. Notmuch will store its database within
+    a sub-directory of the path configured here named ``.notmuch``.
+
+    Default: ``$MAILDIR`` variable if set, otherwise ``$HOME/mail``.
+
+**user.name**
+    Your full name.
+
+    Default: ``$NAME`` variable if set, otherwise read from
+    ``/etc/passwd``.
+
+**user.primary\_email**
+    Your primary email address.
+
+    Default: ``$EMAIL`` variable if set, otherwise constructed from
+    the username and hostname of the current machine.
+
+**user.other\_email**
+    A list of other email addresses at which you receive email.
+
+    Default: not set.
+
+**new.tags**
+    A list of tags that will be added to all messages incorporated by
+    **notmuch new**.
+
+    Default: ``unread;inbox``.
+
+**new.ignore**
+    A list to specify files and directories that will not be searched
+    for messages by **notmuch new**. Each entry in the list is either:
+
+    A file or a directory name, without path, that will be ignored,
+    regardless of the location in the mail store directory hierarchy.
+
+    Or:
+
+    A regular expression delimited with // that will be matched
+    against the path of the file or directory relative to the database
+    path. Matching files and directories will be ignored. The
+    beginning and end of string must be explicitly anchored. For
+    example, /.*/foo$/ would match "bar/foo" and "bar/baz/foo", but
+    not "foo" or "bar/foobar".
+
+    Default: empty list.
+
+**search.exclude\_tags**
+    A list of tags that will be excluded from search results by
+    default. Using an excluded tag in a query will override that
+    exclusion.
+
+    Default: empty list. Note that **notmuch-setup(1)** puts
+    ``deleted;spam`` here when creating new configuration file.
+
+**maildir.synchronize\_flags**
+    If true, then the following maildir flags (in message filenames)
+    will be synchronized with the corresponding notmuch tags:
+
+    +--------+-----------------------------------------------+
+    | Flag   | Tag                                           |
+    +========+===============================================+
+    | D      | draft                                         |
+    +--------+-----------------------------------------------+
+    | F      | flagged                                       |
+    +--------+-----------------------------------------------+
+    | P      | passed                                        |
+    +--------+-----------------------------------------------+
+    | R      | replied                                       |
+    +--------+-----------------------------------------------+
+    | S      | unread (added when 'S' flag is not present)   |
+    +--------+-----------------------------------------------+
+
+    The **notmuch new** command will notice flag changes in filenames
+    and update tags, while the **notmuch tag** and **notmuch restore**
+    commands will notice tag changes and update flags in filenames.
+
+    If there have been any changes in the maildir (new messages added,
+    old ones removed or renamed, maildir flags changed, etc.), it is
+    advisable to run **notmuch new** before **notmuch tag** or
+    **notmuch restore** commands to ensure the tag changes are
+    properly synchronized to the maildir flags, as the commands expect
+    the database and maildir to be in sync.
+
+    Default: ``true``.
+
+**crypto.gpg_path**
+    Name (or full path) of gpg binary to use in verification and
+    decryption of PGP/MIME messages.  NOTE: This configuration item is
+    deprecated, and will be ignored if notmuch is built against GMime
+    3.0 or later.
+
+    Default: ``gpg``.
+
+**index.decrypt** **[STORED IN DATABASE]**
+    Policy for decrypting encrypted messages during indexing.  Must be
+    one of: ``false``, ``auto``, ``nostash``, or ``true``.
+
+    When indexing an encrypted e-mail message, if this variable is set
+    to ``true``, notmuch will try to decrypt the message and index the
+    cleartext, stashing a copy of any discovered session keys for the
+    message.  If ``auto``, it will try to index the cleartext if a
+    stashed session key is already known for the message (e.g. from a
+    previous copy), but will not try to access your secret keys.  Use
+    ``false`` to avoid decrypting even when a stashed session key is
+    already present.
+
+    ``nostash`` is the same as ``true`` except that it will not stash
+    newly-discovered session keys in the database.
+
+    From the command line (i.e. during **notmuch-new(1)**,
+    **notmuch-insert(1)**, or **notmuch-reindex(1)**), the user can
+    override the database's stored decryption policy with the
+    ``--decrypt=`` option.
+
+    Here is a table that summarizes the functionality of each of these
+    policies:
+
+    +------------------------+-------+------+---------+------+
+    |                        | false | auto | nostash | true |
+    +========================+=======+======+=========+======+
+    | Index cleartext using  |       |  X   |    X    |  X   |
+    | stashed session keys   |       |      |         |      |
+    +------------------------+-------+------+---------+------+
+    | Index cleartext        |       |      |    X    |  X   |
+    | using secret keys      |       |      |         |      |
+    +------------------------+-------+------+---------+------+
+    | Stash session keys     |       |      |         |  X   |
+    +------------------------+-------+------+---------+------+
+    | Delete stashed session |   X   |      |         |      |
+    | keys on reindex        |       |      |         |      |
+    +------------------------+-------+------+---------+------+
+
+    Stashed session keys are kept in the database as properties
+    associated with the message.  See ``session-key`` in
+    **notmuch-properties(7)** for more details about how they can be
+    useful.
+
+    Be aware that the notmuch index is likely sufficient (and a
+    stashed session key is certainly sufficient) to reconstruct the
+    cleartext of the message itself, so please ensure that the notmuch
+    message index is adequately protected.  DO NOT USE
+    ``index.decrypt=true`` or ``index.decrypt=nostash`` without
+    considering the security of your index.
+
+    Default: ``auto``.
+
+**built_with.<name>**
+    Compile time feature <name>. Current possibilities include
+    "compact" (see **notmuch-compact(1)**) and "field_processor" (see
+    **notmuch-search-terms(7)**).
+
+**query.<name>** **[STORED IN DATABASE]**
+    Expansion for named query called <name>. See
+    **notmuch-search-terms(7)** for more information about named
+    queries.
+
+ENVIRONMENT
+===========
+
+The following environment variables can be used to control the behavior
+of notmuch.
+
+**NOTMUCH\_CONFIG**
+    Specifies the location of the notmuch configuration file. Notmuch
+    will use ${HOME}/.notmuch-config if this variable is not set.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-properties(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-count.rst b/doc/man1/notmuch-count.rst
new file mode 100644 (file)
index 0000000..9ca20da
--- /dev/null
@@ -0,0 +1,72 @@
+=============
+notmuch-count
+=============
+
+SYNOPSIS
+========
+
+**notmuch** **count** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Count messages matching the search terms.
+
+The number of matching messages (or threads) is output to stdout.
+
+With no search terms, a count of all messages (or threads) in the
+database will be displayed.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+Supported options for **count** include
+
+``--output=(messages|threads|files)``
+    **messages**
+        Output the number of matching messages. This is the default.
+
+    **threads**
+        Output the number of matching threads.
+
+    **files**
+        Output the number of files associated with matching
+        messages. This may be bigger than the number of matching
+        messages due to duplicates (i.e. multiple files having the
+        same message-id).
+
+``--exclude=(true|false)``
+    Specify whether to omit messages matching search.tag\_exclude from
+    the count (the default) or not.
+
+``--batch``
+    Read queries from a file (stdin by default), one per line, and
+    output the number of matching messages (or threads) to stdout, one
+    per line. On an empty input line the count of all messages (or
+    threads) in the database will be output. This option is not
+    compatible with specifying search terms on the command line.
+
+``--lastmod``
+    Append lastmod (counter for number of database updates) and UUID
+    to the output. lastmod values are only comparable between
+    databases with the same UUID.
+
+``--input=``\ <filename>
+    Read input from given file, instead of from stdin. Implies
+    ``--batch``.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-dump.rst b/doc/man1/notmuch-dump.rst
new file mode 100644 (file)
index 0000000..ec6335b
--- /dev/null
@@ -0,0 +1,115 @@
+============
+notmuch-dump
+============
+
+SYNOPSIS
+========
+
+**notmuch** **dump** [--gzip] [--format=(batch-tag|sup)] [--output=<*file*>] [--] [<*search-term*> ...]
+
+DESCRIPTION
+===========
+
+Dump tags for messages matching the given search terms.
+
+Output is to the given filename, if any, or to stdout.
+
+These tags are the only data in the notmuch database that can't be
+recreated from the messages themselves. The output of notmuch dump is
+therefore the only critical thing to backup (and much more friendly to
+incremental backup than the native database files.)
+
+See **notmuch-search-terms(7)** for details of the supported syntax
+for <search-terms>. With no search terms, a dump of all messages in
+the database will be generated. A ``--`` argument instructs notmuch that
+the remaining arguments are search terms.
+
+Supported options for **dump** include
+
+``--gzip``
+    Compress the output in a format compatible with **gzip(1)**.
+
+``--format=(sup|batch-tag)``
+    Notmuch restore supports two plain text dump formats, both with
+    one message-id per line, followed by a list of tags.
+
+    **batch-tag**
+        The default **batch-tag** dump format is intended to more
+        robust against malformed message-ids and tags containing
+        whitespace or non-\ **ascii(7)** characters. Each line has the
+        form::
+
+         +<*encoded-tag*\ > +<*encoded-tag*\ > ... -- id:<*quoted-message-id*\ >
+
+        Tags are hex-encoded by replacing every byte not matching the
+        regex **[A-Za-z0-9@=.,\_+-]** with **%nn** where nn is the two
+        digit hex encoding. The message ID is a valid Xapian query,
+        quoted using Xapian boolean term quoting rules: if the ID
+        contains whitespace or a close paren or starts with a double
+        quote, it must be enclosed in double quotes and double quotes
+        inside the ID must be doubled. The astute reader will notice
+        this is a special case of the batch input format for
+        **notmuch-tag(1)**; note that the single message-id query is
+        mandatory for **notmuch-restore(1)**.
+
+    **sup**
+        The **sup** dump file format is specifically chosen to be
+        compatible with the format of files produced by sup-dump. So
+        if you've previously been using sup for mail, then the
+        **notmuch restore** command provides you a way to import all
+        of your tags (or labels as sup calls them). Each line has the
+        following form::
+
+          <*message-id*\ > **(** <*tag*\ > ... **)**
+
+        with zero or more tags are separated by spaces. Note that
+        (malformed) message-ids may contain arbitrary non-null
+        characters. Note also that tags with spaces will not be
+        correctly restored with this format.
+
+``--include=(config|properties|tags)``
+    Control what kind of metadata is included in the output.
+
+    **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.
+
+    **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.  Ids, keys and values are hex encoded
+        if needed.  See **notmuch-properties(7)** for more details.
+
+    **tags**
+        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 of
+    version 3 of the dump format, there is a header line of the
+    following form::
+
+      #notmuch-dump <*format*>:<*version*> <*included*>
+
+    where <*included*> is a comma separated list of the above options.
+
+``--output=``\ <filename>
+    Write output to given file instead of stdout.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-emacs-mua.rst b/doc/man1/notmuch-emacs-mua.rst
new file mode 100644 (file)
index 0000000..a047613
--- /dev/null
@@ -0,0 +1,81 @@
+=================
+notmuch-emacs-mua
+=================
+
+SYNOPSIS
+========
+
+**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, or mailto: URL.
+
+Supported options for **emacs-mua** include
+
+``-h, --help``
+    Display help.
+
+``-s, --subject=``\ <subject>
+    Specify the subject of the message.
+
+``--to=``\ <to-address>
+    Specify a recipient (To).
+
+``-c, --cc=``\ <cc-address>
+    Specify a carbon-copy (Cc) recipient.
+
+``-b, --bcc=``\ <bcc-address>
+    Specify a blind-carbon-copy (Bcc) recipient.
+
+``-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.
+
+``--client``
+    Use **emacsclient**, rather than **emacs**. For **emacsclient** to
+    work, you need an already running Emacs with a server, or use
+    ``--auto-daemon``.
+
+``--auto-daemon``
+    Automatically start Emacs in daemon mode, if the Emacs server is
+    not running. Applicable with ``--client``. Implies
+    ``--create-frame``.
+
+``--create-frame``
+    Create a new frame instead of trying to use the current Emacs
+    frame. Applicable with ``--client``. This will be required when
+    Emacs is running (or automatically started with ``--auto-daemon``)
+    in daemon mode.
+
+``--print``
+    Output the resulting elisp to stdout instead of evaluating it.
+
+The supported positional parameters and short options are a compatible
+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.
+
+ENVIRONMENT VARIABLES
+=====================
+
+**EMACS**
+    Name of emacs command to invoke. Defaults to "emacs".
+
+**EMACSCLIENT**
+    Name of emacsclient command to invoke. Defaults to "emacsclient".
+
+SEE ALSO
+========
+
+**notmuch(1)**, **emacsclient(1)**, **mutt(1)**
diff --git a/doc/man1/notmuch-insert.rst b/doc/man1/notmuch-insert.rst
new file mode 100644 (file)
index 0000000..86e2f56
--- /dev/null
@@ -0,0 +1,114 @@
+==============
+notmuch-insert
+==============
+
+SYNOPSIS
+========
+
+**notmuch** **insert** [option ...] [+<*tag*>|-<*tag*> ...]
+
+DESCRIPTION
+===========
+
+**notmuch insert** reads a message from standard input and delivers it
+into the maildir directory given by configuration option
+**database.path**, then incorporates the message into the notmuch
+database. It is an alternative to using a separate tool to deliver the
+message then running **notmuch new** afterwards.
+
+The new message will be tagged with the tags specified by the
+**new.tags** configuration option, then by operations specified on the
+command-line: tags prefixed by '+' are added while those prefixed by '-'
+are removed.
+
+If the new message is a duplicate of an existing message in the database
+(it has same Message-ID), it will be added to the maildir folder and
+notmuch database, but the tags will not be changed.
+
+The **insert** command supports hooks. See **notmuch-hooks(5)** for
+more details on hooks.
+
+Option arguments must appear before any tag operation arguments.
+Supported options for **insert** include
+
+``--folder=<``\ folder\ **>**
+    Deliver the message to the specified folder, relative to the
+    top-level directory given by the value of **database.path**. The
+    default is the empty string, which means delivering to the
+    top-level directory.
+
+``--create-folder``
+    Try to create the folder named by the ``--folder`` option, if it
+    does not exist. Otherwise the folder must already exist for mail
+    delivery to succeed.
+
+``--keep``
+    Keep the message file if indexing fails, and keep the message
+    indexed if applying tags or maildir flag synchronization
+    fails. Ignore these errors and return exit status 0 to indicate
+    successful mail delivery.
+
+``--no-hooks``
+    Prevent hooks from being run.
+
+``--world-readable``
+    When writing mail to the mailbox, allow it to be read by users
+    other than the current user.  Note that this does not override
+    umask.  By default, delivered mail is only readable by the current
+    user.
+
+``--decrypt=(true|nostash|auto|false)``
+    If ``true`` and the message is encrypted, try to decrypt the
+    message while indexing, stashing any session keys discovered.  If
+    ``auto``, and notmuch already knows about a session key for the
+    message, it will try decrypting using that session key but will
+    not try to access the user's secret keys.  If decryption is
+    successful, index the cleartext itself.  Either way, the message
+    is always stored to disk in its original form (ciphertext).
+
+    ``nostash`` is the same as ``true`` except that it will not stash
+    newly-discovered session keys in the database.
+
+    Be aware that the index is likely sufficient (and a stashed
+    session key is certainly sufficient) to reconstruct the cleartext
+    of the message itself, so please ensure that the notmuch message
+    index is adequately protected. DO NOT USE ``--decrypt=true`` or
+    ``--decrypt=nostash`` without considering the security of your
+    index.
+
+    See also ``index.decrypt`` in **notmuch-config(1)**.
+
+EXIT STATUS
+===========
+
+This command returns exit status 0 on successful mail delivery,
+non-zero otherwise. The default is to indicate failed mail delivery on
+any errors, including message file delivery to the filesystem, message
+indexing to Notmuch database, changing tags, and synchronizing tags to
+maildir flags. The ``--keep`` option may be used to settle for
+successful message file delivery.
+
+This command supports the following special exit status code for
+errors most likely to be temporary in nature, e.g. failure to get a
+database write lock.
+
+``75 (EX_TEMPFAIL)``
+    A temporary failure occurred; the user is invited to retry.
+
+The exit status of the **post-insert** hook does not affect the exit
+status of the **insert** command.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-new.rst b/doc/man1/notmuch-new.rst
new file mode 100644 (file)
index 0000000..5eeb492
--- /dev/null
@@ -0,0 +1,89 @@
+===========
+notmuch-new
+===========
+
+SYNOPSIS
+========
+
+**notmuch** **new** [options]
+
+DESCRIPTION
+===========
+
+Find and import any new messages to the database.
+
+The **new** command scans all sub-directories of the database,
+performing full-text indexing on new messages that are found. Each new
+message will automatically be tagged with both the **inbox** and
+**unread** tags.
+
+You should run **notmuch new** once after first running **notmuch
+setup** to create the initial database. The first run may take a long
+time if you have a significant amount of mail (several hundred thousand
+messages or more). Subsequently, you should run **notmuch new** whenever
+new mail is delivered and you wish to incorporate it into the database.
+These subsequent runs will be much quicker than the initial run.
+
+Invoking ``notmuch`` with no command argument will run **new** if
+**notmuch setup** has previously been completed, but **notmuch new** has
+not previously been run.
+
+**notmuch new** updates tags according to maildir flag changes if the
+**maildir.synchronize\_flags** configuration option is enabled. See
+**notmuch-config(1)** for details.
+
+The **new** command supports hooks. See **notmuch-hooks(5)** for more
+details on hooks.
+
+Supported options for **new** include
+
+``--no-hooks``
+    Prevents hooks from being run.
+
+``--quiet``
+    Do not print progress or results.
+
+``--decrypt=(true|nostash|auto|false)``
+    If ``true``, when encountering an encrypted message, try to
+    decrypt it while indexing, and stash any discovered session keys.
+    If ``auto``, try to use any session key already known to belong to
+    this message, but do not attempt to use the user's secret keys.
+    If decryption is successful, index the cleartext of the message.
+
+    Be aware that the index is likely sufficient (and the session key
+    is certainly sufficient) to reconstruct the cleartext of the
+    message itself, so please ensure that the notmuch message index is
+    adequately protected.  DO NOT USE ``--decrypt=true`` or
+    ``--decrypt=nostash`` without considering the security of your
+    index.
+
+    See also ``index.decrypt`` in **notmuch-config(1)**.
+
+``--full-scan``
+    By default notmuch-new uses directory modification times (mtimes)
+    to optimize the scanning of directories for new mail. This option turns
+    that optimization off.
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status code
+
+``75 (EX_TEMPFAIL)``
+    A temporary failure occurred; the user is invited to retry.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-reindex.rst b/doc/man1/notmuch-reindex.rst
new file mode 100644 (file)
index 0000000..cd7c91a
--- /dev/null
@@ -0,0 +1,100 @@
+===============
+notmuch-reindex
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **reindex** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Re-index all messages matching the search terms.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<*search-term*\ >.
+
+The **reindex** command searches for all messages matching the
+supplied search terms, and re-creates the full-text index on these
+messages using the supplied options.
+
+Supported options for **reindex** include
+
+``--decrypt=(true|nostash|auto|false)``
+    If ``true``, when encountering an encrypted message, try to
+    decrypt it while reindexing, stashing any session keys discovered.
+    If ``auto``, and notmuch already knows about a session key for the
+    message, it will try decrypting using that session key but will
+    not try to access the user's secret keys.  If decryption is
+    successful, index the cleartext itself.
+
+    ``nostash`` is the same as ``true`` except that it will not stash
+    newly-discovered session keys in the database.
+
+    If ``false``, notmuch reindex will also delete any stashed session
+    keys for all messages matching the search terms.
+
+    Be aware that the index is likely sufficient (and a stashed
+    session key is certainly sufficient) to reconstruct the cleartext
+    of the message itself, so please ensure that the notmuch message
+    index is adequately protected. DO NOT USE ``--decrypt=true`` or
+    ``--decrypt=nostash`` without considering the security of your
+    index.
+
+    See also ``index.decrypt`` in **notmuch-config(1)**.
+
+EXAMPLES
+========
+
+A user just received an encrypted message without indexing its
+cleartext.  After reading it (via ``notmuch show --decrypt=true``),
+they decide that they want to index its cleartext so that they can
+easily find it later and read it without having to have access to
+their secret keys:
+
+::
+
+ notmuch reindex --decrypt=true id:1234567@example.com
+
+A user wants to change their policy going forward to start indexing
+cleartext.  But they also want indexed access to the cleartext of all
+previously-received encrypted messages.  Some messages might have
+already been indexed in the clear (as in the example above). They can
+ask notmuch to just reindex the not-yet-indexed messages:
+
+::
+
+  notmuch config set index.decrypt true
+  notmuch reindex tag:encrypted and not property:index.decryption=success
+
+Later, the user changes their mind, and wants to stop indexing
+cleartext (perhaps their threat model has changed, or their trust in
+their index store has been shaken).  They also want to clear all of
+their old cleartext from the index.  Note that they compact the
+database afterward as a workaround for
+https://trac.xapian.org/ticket/742:
+
+::
+
+  notmuch config set index.decrypt false
+  notmuch reindex property:index.decryption=success
+  notmuch compact
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-compact(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst
new file mode 100644 (file)
index 0000000..5c64c4a
--- /dev/null
@@ -0,0 +1,130 @@
+=============
+notmuch-reply
+=============
+
+SYNOPSIS
+========
+
+**notmuch** **reply** [option ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Constructs a reply template for a set of messages.
+
+To make replying to email easier, **notmuch reply** takes an existing
+set of messages and constructs a suitable mail template. Its To:
+address is set according to the original email in this way: if the
+Reply-to: header is present and different from any To:/Cc: address it
+is used, otherwise From: header is used. Unless
+``--reply-to=sender`` is specified, values from the To: and Cc: headers
+are copied, but not including any of the current user's email addresses
+(as configured in primary\_mail or other\_email in the .notmuch-config
+file) in the recipient list.
+
+It also builds a suitable new subject, including Re: at the front (if
+not already present), and adding the message IDs of the messages being
+replied to to the References list and setting the In-Reply-To: field
+correctly.
+
+Finally, the original contents of the emails are quoted by prefixing
+each line with '> ' and included in the body.
+
+The resulting message template is output to stdout.
+
+Supported options for **reply** include
+
+``--format=``\ (**default**\ \|\ **json**\ \|\ **sexp**\ \|\ **headers-only**)
+    **default**
+        Includes subject and quoted message body as an RFC 2822
+        message.
+
+    **json**
+        Produces JSON output containing headers for a reply message
+        and the contents of the original message. This output can be
+        used by a client to create a reply message intelligently.
+
+    **sexp**
+        Produces S-Expression output containing headers for a reply
+        message and the contents of the original message. This output
+        can be used by a client to create a reply message
+        intelligently.
+
+    **headers-only**
+        Only produces In-Reply-To, References, To, Cc, and Bcc
+        headers.
+
+``--format-version=N``
+    Use the specified structured output format version. This is
+    intended for programs that invoke **notmuch(1)** internally. If
+    omitted, the latest supported version will be used.
+
+``--reply-to=``\ (**all**\ \|\ **sender**)
+    **all** (default)
+        Replies to all addresses.
+
+    **sender**
+        Replies only to the sender. If replying to user's own message
+        (Reply-to: or From: header is one of the user's configured
+        email addresses), try To:, Cc:, and Bcc: headers in this
+        order, and copy values from the first that contains something
+        other than only the user's addresses.
+
+``--decrypt=(false|auto|true)``
+
+    If ``true``, decrypt any MIME encrypted parts found in the
+    selected content (i.e., "multipart/encrypted" parts). Status
+    of the decryption will be reported (currently only supported
+    with ``--format=json`` and ``--format=sexp``), and on successful
+    decryption the multipart/encrypted part will be replaced by
+    the decrypted content.
+
+    If ``auto``, and a session key is already known for the
+    message, then it will be decrypted, but notmuch will not try
+    to access the user's secret keys.
+
+    Use ``false`` to avoid even automatic decryption.
+
+    Non-automatic decryption expects a functioning
+    **gpg-agent(1)** to provide any needed credentials. Without
+    one, the decryption will likely fail.
+
+    Default: ``auto``
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+Note: It is most common to use **notmuch reply** with a search string
+matching a single message, (such as id:<message-id>), but it can be
+useful to reply to several messages at once. For example, when a series
+of patches are sent in a single thread, replying to the entire thread
+allows for the reply to comment on issues found in multiple patches. The
+default format supports replying to multiple messages at once, but the
+JSON and S-Expression formats do not.
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status codes
+
+``20``
+    The requested format version is too old.
+
+``21``
+    The requested format version is too new.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-restore.rst b/doc/man1/notmuch-restore.rst
new file mode 100644 (file)
index 0000000..c0f47f2
--- /dev/null
@@ -0,0 +1,99 @@
+===============
+notmuch-restore
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **restore** [--accumulate] [--format=(auto|batch-tag|sup)] [--input=<*filename*>]
+
+DESCRIPTION
+===========
+
+Restores the tags from the given file (see **notmuch dump**).
+
+The input is read from the given filename, if any, or from stdin.
+
+Supported options for **restore** include
+
+``--accumulate``
+    The union of the existing and new tags is applied, instead of
+    replacing each message's tags as they are read in from the dump
+    file.
+
+``--format=(sup|batch-tag|auto)``
+    Notmuch restore supports two plain text dump formats, with each
+    line specifying a message-id and a set of tags. For details of the
+    actual formats, see **notmuch-dump(1)**.
+
+    **sup**
+        The **sup** dump file format is specifically chosen to be
+        compatible with the format of files produced by sup-dump. So
+        if you've previously been using sup for mail, then the
+        **notmuch restore** command provides you a way to import all
+        of your tags (or labels as sup calls them).
+
+    **batch-tag**
+        The **batch-tag** dump format is intended to more robust
+        against malformed message-ids and tags containing whitespace
+        or non-\ **ascii(7)** characters. See **notmuch-dump(1)** for
+        details on this format.
+
+        **notmuch restore** updates the maildir flags according to tag
+        changes if the **maildir.synchronize\_flags** configuration
+        option is enabled. See **notmuch-config(1)** for details.
+
+    **auto**
+        This option (the default) tries to guess the format from the
+        input. For correctly formed input in either supported format,
+        this heuristic, based the fact that batch-tag format contains
+        no parentheses, should be accurate.
+
+``--include=(config|properties|tags)``
+    Control what kind of metadata is restored.
+
+    **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.
+
+    **properties**
+        Restore per-message (key,value) metadata.  Each line starts
+        with "#= ", followed by a message id, and a space separated
+        list of key=value pairs.  Ids, keys and values are hex encoded
+        if needed.  See **notmuch-properties(7)** for more details.
+
+    **tags**
+        Restore 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.
+
+``--input=``\ <filename>
+    Read input from given file instead of stdin.
+
+GZIPPED INPUT
+=============
+
+\ **notmuch restore** will detect if the input is compressed in
+**gzip(1)** format and automatically decompress it while reading. This
+detection does not depend on file naming and in particular works for
+standard input.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst
new file mode 100644 (file)
index 0000000..654c5f2
--- /dev/null
@@ -0,0 +1,179 @@
+==============
+notmuch-search
+==============
+
+SYNOPSIS
+========
+
+**notmuch** **search** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Search for messages matching the given search terms, and display as
+results the threads containing the matched messages.
+
+The output consists of one line per thread, giving a thread ID, the date
+of the newest (or oldest, depending on the sort option) matched message
+in the thread, the number of matched messages and total messages in the
+thread, the names of all participants in the thread, and the subject of
+the newest (or oldest) message.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+Supported options for **search** include
+
+``--format=``\ (**json**\ \|\ **sexp**\ \|\ **text**\ \|\ **text0**)
+    Presents the results in either JSON, S-Expressions, newline
+    character separated plain-text (default), or null character
+    separated plain-text (compatible with **xargs(1)** -0 option where
+    available).
+
+``--format-version=N``
+    Use the specified structured output format version. This is
+    intended for programs that invoke **notmuch(1)** internally. If
+    omitted, the latest supported version will be used.
+
+``--output=(summary|threads|messages|files|tags)``
+    **summary**
+        Output a summary of each thread with any message matching the
+        search terms. The summary includes the thread ID, date, the
+        number of messages in the thread (both the number matched and
+        the total number), the authors of the thread and the
+        subject. In the case where a thread contains multiple files
+        for some messages, the total number of files is printed in
+        parentheses (see below for an example).
+
+    **threads**
+        Output the thread IDs of all threads with any message matching
+        the search terms, either one per line (``--format=text``),
+        separated by null characters (``--format=text0``), as a JSON array
+        (``--format=json``), or an S-Expression list (``--format=sexp``).
+
+    **messages**
+        Output the message IDs of all messages matching the search
+        terms, either one per line (``--format=text``), separated by null
+        characters (``--format=text0``), as a JSON array (``--format=json``),
+        or as an S-Expression list (``--format=sexp``).
+
+    **files**
+        Output the filenames of all messages matching the search
+        terms, either one per line (``--format=text``), separated by null
+        characters (``--format=text0``), as a JSON array (``--format=json``),
+        or as an S-Expression list (``--format=sexp``).
+
+        Note that each message may have multiple filenames associated
+        with it. All of them are included in the output (unless
+        limited with the ``--duplicate=N`` option). This may be
+        particularly confusing for **folder:** or **path:** searches
+        in a specified directory, as the messages may have duplicates
+        in other directories that are included in the output, although
+        these files alone would not match the search.
+
+    **tags**
+        Output all tags that appear on any message matching the search
+        terms, either one per line (``--format=text``), separated by null
+        characters (``--format=text0``), as a JSON array (``--format=json``),
+        or as an S-Expression list (``--format=sexp``).
+
+``--sort=``\ (**newest-first**\ \|\ **oldest-first**)
+    This option can be used to present results in either chronological
+    order (**oldest-first**) or reverse chronological order
+    (**newest-first**).
+
+    Note: The thread order will be distinct between these two options
+    (beyond being simply reversed). When sorting by **oldest-first**
+    the threads will be sorted by the oldest message in each thread,
+    but when sorting by **newest-first** the threads will be sorted by
+    the newest message in each thread.
+
+    By default, results will be displayed in reverse chronological
+    order, (that is, the newest results will be displayed first).
+
+``--offset=[-]N``
+    Skip displaying the first N results. With the leading '-', start
+    at the Nth result from the end.
+
+``--limit=N``
+    Limit the number of displayed results to N.
+
+``--exclude=(true|false|all|flag)``
+    A message is called "excluded" if it matches at least one tag in
+    search.tag\_exclude that does not appear explicitly in the search
+    terms. This option specifies whether to omit excluded messages in
+    the search process.
+
+    **true** (default)
+        Prevent excluded messages from matching the search terms.
+
+    **all**
+        Additionally prevent excluded messages from appearing in
+        displayed results, in effect behaving as though the excluded
+        messages do not exist.
+
+    **false**
+        Allow excluded messages to match search terms and appear in
+        displayed results. Excluded messages are still marked in the
+        relevant outputs.
+
+    **flag**
+        Only has an effect when ``--output=summary``. The output is
+        almost identical to **false**, but the "match count" is the
+        number of matching non-excluded messages in the thread, rather
+        than the number of matching messages.
+
+``--duplicate=N``
+    For ``--output=files``, output the Nth filename associated with
+    each message matching the query (N is 1-based). If N is greater
+    than the number of files associated with the message, don't print
+    anything.
+
+    For ``--output=messages``, only output message IDs of messages
+    matching the search terms that have at least N filenames
+    associated with them.
+
+    Note that this option is orthogonal with the **folder:** search
+    prefix. The prefix matches messages based on filenames. This
+    option filters filenames of the matching messages.
+
+EXAMPLE
+=======
+
+The following shows an example of the summary output format, with one
+message having multiple filenames.
+
+::
+
+  % notmuch search date:today.. and tag:bad-news
+  thread:0000000000063c10 Today [1/1] Some Persun; To the bone (bad-news inbox unread)
+  thread:0000000000063c25 Today [1/1(2)] Ann Other; Bears (bad-news inbox unread)
+  thread:0000000000063c00 Today [1/1] A Thurd; Bites, stings, sad feelings (bad-news unread)
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status codes
+
+``20``
+    The requested format version is too old.
+
+``21``
+    The requested format version is too new.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
+**notmuch-address(1)**
diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst
new file mode 100644 (file)
index 0000000..8bfa87c
--- /dev/null
@@ -0,0 +1,221 @@
+============
+notmuch-show
+============
+
+SYNOPSIS
+========
+
+**notmuch** **show** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Shows all messages matching the search terms.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<search-terms>.
+
+The messages will be grouped and sorted based on the threading (all
+replies to a particular message will appear immediately after that
+message in date order). The output is not indented by default, but depth
+tags are printed so that proper indentation can be performed by a
+post-processor (such as the emacs interface to notmuch).
+
+Supported options for **show** include
+
+``--entire-thread=(true|false)``
+    If true, **notmuch show** outputs all messages in the thread of
+    any message matching the search terms; if false, it outputs only
+    the matching messages. For ``--format=json`` and ``--format=sexp``
+    this defaults to true. For other formats, this defaults to false.
+
+``--format=(text|json|sexp|mbox|raw)``
+    **text** (default for messages)
+        The default plain-text format has all text-content MIME parts
+        decoded. Various components in the output, (**message**,
+        **header**, **body**, **attachment**, and MIME **part**), will
+        be delimited by easily-parsed markers. Each marker consists of
+        a Control-L character (ASCII decimal 12), the name of the
+        marker, and then either an opening or closing brace, ('{' or
+        '}'), to either open or close the component. For a multipart
+        MIME message, these parts will be nested.
+
+    **json**
+        The output is formatted with Javascript Object Notation
+        (JSON). This format is more robust than the text format for
+        automated processing. The nested structure of multipart MIME
+        messages is reflected in nested JSON output. By default JSON
+        output includes all messages in a matching thread; that is, by
+        default, ``--format=json`` sets ``--entire-thread``. The
+        caller can disable this behaviour by setting
+        ``--entire-thread=false``.  The JSON output is always encoded
+        as UTF-8 and any message content included in the output will
+        be charset-converted to UTF-8.
+
+    **sexp**
+        The output is formatted as the Lisp s-expression (sexp)
+        equivalent of the JSON format above. Objects are formatted as
+        property lists whose keys are keywords (symbols preceded by a
+        colon). True is formatted as ``t`` and both false and null are
+        formatted as ``nil``. As for JSON, the s-expression output is
+        always encoded as UTF-8.
+
+    **mbox**
+        All matching messages are output in the traditional, Unix mbox
+        format with each message being prefixed by a line beginning
+        with "From " and a blank line separating each message. Lines
+        in the message content beginning with "From " (preceded by
+        zero or more '>' characters) have an additional '>' character
+        added. This reversible escaping is termed "mboxrd" format and
+        described in detail here:
+
+            http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html
+
+    **raw** (default if ``--part`` is given)
+        Write the raw bytes of the given MIME part of a message to
+        standard out. For this format, it is an error to specify a
+        query that matches more than one message.
+
+        If the specified part is a leaf part, this outputs the body of
+        the part after performing content transfer decoding (but no
+        charset conversion). This is suitable for saving attachments,
+        for example.
+
+        For a multipart or message part, the output includes the part
+        headers as well as the body (including all child parts). No
+        decoding is performed because multipart and message parts
+        cannot have non-trivial content transfer encoding. Consumers
+        of this may need to implement MIME decoding and similar
+        functions.
+
+``--format-version=N``
+    Use the specified structured output format version. This is
+    intended for programs that invoke **notmuch(1)** internally. If
+    omitted, the latest supported version will be used.
+
+``--part=N``
+    Output the single decoded MIME part N of a single message. The
+    search terms must match only a single message. Message parts are
+    numbered in a depth-first walk of the message MIME structure, and
+    are identified in the 'json', 'sexp' or 'text' output formats.
+
+    Note that even a message with no MIME structure or a single body
+    part still has two MIME parts: part 0 is the whole message
+    (headers and body) and part 1 is just the body.
+
+``--verify``
+    Compute and report the validity of any MIME cryptographic
+    signatures found in the selected content (e.g., "multipart/signed"
+    parts). Status of the signature will be reported (currently only
+    supported with ``--format=json`` and ``--format=sexp``), and the
+    multipart/signed part will be replaced by the signed data.
+
+``--decrypt=(false|auto|true|stash)``
+    If ``true``, decrypt any MIME encrypted parts found in the
+    selected content (e.g., "multipart/encrypted" parts). Status of
+    the decryption will be reported (currently only supported
+    with ``--format=json`` and ``--format=sexp``) and on successful
+    decryption the multipart/encrypted part will be replaced by
+    the decrypted content.
+
+    ``stash`` behaves like ``true``, but upon successful decryption it
+    will also stash the message's session key in the database, and
+    index the cleartext of the message, enabling automatic decryption
+    in the future.
+
+    If ``auto``, and a session key is already known for the
+    message, then it will be decrypted, but notmuch will not try
+    to access the user's keys.
+
+    Use ``false`` to avoid even automatic decryption.
+
+    Non-automatic decryption (``stash`` or ``true``, in the absence of
+    a stashed session key) expects a functioning **gpg-agent(1)** to
+    provide any needed credentials. Without one, the decryption will
+    fail.
+
+    Note: setting either ``true`` or ``stash`` here implies
+    ``--verify``.
+
+    Here is a table that summarizes each of these policies:
+
+    +------------------------+-------+------+------+-------+
+    |                        | false | auto | true | stash |
+    +========================+=======+======+======+=======+
+    | Show cleartext if      |       |  X   |  X   |   X   |
+    | session key is         |       |      |      |       |
+    | already known          |       |      |      |       |
+    +------------------------+-------+------+------+-------+
+    | Use secret keys to     |       |      |  X   |   X   |
+    | show cleartext         |       |      |      |       |
+    +------------------------+-------+------+------+-------+
+    | Stash any newly        |       |      |      |   X   |
+    | recovered session keys,|       |      |      |       |
+    | reindexing message if  |       |      |      |       |
+    | found                  |       |      |      |       |
+    +------------------------+-------+------+------+-------+
+
+    Note: ``--decrypt=stash`` requires write access to the database.
+    Otherwise, ``notmuch show`` operates entirely in read-only mode.
+
+    Default: ``auto``
+
+``--exclude=(true|false)``
+    Specify whether to omit threads only matching search.tag\_exclude
+    from the search results (the default) or not. In either case the
+    excluded message will be marked with the exclude flag (except when
+    output=mbox when there is nowhere to put the flag).
+
+    If ``--entire-thread`` is specified then complete threads are returned
+    regardless (with the excluded flag being set when appropriate) but
+    threads that only match in an excluded message are not returned
+    when ``--exclude=true.``
+
+    The default is ``--exclude=true.``
+
+``--body=(true|false)``
+    If true (the default) **notmuch show** includes the bodies of the
+    messages in the output; if false, bodies are omitted.
+    ``--body=false`` is only implemented for the json and sexp formats
+    and it is incompatible with ``--part > 0.``
+
+    This is useful if the caller only needs the headers as body-less
+    output is much faster and substantially smaller.
+
+``--include-html``
+    Include "text/html" parts as part of the output (currently only
+    supported with ``--format=json`` and ``--format=sexp``). By default,
+    unless ``--part=N`` is used to select a specific part or
+    ``--include-html`` is used to include all "text/html" parts, no
+    part with content type "text/html" is included in the output.
+
+A common use of **notmuch show** is to display a single thread of email
+messages. For this, use a search term of "thread:<thread-id>" as can be
+seen in the first column of output from the **notmuch search** command.
+
+EXIT STATUS
+===========
+
+This command supports the following special exit status codes
+
+``20``
+    The requested format version is too old.
+
+``21``
+    The requested format version is too new.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-tag(1)**
diff --git a/doc/man1/notmuch-tag.rst b/doc/man1/notmuch-tag.rst
new file mode 100644 (file)
index 0000000..c2324f5
--- /dev/null
@@ -0,0 +1,114 @@
+===========
+notmuch-tag
+===========
+
+SYNOPSIS
+========
+
+**notmuch** **tag** [options ...] +<*tag*>|-<*tag*> [--] <*search-term*> ...
+
+**notmuch** **tag** **--batch** [--input=<*filename*>]
+
+DESCRIPTION
+===========
+
+Add/remove tags for all messages matching the search terms.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<*search-term*\ >.
+
+Tags prefixed by '+' are added while those prefixed by '-' are removed.
+For each message, tag changes are applied in the order they appear on
+the command line.
+
+The beginning of the search terms is recognized by the first argument
+that begins with neither '+' nor '-'. Support for an initial search term
+beginning with '+' or '-' is provided by allowing the user to specify a
+"--" argument to separate the tags from the search terms.
+
+**notmuch tag** updates the maildir flags according to tag changes if
+the **maildir.synchronize\_flags** configuration option is enabled. See
+**notmuch-config(1)** for details.
+
+Supported options for **tag** include
+
+``--remove-all``
+    Remove all tags from each message matching the search terms before
+    applying the tag changes appearing on the command line.  This
+    means setting the tags of each message to the tags to be added. If
+    there are no tags to be added, the messages will have no tags.
+
+``--batch``
+    Read batch tagging operations from a file (stdin by default).
+    This is more efficient than repeated **notmuch tag**
+    invocations. See `TAG FILE FORMAT <#tag_file_format>`__ below for
+    the input format. This option is not compatible with specifying
+    tagging on the command line.
+
+``--input=``\ <filename>
+    Read input from given file, instead of from stdin. Implies
+    ``--batch``.
+
+TAG FILE FORMAT
+===============
+
+The input must consist of lines of the format:
+
++<*tag*\ >\|-<*tag*\ > [...] [--] <*query*\ >
+
+Each line is interpreted similarly to **notmuch tag** command line
+arguments. The delimiter is one or more spaces ' '. Any characters in
+<*tag*\ > **may** be hex-encoded with %NN where NN is the hexadecimal
+value of the character. To hex-encode a character with a multi-byte
+UTF-8 encoding, hex-encode each byte. Any spaces in <tag> **must** be
+hex-encoded as %20. Any characters that are not part of <*tag*\ > **must
+not** be hex-encoded.
+
+In the future tag:"tag with spaces" style quoting may be supported for
+<*tag*\ > as well; for this reason all double quote characters in
+<*tag*\ > **should** be hex-encoded.
+
+The <*query*\ > should be quoted using Xapian boolean term quoting
+rules: if a term contains whitespace or a close paren or starts with a
+double quote, it must be enclosed in double quotes (not including any
+prefix) and double quotes inside the term must be doubled (see below for
+examples).
+
+Leading and trailing space ' ' is ignored. Empty lines and lines
+beginning with '#' are ignored.
+
+EXAMPLE
+-------
+
+The following shows a valid input to batch tagging. Note that only the
+isolated '\*' acts as a wildcard. Also note the two different quotings
+of the tag **space in tags**
+
+::
+
+    +winner *
+    +foo::bar%25 -- (One and Two) or (One and tag:winner)
+    +found::it -- tag:foo::bar%
+    # ignore this line and the next
+
+    +space%20in%20tags -- Two
+    # add tag '(tags)', among other stunts.
+    +crazy{ +(tags) +&are +#possible\ -- tag:"space in tags"
+    +match*crazy -- tag:crazy{
+    +some_tag -- id:"this is ""nauty)"""
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst
new file mode 100644 (file)
index 0000000..d2cd8da
--- /dev/null
@@ -0,0 +1,190 @@
+=======
+notmuch
+=======
+
+SYNOPSIS
+========
+
+**notmuch** [option ...] **command** [arg ...]
+
+DESCRIPTION
+===========
+
+Notmuch is a command-line based program for indexing, searching,
+reading, and tagging large collections of email messages.
+
+This page describes how to get started using notmuch from the command
+line, and gives a brief overview of the commands available. For more
+information on e.g. **notmuch show** consult the **notmuch-show(1)** man
+page, also accessible via **notmuch help show**
+
+The quickest way to get started with Notmuch is to simply invoke the
+``notmuch`` command with no arguments, which will interactively guide
+you through the process of indexing your mail.
+
+NOTE
+====
+
+While the command-line program ``notmuch`` provides powerful
+functionality, it does not provide the most convenient interface for
+that functionality. More sophisticated interfaces are expected to be
+built on top of either the command-line interface, or more likely, on
+top of the notmuch library interface. See https://notmuchmail.org for
+more about alternate interfaces to notmuch. The emacs-based interface to
+notmuch (available under **emacs/** in the Notmuch source distribution)
+is probably the most widely used at this time.
+
+OPTIONS
+=======
+
+Supported global options for ``notmuch`` include
+
+``--help`` [command-name]
+    Print a synopsis of available commands and exit. With an optional
+    command name, show the man page for that subcommand.
+
+``--version``
+    Print the installed version of notmuch, and exit.
+
+``--config=FILE``
+    Specify the configuration file to use. This overrides any
+    configuration file specified by ${NOTMUCH\_CONFIG}.
+
+``--uuid=HEX``
+    Enforce that the database UUID (a unique identifier which persists
+    until e.g. the database is compacted) is HEX; exit with an error
+    if it is not. This is useful to detect rollover in modification
+    counts on messages. You can find this UUID using e.g. ``notmuch
+    count --lastmod``
+
+All global options except ``--config`` can also be specified after the
+command. For example, ``notmuch subcommand --uuid=HEX`` is equivalent
+to ``notmuch --uuid=HEX subcommand``.
+
+COMMANDS
+========
+
+SETUP
+-----
+
+The **notmuch setup** command is used to configure Notmuch for first
+use, (or to reconfigure it later).
+
+The setup command will prompt for your full name, your primary email
+address, any alternate email addresses you use, and the directory
+containing your email archives. Your answers will be written to a
+configuration file in ${NOTMUCH\_CONFIG} (if set) or
+${HOME}/.notmuch-config . This configuration file will be created with
+descriptive comments, making it easy to edit by hand later to change the
+configuration. Or you can run **notmuch setup** again to change the
+configuration.
+
+The mail directory you specify can contain any number of sub-directories
+and should primarily contain only files with individual email messages
+(eg. maildir or mh archives are perfect). If there are other, non-email
+files (such as indexes maintained by other email programs) then notmuch
+will do its best to detect those and ignore them.
+
+Mail storage that uses mbox format, (where one mbox file contains many
+messages), will not work with notmuch. If that's how your mail is
+currently stored, it is recommended you first convert it to maildir
+format with a utility such as mb2md before running **notmuch setup .**
+
+Invoking ``notmuch`` with no command argument will run **setup** if the
+setup command has not previously been completed.
+
+OTHER COMMANDS
+--------------
+
+Several of the notmuch commands accept search terms with a common
+syntax. See **notmuch-search-terms**\ (7) for more details on the
+supported syntax.
+
+The **search**, **show**, **address** and **count** commands are used
+to query the email database.
+
+The **reply** command is useful for preparing a template for an email
+reply.
+
+The **tag** command is the only command available for manipulating
+database contents.
+
+The **dump** and **restore** commands can be used to create a textual
+dump of email tags for backup purposes, and to restore from that dump.
+
+The **config** command can be used to get or set settings in the notmuch
+configuration file.
+
+CUSTOM COMMANDS
+---------------
+
+If the given command is not known to notmuch, notmuch tries to execute
+the external **notmuch-<subcommand>** in ${PATH} instead. This allows
+users to have their own notmuch related tools to be run via the
+notmuch command. By design, this does not allow notmuch's own commands
+to be overridden using external commands.
+
+OPTION SYNTAX
+-------------
+
+All options accepting an argument can be used with '=' or ':' as a
+separator. For the cases where it's not ambiguous (in particular
+excluding boolean options), a space can also be used. The following
+are all equivalent:
+
+::
+
+   notmuch --config=alt-config config get user.name
+   notmuch --config:alt-config config get user.name
+   notmuch --config alt-config config get user.name
+
+ENVIRONMENT
+===========
+
+The following environment variables can be used to control the behavior
+of notmuch.
+
+**NOTMUCH\_CONFIG**
+    Specifies the location of the notmuch configuration file. Notmuch
+    will use ${HOME}/.notmuch-config if this variable is not set.
+
+**NOTMUCH\_TALLOC\_REPORT**
+    Location to write a talloc memory usage report. See
+    **talloc\_enable\_leak\_report\_full** in **talloc(3)** for more
+    information.
+
+**NOTMUCH\_DEBUG\_QUERY**
+    If set to a non-empty value, the notmuch library will print (to
+    stderr) Xapian queries it constructs.
+
+SEE ALSO
+========
+
+**notmuch-address(1)**,
+**notmuch-compact(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reindex(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
+
+The notmuch website: **https://notmuchmail.org**
+
+CONTACT
+=======
+
+Feel free to send questions, comments, or kudos to the notmuch mailing
+list <notmuch@notmuchmail.org> . Subscription is not required before
+posting, but is available from the notmuchmail.org website.
+
+Real-time interaction with the Notmuch community is available via IRC
+(server: irc.freenode.net, channel: #notmuch).
diff --git a/doc/man5/notmuch-hooks.rst b/doc/man5/notmuch-hooks.rst
new file mode 100644 (file)
index 0000000..de2ed0c
--- /dev/null
@@ -0,0 +1,62 @@
+=============
+notmuch-hooks
+=============
+
+SYNOPSIS
+========
+
+$DATABASEDIR/.notmuch/hooks/*
+
+DESCRIPTION
+===========
+
+Hooks are scripts (or arbitrary executables or symlinks to such) that
+notmuch invokes before and after certain actions. These scripts reside
+in the .notmuch/hooks directory within the database directory and must
+have executable permissions.
+
+The currently available hooks are described below.
+
+**pre-new**
+    This hook is invoked by the **new** command before scanning or
+    importing new messages into the database. If this hook exits with
+    a non-zero status, notmuch will abort further processing of the
+    **new** command.
+
+    Typically this hook is used for fetching or delivering new mail to
+    be imported into the database.
+
+**post-new**
+    This hook is invoked by the **new** command after new messages
+    have been imported into the database and initial tags have been
+    applied. The hook will not be run if there have been any errors
+    during the scan or import.
+
+    Typically this hook is used to perform additional query-based
+    tagging on the imported messages.
+
+**post-insert**
+    This hook is invoked by the **insert** command after the message
+    has been delivered, added to the database, and initial tags have
+    been applied. The hook will not be run if there have been any
+    errors during the message delivery; what is regarded as successful
+    delivery depends on the ``--keep`` option.
+
+    Typically this hook is used to perform additional query-based
+    tagging on the delivered messages.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst
new file mode 100644 (file)
index 0000000..802e676
--- /dev/null
@@ -0,0 +1,124 @@
+==================
+notmuch-properties
+==================
+
+SYNOPSIS
+========
+
+**notmuch** **count** **property:**\ <*key*>=<*value*>
+
+**notmuch** **search** **property:**\ <*key*>=<*value*>
+
+**notmuch** **show** **property:**\ <*key*>=<*value*>
+
+**notmuch** **reindex** **property:**\ <*key*>=<*value*>
+
+**notmuch** **tag** +<*tag*> **property:**\ <*key*>=<*value*>
+
+
+**notmuch** **dump** **--include=properties**
+
+**notmuch** **restore** **--include=properties**
+
+DESCRIPTION
+===========
+
+Several notmuch commands can search for, modify, add or remove
+properties associated with specific messages.  Properties are
+key/value pairs, and a message can have more than one key/value pair
+for the same key.
+
+While users can select based on a specific property in their search
+terms with the prefix **property:**, the notmuch command-line
+interface does not provide mechanisms for modifying properties
+directly to the user.
+
+Instead, message properties are expected to be set and used
+programmatically, according to logic in notmuch itself, or in
+extensions to it.
+
+Extensions to notmuch which make use of properties are encouraged to
+report the specific properties used to the upstream notmuch project,
+as a way of avoiding collisions in the property namespace.
+
+CONVENTIONS
+===========
+
+Any property with a key that starts with "index." will be removed (and
+possibly re-set) upon reindexing (see **notmuch-reindex(1)**).
+
+MESSAGE PROPERTIES
+==================
+
+The following properties are set by notmuch internally in the course
+of its normal activity.
+
+**index.decryption**
+    If a message contains encrypted content, and notmuch tries to
+    decrypt that content during indexing, it will add the property
+    ``index.decryption=success`` when the cleartext was successfully
+    indexed.  If notmuch attempts to decrypt any part of a message
+    during indexing and that decryption attempt fails, it will add the
+    property ``index.decryption=failure`` to the message.
+
+    Note that it's possible for a single message to have both
+    ``index.decryption=success`` and ``index.decryption=failure``.
+    Consider an encrypted e-mail message that contains another
+    encrypted e-mail message as an attachment -- if the outer message
+    can be decrypted, but the attached part cannot, then both
+    properties will be set on the message as a whole.
+
+    If notmuch never tried to decrypt an encrypted message during
+    indexing (which is the default, see ``index.decrypt`` in
+    **notmuch-config(1)**), then this property will not be set on that
+    message.
+
+**session-key**
+
+    When **notmuch-show(1)** or **nomtuch-reply** encounters a message
+    with an encrypted part, if notmuch finds a ``session-key``
+    property associated with the message, it will try that stashed
+    session key for decryption.
+
+    If you do not want to use any stashed session keys that might be
+    present, you should pass those programs ``--decrypt=false``.
+
+    Using a stashed session key with "notmuch show" will speed up
+    rendering of long encrypted threads.  It also allows the user to
+    destroy the secret part of any expired encryption-capable subkey
+    while still being able to read any retained messages for which
+    they have stashed the session key.  This enables truly deletable
+    e-mail, since (once the session key and asymmetric subkey are both
+    destroyed) there are no keys left that can be used to decrypt any
+    copy of the original message previously stored by an adversary.
+
+    However, access to the stashed session key for an encrypted message
+    permits full byte-for-byte reconstruction of the cleartext
+    message.  This includes attachments, cryptographic signatures, and
+    other material that cannot be reconstructed from the index alone.
+
+    See ``index.decrypt`` in **notmuch-config(1)** for more
+    details about how to set notmuch's policy on when to store session
+    keys.
+
+    The session key should be in the ASCII text form produced by
+    GnuPG.  For OpenPGP, that consists of a decimal representation of
+    the hash algorithm used (identified by number from RFC 4880,
+    e.g. 9 means AES-256) followed by a colon, followed by a
+    hexadecimal representation of the algorithm-specific key.  For
+    example, an AES-128 key might be stashed in a notmuch property as:
+    ``session-key=7:14B16AF65536C28AF209828DFE34C9E0``.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-show(1)**,
+***notmuch-search-terms(7)**
diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst
new file mode 100644 (file)
index 0000000..f7a39ce
--- /dev/null
@@ -0,0 +1,477 @@
+====================
+notmuch-search-terms
+====================
+
+SYNOPSIS
+========
+
+**notmuch** **count** [option ...] <*search-term*> ...
+
+**notmuch** **dump** [--gzip] [--format=(batch-tag|sup)] [--output=<*file*>] [--] [<*search-term*> ...]
+
+**notmuch** **reindex** [option ...] <*search-term*> ...
+
+**notmuch** **search** [option ...] <*search-term*> ...
+
+**notmuch** **show** [option ...] <*search-term*> ...
+
+**notmuch** **tag** +<*tag*> ... -<*tag*> [--] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Several notmuch commands accept a common syntax for search terms.
+
+The search terms can consist of free-form text (and quoted phrases)
+which will match all messages that contain all of the given
+terms/phrases in the body, the subject, or any of the sender or
+recipient headers.
+
+As a special case, a search string consisting of exactly a single
+asterisk ("\*") will match all messages.
+
+Search prefixes
+---------------
+
+In addition to free text, the following prefixes can be used to force
+terms to match against specific portions of an email, (where <brackets>
+indicate user-supplied values).
+
+If notmuch is built with **Xapian Field Processors** (see below) some
+of the prefixes with <regex> forms can be also used to restrict the
+results to those whose value matches a regular expression (see
+**regex(7)**) delimited with //, for example::
+
+   notmuch search 'from:"/bob@.*[.]example[.]com/"'
+
+from:<name-or-address> or from:/<regex>/
+    The **from:** prefix is used to match the name or address of
+    the sender of an email message.
+
+to:<name-or-address>
+    The **to:** prefix is used to match the names or addresses of any
+    recipient of an email message, (whether To, Cc, or Bcc).
+
+subject:<word-or-quoted-phrase> or subject:/<regex>/
+    Any term prefixed with **subject:** will match only text from the
+    subject of an email. Searching for a phrase in the subject is
+    supported by including quotation marks around the phrase,
+    immediately following **subject:**.
+
+attachment:<word>
+    The **attachment:** prefix can be used to search for specific
+    filenames (or extensions) of attachments to email messages.
+
+mimetype:<word>
+    The **mimetype:** prefix will be used to match text from the
+    content-types of MIME parts within email messages (as specified by
+    the sender).
+
+tag:<tag> or tag:/<regex>/ or is:<tag> or is:/<regex>/
+    For **tag:** and **is:** valid tag values include **inbox** and
+    **unread** by default for new messages added by **notmuch new** as
+    well as any other tag values added manually with **notmuch tag**.
+
+id:<message-id> or mid:<message-id> or mid:/<regex>/
+    For **id:** and **mid:**, message ID values are the literal
+    contents of the Message-ID: header of email messages, but without
+    the '<', '>' delimiters.
+
+thread:<thread-id>
+    The **thread:** prefix can be used with the thread ID values that
+    are generated internally by notmuch (and do not appear in email
+    messages). These thread ID values can be seen in the first column
+    of output from **notmuch search**
+
+thread:{<notmuch query>}
+    If notmuch is built with **Xapian Field Processors** (see below),
+    threads may be searched for indirectly by providing an arbitrary
+    notmuch query in **{}**. For example, the following returns
+    threads containing a message from mallory and one (not necessarily
+    the same message) with Subject containing the word "crypto".
+
+    ::
+
+       % notmuch search 'thread:"{from:mallory}" and thread:"{subject:crypto}"'
+
+    The performance of such queries can vary wildly. To understand
+    this, the user should think of the query **thread:{<something>}**
+    as expanding to all of the thread IDs which match **<something>**;
+    notmuch then performs a second search using the expanded query.
+
+path:<directory-path> or path:<directory-path>/** or path:/<regex>/
+    The **path:** prefix searches for email messages that are in
+    particular directories within the mail store. The directory must
+    be specified relative to the top-level maildir (and without the
+    leading slash). By default, **path:** matches messages in the
+    specified directory only. The "/\*\*" suffix can be used to match
+    messages in the specified directory and all its subdirectories
+    recursively. **path:""** matches messages in the root of the mail
+    store and, likewise, **path:\*\*** matches all messages.
+
+    **path:** will find a message if *any* copy of that message is in
+    the specific directory.
+
+folder:<maildir-folder> or folder:/<regex>/
+    The **folder:** prefix searches for email messages by maildir or
+    MH folder. For MH-style folders, this is equivalent to
+    **path:**. For maildir, this includes messages in the "new" and
+    "cur" subdirectories. The exact syntax for maildir folders depends
+    on your mail configuration. For maildir++, **folder:""** matches
+    the inbox folder (which is the root in maildir++), other folder
+    names always start with ".", and nested folders are separated by
+    "."s, such as **folder:.classes.topology**. For "file system"
+    maildir, the inbox is typically **folder:INBOX** and nested
+    folders are separated by slashes, such as
+    **folder:classes/topology**.
+
+    **folder:** will find a message if *any* copy of that message is
+    in the specific folder.
+
+date:<since>..<until> or date:<date>
+    The **date:** prefix can be used to restrict the results to only
+    messages within a particular time range (based on the Date:
+    header).
+
+    See **DATE AND TIME SEARCH** below for details on the range
+    expression, and supported syntax for <since> and <until> date and
+    time expressions.
+
+    The time range can also be specified using timestamps without
+    including the date prefix using a syntax of:
+
+    <initial-timestamp>..<final-timestamp>
+
+    Each timestamp is a number representing the number of seconds
+    since 1970-01-01 00:00:00 UTC. Specifying a time range this way
+    is considered legacy and predates the date prefix.
+
+lastmod:<initial-revision>..<final-revision>
+    The **lastmod:** prefix can be used to restrict the result by the
+    database revision number of when messages were last modified (tags
+    were added/removed or filenames changed). This is usually used in
+    conjunction with the ``--uuid`` argument to **notmuch search** to
+    find messages that have changed since an earlier query.
+
+query:<name>
+    The **query:** prefix allows queries to refer to previously saved
+    queries added with **notmuch-config(1)**. Named queries are only
+    available if notmuch is built with **Xapian Field Processors**
+    (see below).
+
+property:<key>=<value>
+    The **property:** prefix searches for messages with a particular
+    <key>=<value> property pair. Properties are used internally by
+    notmuch (and extensions) to add metadata to messages. A given key
+    can be present on a given message with several different values.
+    See **notmuch-properties(7)** for more details.
+
+Operators
+---------
+
+In addition to individual terms, multiple terms can be combined with
+Boolean operators (**and**, **or**, **not**, and **xor**). Each term
+in the query will be implicitly connected by a logical AND if no
+explicit operator is provided (except that terms with a common prefix
+will be implicitly combined with OR).  The shorthand '-<term>' can be
+used for 'not <term>' but unfortunately this does not work at the
+start of an expression.  Parentheses can also be used to control the
+combination of the Boolean operators, but will have to be protected
+from interpretation by the shell, (such as by putting quotation marks
+around any parenthesized expression).
+
+In addition to the standard boolean operators, Xapian provides several
+operators specific to text searching.
+
+::
+
+        notmuch search term1 NEAR term2
+
+will return results where term1 is within 10 words of term2. The
+threshold can be set like this:
+
+::
+
+        notmuch search term1 NEAR/2 term2
+
+The search
+
+::
+
+        notmuch search term1 ADJ term2
+
+will return results where term1 is within 10 words of term2, but in the
+same order as in the query. The threshold can be set the same as with
+NEAR:
+
+::
+
+        notmuch search term1 ADJ/7 term2
+
+
+Stemming
+--------
+
+**Stemming** in notmuch means that these searches
+
+::
+
+        notmuch search detailed
+        notmuch search details
+        notmuch search detail
+
+will all return identical results, because Xapian first "reduces" the
+term to the common stem (here 'detail') and then performs the search.
+
+There are two ways to turn this off: a search for a capitalized word
+will be performed unstemmed, so that one can search for "John" and not
+get results for "Johnson"; phrase searches are also unstemmed (see
+below for details).  Stemming is currently only supported for
+English. Searches for words in other languages will be performed unstemmed.
+
+Wildcards
+---------
+
+It is possible to use a trailing '\*' as a wildcard. A search for
+'wildc\*' will match 'wildcard', 'wildcat', etc.
+
+
+Boolean and Probabilistic Prefixes
+----------------------------------
+
+Xapian (and hence notmuch) prefixes are either **boolean**, supporting
+exact matches like "tag:inbox" or **probabilistic**, supporting a more
+flexible **term** based searching. Certain **special** prefixes are
+processed by notmuch in a way not strictly fitting either of Xapian's
+built in styles. The prefixes currently supported by notmuch are as
+follows.
+
+Boolean
+   **tag:**, **id:**, **thread:**, **folder:**, **path:**, **property:**
+Probabilistic
+  **to:**, **attachment:**, **mimetype:**
+Special
+   **from:**, **query:**, **subject:**
+
+Terms and phrases
+-----------------
+
+In general Xapian distinguishes between lists of terms and
+**phrases**. Phrases are indicated by double quotes (but beware you
+probably need to protect those from your shell) and insist that those
+unstemmed words occur in that order. One useful, but initially
+surprising feature is that the following are equivalent ways to write
+the same phrase.
+
+- "a list of words"
+- a-list-of-words
+- a/list/of/words
+- a.list.of.words
+
+Both parenthesised lists of terms and quoted phrases are ok with
+probabilistic prefixes such as **to:**, **from:**, and **subject:**. In particular
+
+::
+
+   subject:(pizza free)
+
+is equivalent to
+
+::
+
+   subject:pizza and subject:free
+
+Both of these will match a subject "Free Delicious Pizza" while
+
+::
+
+   subject:"pizza free"
+
+will not.
+
+Quoting
+-------
+
+Double quotes are also used by the notmuch query parser to protect
+boolean terms, regular expressions, or subqueries containing spaces or
+other special characters, e.g.
+
+::
+
+   tag:"a tag"
+
+::
+
+   folder:"/^.*/(Junk|Spam)$/"
+
+::
+
+   thread:"{from:mallory and date:2009}"
+
+As with phrases, you need to protect the double quotes from the shell
+e.g.
+
+::
+
+   % notmuch search 'folder:"/^.*/(Junk|Spam)$/"'
+   % notmuch search 'thread:"{from:mallory and date:2009}" and thread:{to:mallory}'
+
+DATE AND TIME SEARCH
+====================
+
+notmuch understands a variety of standard and natural ways of expressing
+dates and times, both in absolute terms ("2012-10-24") and in relative
+terms ("yesterday"). Any number of relative terms can be combined ("1
+hour 25 minutes") and an absolute date/time can be combined with
+relative terms to further adjust it. A non-exhaustive description of the
+syntax supported for absolute and relative terms is given below.
+
+The range expression
+--------------------
+
+date:<since>..<until>
+
+The above expression restricts the results to only messages from <since>
+to <until>, based on the Date: header.
+
+<since> and <until> can describe imprecise times, such as "yesterday".
+In this case, <since> is taken as the earliest time it could describe
+(the beginning of yesterday) and <until> is taken as the latest time it
+could describe (the end of yesterday). Similarly, date:january..february
+matches from the beginning of January to the end of February.
+
+If specifying a time range using timestamps in conjunction with the
+date prefix, each timestamp must be preceded by @ (ASCII hex 40). As
+above, each timestamp is a number representing the number of seconds
+since 1970-01-01 00:00:00 UTC. For example:
+
+    date:@<initial-timestamp>..@<final-timestamp>
+
+date:<expr>..! can be used as a shorthand for date:<expr>..<expr>. The
+expansion takes place before interpretation, and thus, for example,
+date:monday..! matches from the beginning of Monday until the end of
+Monday.
+With **Xapian Field Processor** support (see below), non-range
+date queries such as date:yesterday will work, but otherwise
+will give unexpected results; if in doubt use date:yesterday..!
+
+Currently, we do not support spaces in range expressions. You can
+replace the spaces with '\_', or (in most cases) '-', or (in some cases)
+leave the spaces out altogether. Examples in this man page use spaces
+for clarity.
+
+Open-ended ranges are supported (since Xapian 1.2.1), i.e. it's possible
+to specify date:..<until> or date:<since>.. to not limit the start or
+end time, respectively. Pre-1.2.1 Xapian does not report an error on
+open ended ranges, but it does not work as expected either.
+
+Relative date and time
+----------------------
+
+[N\|number]
+(years\|months\|weeks\|days\|hours\|hrs\|minutes\|mins\|seconds\|secs)
+[...]
+
+All refer to past, can be repeated and will be accumulated.
+
+Units can be abbreviated to any length, with the otherwise ambiguous
+single m being m for minutes and M for months.
+
+Number can also be written out one, two, ..., ten, dozen, hundred.
+Additionally, the unit may be preceded by "last" or "this" (e.g., "last
+week" or "this month").
+
+When combined with absolute date and time, the relative date and time
+specification will be relative from the specified absolute date and
+time.
+
+Examples: 5M2d, two weeks
+
+Supported absolute time formats
+-------------------------------
+
+-  H[H]:MM[:SS] [(am\|a.m.\|pm\|p.m.)]
+
+-  H[H] (am\|a.m.\|pm\|p.m.)
+
+-  HHMMSS
+
+-  now
+
+-  noon
+
+-  midnight
+
+-  Examples: 17:05, 5pm
+
+Supported absolute date formats
+-------------------------------
+
+-  YYYY-MM[-DD]
+
+-  DD-MM[-[YY]YY]
+
+-  MM-YYYY
+
+-  M[M]/D[D][/[YY]YY]
+
+-  M[M]/YYYY
+
+-  D[D].M[M][.[YY]YY]
+
+-  D[D][(st\|nd\|rd\|th)] Mon[thname] [YYYY]
+
+-  Mon[thname] D[D][(st\|nd\|rd\|th)] [YYYY]
+
+-  Wee[kday]
+
+Month names can be abbreviated at three or more characters.
+
+Weekday names can be abbreviated at three or more characters.
+
+Examples: 2012-07-31, 31-07-2012, 7/31/2012, August 3
+
+Time zones
+----------
+
+-  (+\|-)HH:MM
+
+-  (+\|-)HH[MM]
+
+Some time zone codes, e.g. UTC, EET.
+
+XAPIAN FIELD PROCESSORS
+=======================
+
+Certain optional features of the notmuch query processor rely on the
+presence of the Xapian field processor API. You can determine if your
+notmuch was built against a sufficiently recent version of Xapian by running
+
+::
+
+  % notmuch config get built_with.field_processor
+
+Currently the following features require field processor support:
+
+- non-range date queries, e.g. "date:today"
+- named queries e.g. "query:my_special_query"
+- regular expression searches, e.g. "subject:/^\\[SPAM\\]/"
+- thread subqueries, e.g. "thread:{from:bob}"
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-properties(1)**,
+***notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+***notmuch-show(1)**,
+**notmuch-tag(1)**
diff --git a/doc/notmuch-emacs.rst b/doc/notmuch-emacs.rst
new file mode 100644 (file)
index 0000000..ce2e358
--- /dev/null
@@ -0,0 +1,302 @@
+=============
+notmuch-emacs
+=============
+
+About this Manual
+=================
+
+This manual covers only the Emacs interface to Notmuch. For information
+on the command line interface, see section “Description” in the Notmuch
+Manual Pages. To save typing, we will sometimes use *notmuch* in this
+manual to refer to the Emacs interface to Notmuch. When this distinction
+is important, we’ll refer to the Emacs interface as
+*notmuch-emacs*.
+
+Notmuch-emacs is highly customizable via the the Emacs customization
+framework (or just by setting the appropriate variables). We try to
+point out relevant variables in this manual, but in order to avoid
+duplication of information, you can usually find the most detailed
+description in the variables' docstring.
+
+notmuch-hello
+=============
+
+.. index::
+   single: notmuch-hello
+   single: notmuch
+
+``notmuch-hello`` is the main entry point for Notmuch. You can start it
+with ``M-x notmuch`` or ``M-x notmuch-hello``. The startup screen looks
+something like the following. There are some hints at the bottom of the
+screen. There are three main parts to the notmuch-hello screen,
+discussed below. The **bold** text indicates buttons you can click with
+a mouse or by positioning the cursor and pressing ``<return>``
+
+|   Welcome to **notmuch** You have 52 messages.
+|
+| Saved searches: **[edit]**
+|
+|        52 **inbox**           52 **unread**
+|
+| Search: ____________________________________
+|
+| All tags: **[show]**
+|
+|       Hit \`?' for context-sensitive help in any Notmuch screen.
+|                    Customize Notmuch or this page.
+
+You can change the overall appearance of the notmuch-hello screen by
+customizing the variable :index:`notmuch-hello-sections`.
+
+
+
+notmuch-hello key bindings
+--------------------------
+
+``<tab>``
+    Move to the next widget (button or text entry field)
+
+``<backspace>``
+    Move to the previous widget.
+
+``<return>``
+    Activate the current widget.
+
+``=``
+    Refresh the buffer; mainly update the counts of messages for various
+    saved searches.
+
+``G``
+    Import mail, See :ref:`importing`
+
+``m``
+    Compose a message
+
+``s``
+    Search the notmuch database using :ref:`notmuch-search`
+
+``v``
+    Print notmuch version
+
+``q``
+    Quit
+
+.. _saved-searches:
+
+Saved Searches
+--------------
+
+Since notmuch is entirely search-based, it's often useful to organize
+mail around common searches.  To facilitate this, the first section of
+notmuch-hello presents a customizable set of saved searches.  Saved
+searches can also be accessed from anywhere in notmuch by pressing
+``j`` to access :ref:`notmuch-jump`.
+
+The saved searches default to various common searches such as
+``tag:inbox`` to access the inbox and ``tag:unread`` to access all
+unread mail, but there are several options for customization:
+
+:index:`notmuch-saved-searches`
+    The list of saved searches, including names, queries, and
+    additional per-query options.
+
+:index:`notmuch-saved-searches-sort-function`
+    This variable controls how saved searches should be sorted. A value
+    of ``nil`` displays the saved searches in the order they are stored
+    in ‘notmuch-saved-searches’.
+
+:index:`notmuch-column-control`
+    Controls the number of columns for displaying saved-searches/tags
+
+Search Box
+----------
+
+The search box lets the user enter a Notmuch query. See section
+“Description” in Notmuch Query Syntax, for more info on Notmuch query
+syntax. A history of recent searches is also displayed by default. The
+latter is controlled by the variable :index:`notmuch-hello-recent-searches-max`.
+
+Known Tags
+----------
+
+One special kind of saved search provided by default is for each
+individual tag defined in the database. This can be controlled via the
+following variables.
+
+:index:`notmuch-hello-tag-list-make-query`
+    Control how to construct a search (“virtual folder”) from a given
+    tag.
+
+:index:`notmuch-hello-hide-tags`
+    Which tags not to display at all.
+
+:index:`notmuch-column-control`
+    Controls the number of columns for displaying saved-searches/tags
+
+.. _notmuch-search:
+
+notmuch-search
+==============
+
+``notmuch-search-mode`` is used to display the results from executing
+a query via ``notmuch-search``. The syntax for these queries is the
+the same as :ref:`saved-searches`. For details of this syntax see
+info:notmuch-search-terms
+
+By default the output approximates that of the command line See section
+“Description” in notmuch search command.
+
+The main purpose of the ``notmuch-search-mode`` buffer is to act as a
+menu of results that the user can explore further by pressing
+``<return>`` on the appropriate line.
+
+``n,C-n,<down>``
+    Move to next line
+
+``p,C-p,<up>``
+    Move to previous line
+
+``<return>``
+    Open thread on current line in :ref:`notmuch-show` mode
+
+``?``
+    Display full set of key bindings
+
+The presentation of results can be controlled by the following
+variables.
+
+:index:`notmuch-search-result-format`
+    Control how each thread of messages is presented in the
+    ``notmuch-show-mode`` buffer
+
+:index:`notmuch-search-oldest-first`
+    Display the oldest threads at the top of the buffer
+
+.. _notmuch-show:
+
+notmuch-show
+============
+
+``notmuch-show-mode`` is used to display a single thread of email from
+your email archives.
+
+By default, various components of email messages, (citations,
+signatures, already-read messages), are hidden. You can make
+these parts visible by clicking with the mouse button or by
+pressing RET after positioning the cursor on a hidden part.
+
+``<space>``
+    Scroll the current message (if necessary),
+    advance to the next message, or advance to the next thread (if
+    already on the last message of a thread).
+
+``N``
+    Move to next message
+
+``P``
+    Move to previous message (or start of current message)
+
+``n``
+    Move to next matching message
+
+``p``
+    Move to previous matching message
+
+``+,-``
+    Add or remove arbitrary tags from the current message.
+
+``?``
+    Display full set of key bindings
+
+.. _notmuch-tree:
+
+notmuch-tree
+============
+
+``notmuch-tree-mode`` displays the results of a "notmuch tree" of your
+email archives. Each line in the buffer represents a single
+message giving the relative date, the author, subject, and any
+tags.
+
+``<return>``
+   Displays that message.
+
+``N``
+    Move to next message
+
+``P``
+    Move to previous message
+
+``n``
+    Move to next matching message
+
+``p``
+    Move to previous matching message
+
+``?``
+    Display full set of key bindings
+
+Global key bindings
+===================
+
+Several features are accessible from anywhere in notmuch through the
+following key bindings:
+
+``j``
+    Jump to saved searches using :ref:`notmuch-jump`.
+
+``k``
+    Tagging operations using :ref:`notmuch-tag-jump`
+
+.. _notmuch-jump:
+
+notmuch-jump
+------------
+
+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
+example, in the default configuration ``j i`` jumps immediately to the
+inbox search.  When you press ``j``, notmuch-jump shows the saved
+searches and their shortcut keys in the mini-buffer.
+
+.. _notmuch-tag-jump:
+
+notmuch-tag-jump
+----------------
+
+Tagging operations configured through ``notmuch-tagging-keys`` can
+be accessed via :kbd:`k` in :ref:`notmuch-show`,
+:ref:`notmuch-search` and :ref:`notmuch-tree`.  With a
+prefix (:kbd:`C-u k`), notmuch displays a menu of the reverses of the
+operations specified in ``notmuch-tagging-keys``; i.e. each
+``+tag`` is replaced by ``-tag`` and vice versa.
+
+:index:`notmuch-tagging-keys`
+
+   A list of keys and corresponding tagging operations.
+
+Configuration
+=============
+
+.. _importing:
+
+Importing Mail
+--------------
+
+:index:`notmuch-poll`
+
+:index:`notmuch-poll-script`
+
+Init File
+---------
+
+When Notmuch is loaded, it will read the ``notmuch-init-file``
+(``~/.emacs.d/notmuch-config`` by default) file. This is normal Emacs Lisp
+file and can be used to avoid cluttering your ``~/.emacs`` with Notmuch
+stuff. If the file with ``.elc``, ``.elc.gz``, ``.el`` or ``.el.gz``
+suffix exist it will be read instead (just one of these, chosen in this
+order). Most often users create ``~/.emacs.d/notmuch-config.el`` and just
+work with it. If Emacs was invoked with the ``-q`` or ``--no-init-file``
+options, ``notmuch-init-file`` is not read.
diff --git a/elpa-notmuch.elpa b/elpa-notmuch.elpa
deleted file mode 100644 (file)
index a070ae5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-emacs/*.el
-debian/tmp/usr/share/info/*
diff --git a/elpa-test b/elpa-test
deleted file mode 100644 (file)
index e3346c1..0000000
--- a/elpa-test
+++ /dev/null
@@ -1 +0,0 @@
-disable=true
diff --git a/emacs/.gitignore b/emacs/.gitignore
new file mode 100644 (file)
index 0000000..fbf8dde
--- /dev/null
@@ -0,0 +1,4 @@
+/.eldeps*
+/*.elc
+/notmuch-version.el
+/notmuch-pkg.el
diff --git a/emacs/Makefile b/emacs/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/emacs/Makefile.local b/emacs/Makefile.local
new file mode 100644 (file)
index 0000000..5e4ae1b
--- /dev/null
@@ -0,0 +1,123 @@
+# -*- makefile -*-
+
+dir := emacs
+emacs_sources := \
+       $(dir)/notmuch-lib.el \
+       $(dir)/notmuch-compat.el \
+       $(dir)/notmuch-parser.el \
+       $(dir)/notmuch.el \
+       $(dir)/notmuch-query.el \
+       $(dir)/notmuch-show.el \
+       $(dir)/notmuch-tree.el \
+       $(dir)/notmuch-wash.el \
+       $(dir)/notmuch-hello.el \
+       $(dir)/notmuch-mua.el \
+       $(dir)/notmuch-address.el \
+       $(dir)/notmuch-maildir-fcc.el \
+       $(dir)/notmuch-message.el \
+       $(dir)/notmuch-crypto.el \
+       $(dir)/notmuch-tag.el \
+       $(dir)/coolj.el \
+       $(dir)/notmuch-print.el \
+       $(dir)/notmuch-version.el \
+       $(dir)/notmuch-jump.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
+       @sed -e 's/%AG%/Generated file (from $(<F)) -- do not edit!/' \
+            -e 's/%VERSION%/"$(VERSION)"/' $< > $@
+
+$(dir)/notmuch-pkg.el: $(srcdir)/$(dir)/notmuch-pkg.el.tmpl
+       @sed -e 's/%AG%/Generated file (from $(<F)) -- do not edit!/' \
+            -e 's/%VERSION%/"$(ELPA_VERSION)"/' $< > $@
+
+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
+
+emacs_bytecode = $(emacs_sources:.el=.elc)
+
+# Because of defmacro's and defsubst's, we have to account for load
+# dependencies between Elisp files when byte compiling.  Otherwise,
+# the byte compiler may load an old .elc file when processing a
+# "require" or we may fail to rebuild a .elc that depended on a macro
+# from an updated file.
+ifeq ($(HAVE_EMACS),1)
+$(dir)/.eldeps: $(dir)/Makefile.local $(dir)/make-deps.el $(emacs_sources)
+       $(call quiet,EMACS) --directory emacs -batch -l make-deps.el \
+               -f batch-make-deps $(emacs_sources) > $@.tmp && \
+               mv $@.tmp $@
+# We could include .eldeps directly, but that would cause a make
+# restart whenever any .el file was modified, even if dependencies
+# didn't change, because the mtime of .eldeps will change.  Instead,
+# we include a second file, .eldeps.x, which we ensure always has the
+# same content as .eldeps, but its mtime only changes when dependency
+# information changes, in which case a make restart is necessary
+# anyway.
+$(dir)/.eldeps.x: $(dir)/.eldeps
+       @cmp -s $^ $@ || cp $^ $@
+-include $(dir)/.eldeps.x
+
+# Add the one dependency make-deps.el does not have visibility to.
+$(dir)/notmuch-lib.elc: $(dir)/notmuch-version.elc
+
+endif
+CLEAN+=$(dir)/.eldeps $(dir)/.eldeps.tmp $(dir)/.eldeps.x
+
+ifeq ($(HAVE_EMACS),1)
+%.elc: %.el $(global_deps)
+       $(call quiet,EMACS) --directory emacs -batch -f batch-byte-compile $<
+endif
+
+elpa: $(ELPA_FILE)
+
+ELPA_DIR=.elpa-build/notmuch-${ELPA_VERSION}
+notmuch-emacs-%.tar: ${elpa_sources} build-info
+       mkdir -p .elpa-build/notmuch-${ELPA_VERSION}
+       cp ${elpa_sources} ${ELPA_DIR}
+ifeq ($(HAVE_SPHINX)$(HAVE_MAKEINFO)$(HAVE_INSTALL_INFO),111)
+       cp ${INFO_INFO_FILES} ${ELPA_DIR}
+       for file in ${INFO_INFO_FILES}; do install-info $$file ${ELPA_DIR}/dir; done
+endif
+       tar -C .elpa-build -cf $@ notmuch-${ELPA_VERSION}
+       rm -r .elpa-build
+
+ifeq ($(WITH_EMACS),1)
+ifeq ($(HAVE_EMACS),1)
+all: $(emacs_bytecode)
+install-emacs: $(emacs_bytecode)
+endif
+
+install: install-emacs
+endif
+
+.PHONY: install-emacs
+install-emacs: $(emacs_sources) $(emacs_images)
+       mkdir -p "$(DESTDIR)$(emacslispdir)"
+       install -m0644 $(emacs_sources) "$(DESTDIR)$(emacslispdir)"
+ifeq ($(HAVE_EMACS),1)
+       install -m0644 $(emacs_bytecode) "$(DESTDIR)$(emacslispdir)"
+endif
+       mkdir -p "$(DESTDIR)$(emacsetcdir)"
+       install -m0644 $(emacs_images) "$(DESTDIR)$(emacsetcdir)"
+       mkdir -p "$(DESTDIR)$(prefix)/bin/"
+ifeq ($(HAVE_BASH),1)
+       sed "1s|^#!.*|#! $(BASH_ABSOLUTE)|" < $(emacs_mua) > $(DESTDIR)$(prefix)/bin/notmuch-emacs-mua
+       chmod 755 $(DESTDIR)$(prefix)/bin/notmuch-emacs-mua
+endif
+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
diff --git a/emacs/coolj.el b/emacs/coolj.el
new file mode 100644 (file)
index 0000000..350d537
--- /dev/null
@@ -0,0 +1,147 @@
+;;; coolj.el --- automatically wrap long lines  -*- coding:utf-8 -*-
+
+;; Copyright (C) 2000, 2001, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+
+;; Authors:    Kai Grossjohann <Kai.Grossjohann@CS.Uni-Dortmund.DE>
+;;             Alex Schroeder <alex@gnu.org>
+;;             Chong Yidong <cyd@stupidchicken.com>
+;; Maintainer: David Edmondson <dme@dme.org>
+;; Keywords: convenience, wp
+
+;; This file is not part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; This is a simple derivative of some functionality from
+;;; `longlines.el'. The key difference is that this version will
+;;; insert a prefix at the head of each wrapped line. The prefix is
+;;; calculated from the originating long line.
+
+;;; No minor-mode is provided, the caller is expected to call
+;;; `coolj-wrap-region' to wrap the region of interest.
+
+;;; Code:
+
+(defgroup coolj nil
+  "Wrapping of long lines with prefix."
+  :group 'fill)
+
+(defcustom coolj-wrap-follows-window-size t
+  "Non-nil means wrap text to the window size.
+Otherwise respect `fill-column'."
+  :group 'coolj
+  :type 'boolean)
+
+(defcustom coolj-line-prefix-regexp "^\\(>+ \\)*"
+  "Regular expression that matches line prefixes."
+  :group 'coolj
+  :type 'regexp)
+
+(defvar coolj-wrap-point nil)
+
+(make-variable-buffer-local 'coolj-wrap-point)
+
+(defun coolj-determine-prefix ()
+  "Determine the prefix for the current line."
+  (save-excursion
+    (beginning-of-line)
+    (if (re-search-forward coolj-line-prefix-regexp nil t)
+       (buffer-substring (match-beginning 0) (match-end 0))
+      "")))
+
+(defun coolj-wrap-buffer ()
+  "Wrap the current buffer."
+  (coolj-wrap-region (point-min) (point-max)))
+
+(defun coolj-wrap-region (beg end)
+  "Wrap each successive line, starting with the line before BEG.
+Stop when we reach lines after END that don't need wrapping, or the
+end of the buffer."
+  (setq fill-column (if coolj-wrap-follows-window-size
+                       (window-width)
+                     fill-column))
+  (let ((mod (buffer-modified-p)))
+    (setq coolj-wrap-point (point))
+    (goto-char beg)
+    (forward-line -1)
+    ;; Two successful coolj-wrap-line's in a row mean successive
+    ;; lines don't need wrapping.
+    (while (null (and (coolj-wrap-line)
+                     (or (eobp)
+                         (and (>= (point) end)
+                              (coolj-wrap-line))))))
+    (goto-char coolj-wrap-point)
+    (set-buffer-modified-p mod)))
+
+(defun coolj-wrap-line ()
+  "If the current line needs to be wrapped, wrap it and return nil.
+If wrapping is performed, point remains on the line.  If the line does
+not need to be wrapped, move point to the next line and return t."
+  (let ((prefix (coolj-determine-prefix)))
+    (if (coolj-set-breakpoint prefix)
+       (progn
+         (insert-before-markers ?\n)
+         (backward-char 1)
+         (delete-char -1)
+         (forward-char 1)
+         (insert-before-markers prefix)
+         nil)
+      (forward-line 1)
+      t)))
+
+(defun coolj-set-breakpoint (prefix)
+  "Place point where we should break the current line, and return t.
+If the line should not be broken, return nil; point remains on the
+line."
+  (move-to-column fill-column)
+  (if (and (re-search-forward "[^ ]" (line-end-position) 1)
+           (> (current-column) fill-column))
+      ;; This line is too long.  Can we break it?
+      (or (coolj-find-break-backward prefix)
+          (progn (move-to-column fill-column)
+                 (coolj-find-break-forward)))))
+
+(defun coolj-find-break-backward (prefix)
+  "Move point backward to the first available breakpoint and return t.
+If no breakpoint is found, return nil."
+  (let ((end-of-prefix (+ (line-beginning-position) (length prefix))))
+    (and (search-backward " " end-of-prefix 1)
+        (save-excursion
+          (skip-chars-backward " " end-of-prefix)
+          (null (bolp)))
+        (progn (forward-char 1)
+               (if (and fill-nobreak-predicate
+                        (run-hook-with-args-until-success
+                         'fill-nobreak-predicate))
+                   (progn (skip-chars-backward " " end-of-prefix)
+                          (coolj-find-break-backward prefix))
+                 t)))))
+
+(defun coolj-find-break-forward ()
+  "Move point forward to the first available breakpoint and return t.
+If no break point is found, return nil."
+  (and (search-forward " " (line-end-position) 1)
+       (progn (skip-chars-forward " " (line-end-position))
+              (null (eolp)))
+       (if (and fill-nobreak-predicate
+                (run-hook-with-args-until-success
+                 'fill-nobreak-predicate))
+           (coolj-find-break-forward)
+         t)))
+
+(provide 'coolj)
+
+;;; coolj.el ends here
diff --git a/emacs/make-deps.el b/emacs/make-deps.el
new file mode 100644 (file)
index 0000000..5b6db69
--- /dev/null
@@ -0,0 +1,70 @@
+;; make-deps.el --- compute make dependencies for Elisp sources
+;;
+;; 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 <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Austin Clements <aclements@csail.mit.edu>
+
+;;; Code:
+
+(defun batch-make-deps ()
+  "Invoke `make-deps' for each file on the command line."
+
+  (setq debug-on-error t)
+  (dolist (file command-line-args-left)
+    (let ((default-directory command-line-default-directory))
+      (find-file-literally file))
+    (make-deps command-line-default-directory))
+  (kill-emacs))
+
+(defun make-deps (&optional dir)
+  "Print make dependencies for the current buffer.
+
+This prints make dependencies to `standard-output' based on the
+top-level `require' expressions in the current buffer.  Paths in
+rules will be given relative to DIR, or `default-directory'."
+
+  (setq dir (or dir default-directory))
+  (save-excursion
+    (goto-char (point-min))
+    (condition-case nil
+       (while t
+         (let ((form (read (current-buffer))))
+           ;; Is it a (require 'x) form?
+           (when (and (listp form) (= (length form) 2)
+                      (eq (car form) 'require)
+                      (listp (cadr form)) (= (length (cadr form)) 2)
+                      (eq (car (cadr form)) 'quote)
+                      (symbolp (cadr (cadr form))))
+             ;; Find the required library
+             (let* ((name (cadr (cadr form)))
+                    (fname (locate-library (symbol-name name))))
+               ;; Is this file and the library in the same directory?
+               ;; If not, assume it's a system library and don't
+               ;; bother depending on it.
+               (when (and fname
+                          (string= (file-name-directory (buffer-file-name))
+                                   (file-name-directory fname)))
+                 ;; Print the dependency
+                 (princ (format "%s.elc: %s.elc\n"
+                                (file-name-sans-extension
+                                 (file-relative-name (buffer-file-name) dir))
+                                (file-name-sans-extension
+                                 (file-relative-name fname dir)))))))))
+      (end-of-file nil))))
+
+;;; make-deps.el ends here
diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el
new file mode 100644 (file)
index 0000000..64887a4
--- /dev/null
@@ -0,0 +1,446 @@
+;;; notmuch-address.el --- address completion with notmuch
+;;
+;; Copyright © David Edmondson
+;;
+;; 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: David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(require 'message)
+(require 'notmuch-parser)
+(require 'notmuch-lib)
+(require 'notmuch-company)
+;;
+(declare-function company-manual-begin "company")
+
+(defvar notmuch-address-last-harvest 0
+  "Time of last address harvest")
+
+(defvar notmuch-address-completions (make-hash-table :test 'equal)
+  "Hash of email addresses for completion during email composition.
+  This variable is set by calling `notmuch-address-harvest'.")
+
+(defvar notmuch-address-full-harvest-finished nil
+  "t indicates that full completion address harvesting has been
+finished. 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.
+
+If it is a string then that string should be an external program
+which must take a single argument (searched string) and output a
+list of completion candidates, one per line.
+
+Alternatively, it can be the symbol 'internal, in which case
+internal completion is used; the variable
+`notmuch-address-internal-completion` can be used to customize
+this case.
+
+Finally, if this variable is nil then address completion is
+disabled."
+  :type '(radio
+         (const :tag "Use internal address completion" internal)
+         (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)
+  "Determines how internal address completion generates candidates.
+
+This should be a list of the form '(DIRECTION FILTER), where
+ DIRECTION is either sent or received and specifies whether the
+ candidates are searched in messages sent by the user or received
+ by the user (note received by is much faster), and FILTER is
+ either nil or a filter-string, such as \"date:1y..\" to append
+ to the query."
+  :type '(list :tag "Use internal address completion"
+              (radio
+               :tag "Base completion on messages you have"
+               :value sent
+               (const :tag "sent (more accurate)" sent)
+               (const :tag "received (faster)" received))
+              (radio :tag "Filter messages used for completion"
+                     (const :tag "Use all messages" nil)
+                     (string :tag "Filter query")))
+  ;; We override set so that we can clear the cache when this changes
+  :set (lambda (symbol value)
+        (set-default symbol value)
+        (setq notmuch-address-last-harvest 0)
+        (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
+  "The function to select address from given list. The function is
+called with PROMPT, COLLECTION, and INITIAL-INPUT as arguments
+(subset of what `completing-read' can be called with).
+While executed the value of `completion-ignore-case' is t.
+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)"
+  (completing-read
+   prompt collection nil nil initial-input 'notmuch-address-history))
+
+(defvar notmuch-address-completion-headers-regexp
+  "^\\(Resent-\\)?\\(To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\):")
+
+(defvar notmuch-address-history nil)
+
+(defun notmuch-address-message-insinuate ()
+  (message "calling notmuch-address-message-insinuate is no longer needed"))
+
+(defcustom notmuch-address-use-company t
+  "If available, use company mode for address completion"
+  :type 'boolean
+  :group 'notmuch-send
+  :group 'notmuch-address)
+
+(defun notmuch-address-setup ()
+  (let* ((setup-company (and notmuch-address-use-company
+                          (require 'company nil t)))
+        (pair (cons notmuch-address-completion-headers-regexp
+                      #'notmuch-address-expand-name)))
+      (when setup-company
+       (notmuch-company-setup))
+      (unless (member pair message-completion-alist)
+       (setq message-completion-alist
+             (push pair message-completion-alist)))))
+
+(defun notmuch-address-toggle-internal-completion ()
+  "Toggle use of internal completion for current buffer.
+
+This overrides the global setting for address completion and
+toggles the setting in this buffer."
+  (interactive)
+  (if (local-variable-p 'notmuch-address-command)
+      (kill-local-variable 'notmuch-address-command)
+    (notmuch-setq-local notmuch-address-command 'internal))
+  (if (boundp 'company-idle-delay)
+      (if (local-variable-p 'company-idle-delay)
+         (kill-local-variable 'company-idle-delay)
+       (notmuch-setq-local company-idle-delay nil))))
+
+(defun notmuch-address-matching (substring)
+  "Returns a list of completion candidates matching SUBSTRING.
+The candidates are taken from `notmuch-address-completions'."
+  (let ((candidates)
+       (re (regexp-quote substring)))
+    (maphash (lambda (key val)
+              (when (string-match re key)
+                (push key candidates)))
+            notmuch-address-completions)
+    candidates))
+
+(defun notmuch-address-options (original)
+  "Returns a list of completion candidates. Uses either
+elisp-based implementation or older implementation requiring
+external commands."
+  (cond
+   ((eq notmuch-address-command 'internal)
+    (unless (notmuch-address--harvest-ready)
+      ;; First, run quick synchronous harvest based on what the user
+      ;; entered so far
+      (notmuch-address-harvest original t))
+    (prog1 (notmuch-address-matching original)
+      ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+      (notmuch-address-harvest-trigger)))
+   (t
+    (process-lines notmuch-address-command original))))
+
+(defun notmuch-address-expand-name ()
+  (cond
+   ((and (eq notmuch-address-command 'internal)
+        notmuch-address-use-company
+        (bound-and-true-p company-mode))
+    (company-manual-begin))
+   (notmuch-address-command
+    (let* ((end (point))
+          (beg (save-excursion
+                 (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
+                 (goto-char (match-end 0))
+                 (point)))
+          (orig (buffer-substring-no-properties beg end))
+          (completion-ignore-case t)
+          (options (with-temp-message "Looking for completion candidates..."
+                     (notmuch-address-options orig)))
+          (num-options (length options))
+          (chosen (cond
+                   ((eq num-options 0)
+                    nil)
+                   ((eq num-options 1)
+                    (car options))
+                   (t
+                    (funcall notmuch-address-selection-function
+                             (format "Address (%s matches): " num-options)
+                             ;; 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)
+           (run-hook-with-args 'notmuch-address-post-completion-functions chosen))
+       (message "No matches.")
+       (ding))))
+   (t nil)))
+
+;; Copied from `w3m-which-command'.
+(defun notmuch-address-locate-command (command)
+  "Return non-nil if `command' is an executable either on
+`exec-path' or an absolute pathname."
+  (when (stringp command)
+    (if (and (file-name-absolute-p command)
+            (file-executable-p command))
+       command
+      (setq command (file-name-nondirectory command))
+      (catch 'found-command
+       (let (bin)
+         (dolist (dir exec-path)
+           (setq bin (expand-file-name command dir))
+           (when (or (and (file-executable-p bin)
+                          (not (file-directory-p bin)))
+                     (and (file-executable-p (setq bin (concat bin ".exe")))
+                          (not (file-directory-p bin))))
+             (throw 'found-command bin))))))))
+
+(defun notmuch-address-harvest-addr (result)
+  (let ((name-addr (plist-get result :name-addr)))
+    (puthash name-addr t notmuch-address-completions)))
+
+(defun notmuch-address-harvest-handle-result (obj)
+  (notmuch-address-harvest-addr obj))
+
+(defun notmuch-address-harvest-filter (proc string)
+  (when (buffer-live-p (process-buffer proc))
+    (with-current-buffer (process-buffer proc)
+      (save-excursion
+       (goto-char (point-max))
+       (insert string))
+      (notmuch-sexp-parse-partial-list
+       'notmuch-address-harvest-handle-result (process-buffer proc)))))
+
+(defvar notmuch-address-harvest-procs '(nil . nil)
+  "The currently running harvests.
+
+The car is a partial harvest, and the cdr is a full harvest")
+
+(defun notmuch-address-harvest (&optional addr-prefix synchronous callback)
+  "Collect addresses completion candidates.
+
+It queries the notmuch database for messages sent/received (as
+configured with `notmuch-address-command`) by the user, collects
+destination/source addresses from those messages and stores them
+in `notmuch-address-completions'.
+
+If ADDR-PREFIX is not nil, only messages with to/from addresses
+matching ADDR-PREFIX*' are queried.
+
+Address harvesting may take some time so the address collection runs
+asynchronously unless SYNCHRONOUS is t. In case of asynchronous
+execution, CALLBACK is called when harvesting finishes."
+
+  (let* ((sent (eq (car notmuch-address-internal-completion) 'sent))
+        (config-query (cadr notmuch-address-internal-completion))
+        (prefix-query (when addr-prefix
+                        (format "%s:%s*" (if sent "to" "from") addr-prefix)))
+        (from-or-to-me-query
+         (mapconcat (lambda (x)
+                      (concat (if sent "from:" "to:") x))
+                    (notmuch-user-emails) " or "))
+        (query (if (or prefix-query config-query)
+                   (concat (format "(%s)" from-or-to-me-query)
+                           (when prefix-query
+                             (format " and (%s)" prefix-query))
+                           (when config-query
+                             (format " and (%s)" config-query)))
+                 from-or-to-me-query))
+        (args `("address" "--format=sexp" "--format-version=4"
+                ,(if sent "--output=recipients" "--output=sender")
+                "--deduplicate=address"
+                ,query)))
+    (if synchronous
+       (mapc #'notmuch-address-harvest-addr
+                                  (apply 'notmuch-call-notmuch-sexp args))
+      ;; Asynchronous
+      (let* ((current-proc (if addr-prefix
+                              (car notmuch-address-harvest-procs)
+                            (cdr notmuch-address-harvest-procs)))
+            (proc-name (format "notmuch-address-%s-harvest"
+                               (if addr-prefix "partial" "full")))
+            (proc-buf (concat " *" proc-name "*")))
+       ;; Kill any existing process
+       (when current-proc
+         (kill-buffer (process-buffer current-proc))) ; this also kills the process
+
+       (setq current-proc
+             (apply 'notmuch-start-notmuch proc-name proc-buf
+                    callback                           ; process sentinel
+                    args))
+       (set-process-filter current-proc 'notmuch-address-harvest-filter)
+       (set-process-query-on-exit-flag current-proc nil)
+       (if addr-prefix
+           (setcar notmuch-address-harvest-procs current-proc)
+         (setcdr notmuch-address-harvest-procs current-proc)))))
+  ;; 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)
+      (setq notmuch-address-last-harvest now)
+      (notmuch-address-harvest nil nil
+                              (lambda (proc event)
+                                ;; If harvest fails, we want to try
+                                ;; again when the trigger is next
+                                ;; called
+                                (if (string= event "finished\n")
+                                    (progn
+                                      (notmuch-address--save-address-hash)
+                                      (setq notmuch-address-full-harvest-finished t))
+                                  (setq notmuch-address-last-harvest 0)))))))
+
+;;
+
+(defun notmuch-address-from-minibuffer (prompt)
+  (if (not notmuch-address-command)
+      (read-string prompt)
+    (let ((rmap (copy-keymap minibuffer-local-map))
+         (omap minibuffer-local-map))
+      ;; Configure TAB to start completion when executing read-string.
+      ;; "Original" minibuffer keymap is restored just before calling
+      ;; notmuch-address-expand-name as it may also use minibuffer-local-map
+      ;; (completing-read probably does not but if something else is used there).
+      (define-key rmap (kbd "TAB") (lambda ()
+                                    (interactive)
+                                    (let ((enable-recursive-minibuffers t)
+                                          (minibuffer-local-map omap))
+                                      (notmuch-address-expand-name))))
+      (let ((minibuffer-local-map rmap))
+       (read-string prompt)))))
+
+;;
+
+(provide 'notmuch-address)
+
+;;; notmuch-address.el ends here
diff --git a/emacs/notmuch-company.el b/emacs/notmuch-company.el
new file mode 100644 (file)
index 0000000..3e12e7a
--- /dev/null
@@ -0,0 +1,98 @@
+;;; notmuch-company.el --- Mail address completion for notmuch via company-mode  -*- lexical-binding: t -*-
+
+;; Authors: Trevor Jim <tjim@mac.com>
+;;         Michal Sojka <sojkam1@fel.cvut.cz>
+;;
+;; Keywords: mail, completion
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; To enable this, install company mode (https://company-mode.github.io/)
+;;
+;; NB company-minimum-prefix-length defaults to 3 so you don't get
+;; completion unless you type 3 characters
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'notmuch-lib)
+
+(defvar notmuch-company-last-prefix nil)
+(make-variable-buffer-local 'notmuch-company-last-prefix)
+(declare-function company-begin-backend "company")
+(declare-function company-grab "company")
+(declare-function company-mode "company")
+(declare-function company-manual-begin "company")
+(defvar company-backends)
+(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")
+(declare-function notmuch-address--harvest-ready "notmuch-address")
+(defvar notmuch-address-completion-headers-regexp)
+(defvar notmuch-address-command)
+
+;;;###autoload
+(defun notmuch-company-setup ()
+  (company-mode)
+  (make-local-variable 'company-backends)
+  (setq company-backends '(notmuch-company))
+  ;; Disable automatic company completion unless an internal
+  ;; completion method is configured. Company completion (using
+  ;; internal completion) can still be accessed via standard company
+  ;; functions, e.g., company-complete.
+  (unless (eq notmuch-address-command 'internal)
+    (notmuch-setq-local company-idle-delay nil)))
+
+;;;###autoload
+(defun notmuch-company (command &optional arg &rest _ignore)
+  "`company-mode' completion back-end for `notmuch'."
+  (interactive (list 'interactive))
+  (require 'company)
+  (let ((case-fold-search t)
+       (completion-ignore-case t))
+    (case command
+      (interactive (company-begin-backend 'notmuch-company))
+      (prefix (and (derived-mode-p 'message-mode)
+                  (looking-back (concat notmuch-address-completion-headers-regexp ".*")
+                                (line-beginning-position))
+                  (setq notmuch-company-last-prefix (company-grab "[:,][ \t]*\\(.*\\)" 1 (point-at-bol)))))
+      (candidates (cond
+                  ((notmuch-address--harvest-ready)
+                   ;; Update harvested addressed from time to time
+                   (notmuch-address-harvest-trigger)
+                   (notmuch-address-matching arg))
+                  (t
+                   (cons :async
+                         (lambda (callback)
+                           ;; First run quick asynchronous harvest based on what the user entered so far
+                           (notmuch-address-harvest
+                            arg nil
+                            (lambda (_proc _event)
+                              (funcall callback (notmuch-address-matching arg))
+                              ;; Then start the (potentially long-running) full asynchronous harvest if necessary
+                              (notmuch-address-harvest-trigger))))))))
+      (match (if (string-match notmuch-company-last-prefix arg)
+                (match-end 0)
+              0))
+      (post-completion (run-hook-with-args 'notmuch-address-post-completion-functions arg))
+      (no-cache t))))
+
+
+(provide 'notmuch-company)
+
+;;; notmuch-company.el ends here
diff --git a/emacs/notmuch-compat.el b/emacs/notmuch-compat.el
new file mode 100644 (file)
index 0000000..2cedd39
--- /dev/null
@@ -0,0 +1,93 @@
+;; Compatibility functions for earlier versions of emacs
+
+;; The functions in this file are copied from more modern versions of
+;; emacs and are Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2017
+;; Free Software Foundation, Inc.
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; emacs master has a bugfix for folding long headers when sending
+;; messages. Include the fix for earlier versions of emacs. To avoid
+;; interfering with gnus we only run the hook when called from
+;; notmuch-message-mode.
+
+(declare-function mail-header-fold-field "mail-parse" nil)
+
+(defun notmuch-message--fold-long-headers ()
+  (when (eq major-mode 'notmuch-message-mode)
+    (goto-char (point-min))
+    (while (not (eobp))
+      (when (and (looking-at "[^:]+:")
+                (> (- (line-end-position) (point)) 998))
+       (mail-header-fold-field))
+      (forward-line 1))))
+
+(unless (fboundp 'message--fold-long-headers)
+  (add-hook 'message-header-hook 'notmuch-message--fold-long-headers))
+
+(if (fboundp 'setq-local)
+    (defalias 'notmuch-setq-local 'setq-local)
+  (defmacro notmuch-setq-local (var val)
+    "Set variable VAR to value VAL in current buffer.
+
+Backport of setq-local for emacs without setq-local (pre 24.3)."
+    `(set (make-local-variable ',var) ,val)))
+
+(if (fboundp 'read-char-choice)
+    (defalias 'notmuch-read-char-choice 'read-char-choice)
+  (defun notmuch-read-char-choice (prompt chars &optional inhibit-keyboard-quit)
+  "Read and return one of CHARS, prompting for PROMPT.
+Any input that is not one of CHARS is ignored.
+
+If optional argument INHIBIT-KEYBOARD-QUIT is non-nil, ignore
+keyboard-quit events while waiting for a valid input.
+
+This is an exact copy of this function from emacs 24 for use on
+emacs 23, except with the one emacs 24 only function it calls
+inlined."
+  (unless (consp chars)
+    (error "Called `read-char-choice' without valid char choices"))
+  (let (char done show-help (helpbuf " *Char Help*"))
+    (let ((cursor-in-echo-area t)
+          (executing-kbd-macro executing-kbd-macro)
+         (esc-flag nil))
+      (save-window-excursion         ; in case we call help-form-show
+       (while (not done)
+         (unless (get-text-property 0 'face prompt)
+           (setq prompt (propertize prompt 'face 'minibuffer-prompt)))
+         (setq char (let ((inhibit-quit inhibit-keyboard-quit))
+                      (read-key prompt)))
+         (and show-help (buffer-live-p (get-buffer helpbuf))
+              (kill-buffer helpbuf))
+         (cond
+          ((not (numberp char)))
+          ;; If caller has set help-form, that's enough.
+          ;; They don't explicitly have to add help-char to chars.
+          ((and help-form
+                (eq char help-char)
+                (setq show-help t)
+                ;; This is an inlined copy of help-form-show as that
+                ;; was introduced in emacs 24 too.
+                (let ((msg (eval help-form)))
+                  (if (stringp msg)
+                      (with-output-to-temp-buffer " *Char Help*"
+                        (princ msg))))))
+          ((memq char chars)
+           (setq done t))
+          ((and executing-kbd-macro (= char -1))
+           ;; read-event returns -1 if we are in a kbd macro and
+           ;; there are no more events in the macro.  Attempt to
+           ;; get an event interactively.
+           (setq executing-kbd-macro nil))
+          ((not inhibit-keyboard-quit)
+           (cond
+            ((and (null esc-flag) (eq char ?\e))
+             (setq esc-flag t))
+            ((memq char '(?\C-g ?\e))
+             (keyboard-quit))))))))
+    ;; Display the question with the answer.  But without cursor-in-echo-area.
+    (message "%s%s" prompt (char-to-string char))
+    char)))
+
+;; End of compatibility functions
+
+(provide 'notmuch-compat)
diff --git a/emacs/notmuch-crypto.el b/emacs/notmuch-crypto.el
new file mode 100644 (file)
index 0000000..353f721
--- /dev/null
@@ -0,0 +1,187 @@
+;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata.
+;;
+;; Copyright © Jameson Rollins
+;;
+;; 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: Jameson Rollins <jrollins@finestructure.net>
+
+;;; Code:
+
+(require 'epg)
+(require 'notmuch-lib)
+
+(defcustom notmuch-crypto-process-mime t
+  "Should cryptographic MIME parts be processed?
+
+If this variable is non-nil signatures in multipart/signed
+messages will be verified and multipart/encrypted parts will be
+decrypted.  The result of the crypto operation will be displayed
+in a specially colored header button at the top of the processed
+part.  Signed parts will have variously colored headers depending
+on the success or failure of the verification process and on the
+validity of user ID of the signer.
+
+The effect of setting this variable can be seen temporarily by
+providing a prefix when viewing a signed or encrypted message, or
+by providing a prefix when reloading the message in notmuch-show
+mode."
+  :type 'boolean
+  :package-version '(notmuch . "0.25")
+  :group 'notmuch-crypto)
+
+(defface notmuch-crypto-part-header
+  '((((class color)
+      (background dark))
+     (:foreground "LightBlue1"))
+    (((class color)
+      (background light))
+     (:foreground "blue")))
+  "Face used for crypto parts headers."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(defface notmuch-crypto-signature-good
+  '((t (:background "green" :foreground "black")))
+  "Face used for good signatures."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(defface notmuch-crypto-signature-good-key
+  '((t (:background "orange" :foreground "black")))
+  "Face used for good signatures."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(defface notmuch-crypto-signature-bad
+  '((t (:background "red" :foreground "black")))
+  "Face used for bad signatures."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(defface notmuch-crypto-signature-unknown
+  '((t (:background "red" :foreground "black")))
+  "Face used for signatures of unknown status."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(defface notmuch-crypto-decryption
+  '((t (:background "purple" :foreground "black")))
+  "Face used for encryption/decryption status messages."
+  :group 'notmuch-crypto
+  :group 'notmuch-faces)
+
+(define-button-type 'notmuch-crypto-status-button-type
+  'action (lambda (button) (message (button-get button 'help-echo)))
+  'follow-link t
+  'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."
+  :supertype 'notmuch-button-type)
+
+(defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
+  (let* ((status (plist-get sigstatus :status))
+        (help-msg nil)
+        (label "Signature not processed")
+        (face 'notmuch-crypto-signature-unknown)
+        (button-action (lambda (button) (message (button-get button 'help-echo)))))
+    (cond
+     ((string= status "good")
+      (let ((fingerprint (concat "0x" (plist-get sigstatus :fingerprint))))
+       ;; if userid present, userid has full or greater validity
+       (if (plist-member sigstatus :userid)
+           (let ((userid (plist-get sigstatus :userid)))
+             (setq label (concat "Good signature by: " userid))
+             (setq face 'notmuch-crypto-signature-good))
+         (progn
+           (setq label (concat "Good signature by key: " fingerprint))
+           (setq face 'notmuch-crypto-signature-good-key)))
+       (setq button-action 'notmuch-crypto-sigstatus-good-callback)
+       (setq help-msg (concat "Click to list key ID 0x" fingerprint "."))))
+     ((string= status "error")
+      (let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
+       (setq label (concat "Unknown key ID " keyid " or unsupported algorithm"))
+       (setq button-action 'notmuch-crypto-sigstatus-error-callback)
+       (setq help-msg (concat "Click to retrieve key ID " keyid " from keyserver and redisplay."))))
+     ((string= status "bad")
+      (let ((keyid (concat "0x" (plist-get sigstatus :keyid))))
+       (setq label (concat "Bad signature (claimed key ID " keyid ")"))
+       (setq face 'notmuch-crypto-signature-bad)))
+     (t
+      (setq label (concat "Unknown signature status"
+                         (if status (concat ": " status))))))
+    (insert-button
+     (concat "[ " label " ]")
+     :type 'notmuch-crypto-status-button-type
+     'help-echo help-msg
+     'face face
+     'mouse-face face
+     'action button-action
+     :notmuch-sigstatus sigstatus
+     :notmuch-from from)
+    (insert "\n")))
+
+(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state))
+
+(defun notmuch-crypto-sigstatus-good-callback (button)
+  (let* ((sigstatus (button-get button :notmuch-sigstatus))
+        (fingerprint (concat "0x" (plist-get sigstatus :fingerprint)))
+        (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))
+        (window (display-buffer buffer t nil)))
+    (with-selected-window window
+      (with-current-buffer buffer
+       (goto-char (point-max))
+       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" fingerprint))
+      (recenter -1))))
+
+(defun notmuch-crypto-sigstatus-error-callback (button)
+  (let* ((sigstatus (button-get button :notmuch-sigstatus))
+        (keyid (concat "0x" (plist-get sigstatus :keyid)))
+        (buffer (get-buffer-create "*notmuch-crypto-gpg-out*"))
+        (window (display-buffer buffer t nil)))
+    (with-selected-window window
+      (with-current-buffer buffer
+       (goto-char (point-max))
+       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--recv-keys" keyid)
+       (insert "\n")
+       (call-process epg-gpg-program nil t t "--batch" "--no-tty" "--list-keys" keyid))
+      (recenter -1))
+    (notmuch-show-refresh-view)))
+
+(defun notmuch-crypto-insert-encstatus-button (encstatus)
+  (let* ((status (plist-get encstatus :status))
+        (help-msg nil)
+        (label "Decryption not attempted")
+        (face 'notmuch-crypto-decryption))
+    (cond
+     ((string= status "good")
+      (setq label "Decryption successful"))
+     ((string= status "bad")
+      (setq label "Decryption error"))
+     (t
+      (setq label (concat "Unknown encryption status"
+                         (if status (concat ": " status))))))
+    (insert-button
+     (concat "[ " label " ]")
+     :type 'notmuch-crypto-status-button-type
+     'help-echo help-msg
+     'face face
+     'mouse-face face)
+    (insert "\n")))
+
+;;
+
+(provide 'notmuch-crypto)
+
+;;; notmuch-crypto.el ends here
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..0d9af2a
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=Notmuch (emacs interface)
+GenericName=Email Client
+Comment=Emacs based email client
+Exec=notmuch-emacs-mua --hello %u
+MimeType=x-scheme-handler/mailto;
+Icon=emblem-mail
+Terminal=false
+Type=Application
+Categories=Network;Email;
diff --git a/emacs/notmuch-hello.el b/emacs/notmuch-hello.el
new file mode 100644 (file)
index 0000000..aff8beb
--- /dev/null
@@ -0,0 +1,1017 @@
+;;; notmuch-hello.el --- welcome to notmuch, a frontend
+;;
+;; Copyright © David Edmondson
+;;
+;; 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: David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'widget)
+(require 'wid-edit) ; For `widget-forward'.
+
+(require 'notmuch-lib)
+(require 'notmuch-mua)
+
+(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line continuation))
+(declare-function notmuch-poll "notmuch" ())
+(declare-function notmuch-tree "notmuch-tree"
+                  (&optional query query-context target buffer-name open-target))
+
+(defun notmuch-saved-search-get (saved-search field)
+  "Get FIELD from SAVED-SEARCH.
+
+If SAVED-SEARCH is a plist, this is just `plist-get', but for
+backwards compatibility, this also deals with the two other
+possible formats for SAVED-SEARCH: cons cells (NAME . QUERY) and
+lists (NAME QUERY COUNT-QUERY)."
+  (cond
+   ((keywordp (car saved-search))
+    (plist-get saved-search field))
+   ;; It is not a plist so it is an old-style entry.
+   ((consp (cdr saved-search)) ;; It is a list (NAME QUERY COUNT-QUERY)
+    (case field
+      (:name (first saved-search))
+      (:query (second saved-search))
+      (:count-query (third saved-search))
+      (t nil)))
+   (t  ;; It is a cons-cell (NAME . QUERY)
+    (case field
+      (:name (car saved-search))
+      (:query (cdr saved-search))
+      (t nil)))))
+
+(defun notmuch-hello-saved-search-to-plist (saved-search)
+  "Return a copy of SAVED-SEARCH in plist form.
+
+If saved search is a plist then just return a copy. In other
+cases, for backwards compatibility, convert to plist form and
+return that."
+  (if (keywordp (car saved-search))
+      (copy-seq saved-search)
+    (let ((fields (list :name :query :count-query))
+         plist-search)
+      (dolist (field fields plist-search)
+       (let ((string (notmuch-saved-search-get saved-search field)))
+         (when string
+           (setq plist-search (append plist-search (list field string)))))))))
+
+(defun notmuch-hello--saved-searches-to-plist (symbol)
+  "Extract a saved-search variable into plist form.
+
+The new style saved search is just a plist, but for backwards
+compatibility we use this function to extract old style saved
+searches so they still work in customize."
+  (let ((saved-searches (default-value symbol)))
+    (mapcar #'notmuch-hello-saved-search-to-plist saved-searches)))
+
+(define-widget 'notmuch-saved-search-plist 'list
+  "A single saved search property list."
+  :tag "Saved Search"
+  :args '((list :inline t
+               :format "%v"
+               (group :format "%v" :inline t (const :format "   Name: " :name) (string :format "%v"))
+               (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"
+                                   (const :tag "Default" nil)
+                                   (const :tag "Oldest-first" oldest-first)
+                                   (const :tag "Newest-first" newest-first)))
+                    (group :format "%v" :inline t (const :format "" :search-type)
+                           (choice :tag " Search Type"
+                                   (const :tag "Search mode" nil)
+                                   (const :tag "Tree mode" tree))))))
+
+(defcustom notmuch-saved-searches
+  `((:name "inbox" :query "tag:inbox" :key ,(kbd "i"))
+    (: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
+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.
+  :sort-order      Specify the sort order to be used for the search.
+                   Possible values are 'oldest-first 'newest-first or
+                   nil. Nil means use the default sort order.
+  :search-type     Specify whether to run the search in search-mode
+                   or tree mode. Set to 'tree to specify tree
+                   mode, set to nil (or anything except tree) to
+                   specify search mode.
+
+Other accepted forms are a cons cell of the form (NAME . QUERY)
+or a list of the form (NAME QUERY COUNT-QUERY)."
+;; The saved-search format is also used by the all-tags notmuch-hello
+;; section. This section generates its own saved-search list in one of
+;; the latter two forms.
+
+  :get 'notmuch-hello--saved-searches-to-plist
+  :type '(repeat notmuch-saved-search-plist)
+  :tag "List of Saved Searches"
+  :group 'notmuch-hello)
+
+(defcustom notmuch-hello-recent-searches-max 10
+  "The number of recent searches to display."
+  :type 'integer
+  :group 'notmuch-hello)
+
+(defcustom notmuch-show-empty-saved-searches nil
+  "Should saved searches with no messages be listed?"
+  :type 'boolean
+  :group 'notmuch-hello)
+
+(defun notmuch-sort-saved-searches (saved-searches)
+  "Generate an alphabetically sorted saved searches list."
+  (sort (copy-sequence saved-searches)
+       (lambda (a b)
+         (string< (notmuch-saved-search-get a :name)
+                  (notmuch-saved-search-get b :name)))))
+
+(defcustom notmuch-saved-search-sort-function nil
+  "Function used to sort the saved searches for the notmuch-hello view.
+
+This variable controls how saved searches should be sorted. No
+sorting (nil) displays the saved searches in the order they are
+stored in `notmuch-saved-searches'. Sort alphabetically sorts the
+saved searches in alphabetical order. Custom sort function should
+be a function or a lambda expression that takes the saved
+searches list as a parameter, and returns a new saved searches
+list to be used. For compatibility with the various saved-search
+formats it should use notmuch-saved-search-get to access the
+fields of the search."
+  :type '(choice (const :tag "No sorting" nil)
+                (const :tag "Sort alphabetically" notmuch-sort-saved-searches)
+                (function :tag "Custom sort function"
+                          :value notmuch-sort-saved-searches))
+  :group 'notmuch-hello)
+
+(defvar notmuch-hello-indent 4
+  "How much to indent non-headers.")
+
+(defcustom notmuch-show-logo t
+  "Should the notmuch logo be shown?"
+  :type 'boolean
+  :group 'notmuch-hello)
+
+(defcustom notmuch-show-all-tags-list nil
+  "Should all tags be shown in the notmuch-hello view?"
+  :type 'boolean
+  :group 'notmuch-hello)
+
+(defcustom notmuch-hello-tag-list-make-query nil
+  "Function or string to generate queries for the all tags list.
+
+This variable controls which query results are shown for each tag
+in the \"all tags\" list. If nil, it will use all messages with
+that tag. If this is set to a string, it is used as a filter for
+messages having that tag (equivalent to \"tag:TAG and (THIS-VARIABLE)\").
+Finally this can be a function that will be called for each tag and
+should return a filter for that tag, or nil to hide the tag."
+  :type '(choice (const :tag "All messages" nil)
+                (const :tag "Unread messages" "tag:unread")
+                (string :tag "Custom filter"
+                        :value "tag:unread")
+                (function :tag "Custom filter function"))
+  :group 'notmuch-hello)
+
+(defcustom notmuch-hello-hide-tags nil
+  "List of tags to be hidden in the \"all tags\"-section."
+  :type '(repeat string)
+  :group 'notmuch-hello)
+
+(defface notmuch-hello-logo-background
+  '((((class color)
+      (background dark))
+     (:background "#5f5f5f"))
+    (((class color)
+      (background light))
+     (:background "white")))
+  "Background colour for the notmuch logo."
+  :group 'notmuch-hello
+  :group 'notmuch-faces)
+
+(defcustom notmuch-column-control t
+  "Controls the number of columns for saved searches/tags in notmuch view.
+
+This variable has three potential sets of values:
+
+- t: automatically calculate the number of columns possible based
+  on the tags to be shown and the window width,
+- an integer: a lower bound on the number of characters that will
+  be used to display each column,
+- a float: a fraction of the window width that is the lower bound
+  on the number of characters that should be used for each
+  column.
+
+So:
+- if you would like two columns of tags, set this to 0.5.
+- if you would like a single column of tags, set this to 1.0.
+- if you would like tags to be 30 characters wide, set this to
+  30.
+- if you don't want to worry about all of this nonsense, leave
+  this set to `t'."
+  :type '(choice
+         (const :tag "Automatically calculated" t)
+         (integer :tag "Number of characters")
+         (float :tag "Fraction of window"))
+  :group 'notmuch-hello)
+
+(defcustom notmuch-hello-thousands-separator " "
+  "The string used as a thousands separator.
+
+Typically \",\" in the US and UK and \".\" or \" \" in Europe.
+The latter is recommended in the SI/ISO 31-0 standard and by the
+International Bureau of Weights and Measures."
+  :type 'string
+  :group 'notmuch-hello)
+
+(defcustom notmuch-hello-mode-hook nil
+  "Functions called after entering `notmuch-hello-mode'."
+  :type 'hook
+  :group 'notmuch-hello
+  :group 'notmuch-hooks)
+
+(defcustom notmuch-hello-refresh-hook nil
+  "Functions called after updating a `notmuch-hello' buffer."
+  :type 'hook
+  :group 'notmuch-hello
+  :group 'notmuch-hooks)
+
+(defvar notmuch-hello-url "https://notmuchmail.org"
+  "The `notmuch' web site.")
+
+(defvar notmuch-hello-custom-section-options
+  '((:filter (string :tag "Filter for each tag"))
+    (:filter-count (string :tag "Different filter to generate message counts"))
+    (:initially-hidden (const :tag "Hide this section on startup" t))
+    (:show-empty-searches (const :tag "Show queries with no matching messages" t))
+    (:hide-if-empty (const :tag "Hide this section if all queries are empty
+\(and not shown by show-empty-searches)" t)))
+  "Various customization-options for notmuch-hello-tags/query-section.")
+
+(define-widget 'notmuch-hello-tags-section 'lazy
+  "Customize-type for notmuch-hello tag-list sections."
+  :tag "Customized tag-list section (see docstring for details)"
+  :type
+  `(list :tag ""
+        (const :tag "" notmuch-hello-insert-tags-section)
+        (string :tag "Title for this section")
+        (plist
+         :inline t
+         :options
+         ,(append notmuch-hello-custom-section-options
+                  '((:hide-tags (repeat :tag "Tags that will be hidden"
+                                        string)))))))
+
+(define-widget 'notmuch-hello-query-section 'lazy
+  "Customize-type for custom saved-search-like sections"
+  :tag "Customized queries section (see docstring for details)"
+  :type
+  `(list :tag ""
+        (const :tag "" notmuch-hello-insert-searches)
+        (string :tag "Title for this section")
+        (repeat :tag "Queries"
+                (cons (string :tag "Name") (string :tag "Query")))
+        (plist :inline t :options ,notmuch-hello-custom-section-options)))
+
+(defcustom notmuch-hello-sections
+  (list #'notmuch-hello-insert-header
+       #'notmuch-hello-insert-saved-searches
+       #'notmuch-hello-insert-search
+       #'notmuch-hello-insert-recent-searches
+       #'notmuch-hello-insert-alltags
+       #'notmuch-hello-insert-footer)
+  "Sections for notmuch-hello.
+
+The list contains functions which are used to construct sections in
+notmuch-hello buffer.  When notmuch-hello buffer is constructed,
+these functions are run in the order they appear in this list.  Each
+function produces a section simply by adding content to the current
+buffer.  A section should not end with an empty line, because a
+newline will be inserted after each section by `notmuch-hello'.
+
+Each function should take no arguments. The return value is
+ignored.
+
+For convenience an element can also be a list of the form (FUNC ARG1
+ARG2 .. ARGN) in which case FUNC will be applied to the rest of the
+list.
+
+A \"Customized tag-list section\" item in the customize-interface
+displays a list of all tags, optionally hiding some of them. It
+is also possible to filter the list of messages matching each tag
+by an additional filter query. Similarly, the count of messages
+displayed next to the buttons can be generated by applying a
+different filter to the tag query. These filters are also
+supported for \"Customized queries section\" items."
+  :group 'notmuch-hello
+  :type
+  '(repeat
+    (choice (function-item notmuch-hello-insert-header)
+           (function-item notmuch-hello-insert-saved-searches)
+           (function-item notmuch-hello-insert-search)
+           (function-item notmuch-hello-insert-recent-searches)
+           (function-item notmuch-hello-insert-alltags)
+           (function-item notmuch-hello-insert-footer)
+           (function-item notmuch-hello-insert-inbox)
+           notmuch-hello-tags-section
+           notmuch-hello-query-section
+           (function :tag "Custom section"))))
+
+(defcustom notmuch-hello-auto-refresh t
+  "Automatically refresh when returning to the notmuch-hello buffer."
+  :group 'notmuch-hello
+  :type 'boolean)
+
+(defvar notmuch-hello-hidden-sections nil
+  "List of sections titles whose contents are hidden")
+
+(defvar notmuch-hello-first-run t
+  "True if `notmuch-hello' is run for the first time, set to nil
+afterwards.")
+
+(defun notmuch-hello-nice-number (n)
+  (let (result)
+    (while (> n 0)
+      (push (% n 1000) result)
+      (setq n (/ n 1000)))
+    (setq result (or result '(0)))
+    (apply #'concat
+     (number-to-string (car result))
+     (mapcar (lambda (elem)
+             (format "%s%03d" notmuch-hello-thousands-separator elem))
+            (cdr result)))))
+
+(defun notmuch-hello-trim (search)
+  "Trim whitespace."
+  (if (string-match "^[[:space:]]*\\(.*[^[:space:]]\\)[[:space:]]*$" search)
+      (match-string 1 search)
+    search))
+
+(defun notmuch-hello-search (&optional search)
+  (unless (null search)
+    (setq search (notmuch-hello-trim search))
+    (let ((history-delete-duplicates t))
+      (add-to-history 'notmuch-search-history search)))
+  (notmuch-search search notmuch-search-oldest-first))
+
+(defun notmuch-hello-add-saved-search (widget)
+  (interactive)
+  (let ((search (widget-value
+                (symbol-value
+                 (widget-get widget :notmuch-saved-search-widget))))
+       (name (completing-read "Name for saved search: "
+                              notmuch-saved-searches)))
+    ;; If an existing saved search with this name exists, remove it.
+    (setq notmuch-saved-searches
+         (loop for elem in notmuch-saved-searches
+               if (not (equal name
+                              (notmuch-saved-search-get elem :name)))
+               collect elem))
+    ;; Add the new one.
+    (customize-save-variable 'notmuch-saved-searches
+                            (add-to-list 'notmuch-saved-searches
+                                         (list :name name :query search) t))
+    (message "Saved '%s' as '%s'." search name)
+    (notmuch-hello-update)))
+
+(defun notmuch-hello-delete-search-from-history (widget)
+  (interactive)
+  (let ((search (widget-value
+                (symbol-value
+                 (widget-get widget :notmuch-saved-search-widget)))))
+    (setq notmuch-search-history (delete search
+                                        notmuch-search-history))
+    (notmuch-hello-update)))
+
+(defun notmuch-hello-longest-label (searches-alist)
+  (or (loop for elem in searches-alist
+           maximize (length (notmuch-saved-search-get elem :name)))
+      0))
+
+(defun notmuch-hello-reflect-generate-row (ncols nrows row list)
+  (let ((len (length list)))
+    (loop for col from 0 to (- ncols 1)
+         collect (let ((offset (+ (* nrows col) row)))
+                   (if (< offset len)
+                       (nth offset list)
+                     ;; Don't forget to insert an empty slot in the
+                     ;; output matrix if there is no corresponding
+                     ;; value in the input matrix.
+                     nil)))))
+
+(defun notmuch-hello-reflect (list ncols)
+  "Reflect a `ncols' wide matrix represented by `list' along the
+diagonal."
+  ;; Not very lispy...
+  (let ((nrows (ceiling (length list) ncols)))
+    (loop for row from 0 to (- nrows 1)
+         append (notmuch-hello-reflect-generate-row ncols nrows row list))))
+
+(defun notmuch-hello-widget-search (widget &rest ignore)
+  (if (widget-get widget :notmuch-search-type)
+      (notmuch-tree (widget-get widget
+                               :notmuch-search-terms))
+    (notmuch-search (widget-get widget
+                               :notmuch-search-terms)
+                   (widget-get widget
+                               :notmuch-search-oldest-first))))
+
+(defun notmuch-saved-search-count (search)
+  (car (process-lines notmuch-command "count" search)))
+
+(defun notmuch-hello-tags-per-line (widest)
+  "Determine how many tags to show per line and how wide they
+should be. Returns a cons cell `(tags-per-line width)'."
+  (let ((tags-per-line
+        (cond
+         ((integerp notmuch-column-control)
+          (max 1
+               (/ (- (window-width) notmuch-hello-indent)
+                  ;; Count is 9 wide (8 digits plus space), 1 for the space
+                  ;; after the name.
+                  (+ 9 1 (max notmuch-column-control widest)))))
+
+         ((floatp notmuch-column-control)
+          (let* ((available-width (- (window-width) notmuch-hello-indent))
+                 (proposed-width (max (* available-width notmuch-column-control) widest)))
+            (floor available-width proposed-width)))
+
+         (t
+          (max 1
+               (/ (- (window-width) notmuch-hello-indent)
+                  ;; Count is 9 wide (8 digits plus space), 1 for the space
+                  ;; after the name.
+                  (+ 9 1 widest)))))))
+
+    (cons tags-per-line (/ (max 1
+                               (- (window-width) notmuch-hello-indent
+                                  ;; Count is 9 wide (8 digits plus
+                                  ;; space), 1 for the space after the
+                                  ;; name.
+                                  (* tags-per-line (+ 9 1))))
+                          tags-per-line))))
+
+(defun notmuch-hello-filtered-query (query filter)
+  "Constructs a query to search all messages matching QUERY and FILTER.
+
+If FILTER is a string, it is directly used in the returned query.
+
+If FILTER is a function, it is called with QUERY as a parameter and
+the string it returns is used as the query. If nil is returned,
+the entry is hidden.
+
+Otherwise, FILTER is ignored.
+"
+  (cond
+   ((functionp filter) (funcall filter query))
+   ((stringp filter)
+    (concat "(" query ") and (" filter ")"))
+   (t query)))
+
+(defun notmuch-hello-query-counts (query-list &rest options)
+  "Compute list of counts of matched messages from QUERY-LIST.
+
+QUERY-LIST must be a list of saved-searches. Ideally each of
+these is a plist but other options are available for backwards
+compatibility: see `notmuch-saved-searches' for details.
+
+The result is a list of plists each of which includes the
+properties :name NAME, :query QUERY and :count COUNT, together
+with any properties in the original saved-search.
+
+The values :show-empty-searches, :filter and :filter-count from
+options will be handled as specified for
+`notmuch-hello-insert-searches'."
+  (with-temp-buffer
+    (dolist (elem query-list nil)
+      (let ((count-query (or (notmuch-saved-search-get elem :count-query)
+                            (notmuch-saved-search-get elem :query))))
+       (insert
+        (replace-regexp-in-string
+         "\n" " "
+         (notmuch-hello-filtered-query count-query
+                                       (or (plist-get options :filter-count)
+                                           (plist-get options :filter))))
+         "\n")))
+
+    (unless (= (call-process-region (point-min) (point-max) notmuch-command
+                                   t t nil "count" "--batch") 0)
+      (notmuch-logged-error "notmuch count --batch failed"
+                           "Please check that the notmuch CLI is new enough to support `count
+--batch'. In general we recommend running matching versions of
+the CLI and emacs interface."))
+
+    (goto-char (point-min))
+
+    (notmuch-remove-if-not
+     #'identity
+     (mapcar
+      (lambda (elem)
+       (let* ((elem-plist (notmuch-hello-saved-search-to-plist elem))
+              (search-query (plist-get elem-plist :query))
+              (filtered-query (notmuch-hello-filtered-query
+                               search-query (plist-get options :filter)))
+              (message-count (prog1 (read (current-buffer))
+                               (forward-line 1))))
+         (when (and filtered-query (or (plist-get options :show-empty-searches) (> message-count 0)))
+           (setq elem-plist (plist-put elem-plist :query filtered-query))
+           (plist-put elem-plist :count message-count))))
+      query-list))))
+
+(defun notmuch-hello-insert-buttons (searches)
+  "Insert buttons for SEARCHES.
+
+SEARCHES must be a list of plists each of which should contain at
+least the properties :name NAME :query QUERY and :count COUNT,
+where QUERY is the query to start when the button for the
+corresponding entry is activated, and COUNT should be the number
+of messages matching the query.  Such a plist can be computed
+with `notmuch-hello-query-counts'."
+  (let* ((widest (notmuch-hello-longest-label searches))
+        (tags-and-width (notmuch-hello-tags-per-line widest))
+        (tags-per-line (car tags-and-width))
+        (column-width (cdr tags-and-width))
+        (column-indent 0)
+        (count 0)
+        (reordered-list (notmuch-hello-reflect searches tags-per-line))
+        ;; Hack the display of the buttons used.
+        (widget-push-button-prefix "")
+        (widget-push-button-suffix ""))
+    ;; dme: It feels as though there should be a better way to
+    ;; implement this loop than using an incrementing counter.
+    (mapc (lambda (elem)
+           ;; (not elem) indicates an empty slot in the matrix.
+           (when elem
+             (if (> column-indent 0)
+                 (widget-insert (make-string column-indent ? )))
+             (let* ((name (plist-get elem :name))
+                    (query (plist-get elem :query))
+                    (oldest-first (case (plist-get elem :sort-order)
+                                    (newest-first nil)
+                                    (oldest-first t)
+                                    (otherwise notmuch-search-oldest-first)))
+                    (search-type (eq (plist-get elem :search-type) 'tree))
+                    (msg-count (plist-get elem :count)))
+               (widget-insert (format "%8s "
+                                      (notmuch-hello-nice-number msg-count)))
+               (widget-create 'push-button
+                              :notify #'notmuch-hello-widget-search
+                              :notmuch-search-terms query
+                              :notmuch-search-oldest-first oldest-first
+                              :notmuch-search-type search-type
+                              name)
+               (setq column-indent
+                     (1+ (max 0 (- column-width (length name)))))))
+           (setq count (1+ count))
+           (when (eq (% count tags-per-line) 0)
+             (setq column-indent 0)
+             (widget-insert "\n")))
+         reordered-list)
+
+    ;; If the last line was not full (and hence did not include a
+    ;; carriage return), insert one now.
+    (unless (eq (% count tags-per-line) 0)
+      (widget-insert "\n"))))
+
+(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
+
+(defun notmuch-hello-update ()
+  "Update the notmuch-hello buffer."
+  ;; Lazy - rebuild everything.
+  (interactive)
+  (notmuch-hello t))
+
+(defun notmuch-hello-window-configuration-change ()
+  "Hook function to update the hello buffer when it is switched to."
+  (let ((hello-buf (get-buffer "*notmuch-hello*"))
+       (do-refresh nil))
+    ;; Consider all windows in the currently selected frame, since
+    ;; that's where the configuration change happened.  This also
+    ;; refreshes our snapshot of all windows, so we have to do this
+    ;; even if we know we won't refresh (e.g., hello-buf is null).
+    (dolist (window (window-list))
+      (let ((last-buf (window-parameter window 'notmuch-hello-last-buffer))
+           (cur-buf (window-buffer window)))
+       (when (not (eq last-buf cur-buf))
+         ;; This window changed or is new.  Update recorded buffer
+         ;; for next time.
+         (set-window-parameter window 'notmuch-hello-last-buffer cur-buf)
+         (when (and (eq cur-buf hello-buf) last-buf)
+           ;; The user just switched to hello in this window (hello
+           ;; is currently visible, was not visible on the last
+           ;; configuration change, and this is not a new window)
+           (setq do-refresh t)))))
+    (when (and do-refresh notmuch-hello-auto-refresh)
+      ;; Refresh hello as soon as we get back to redisplay.  On Emacs
+      ;; 24, we can't do it right here because something in this
+      ;; hook's call stack overrides hello's point placement.
+      (run-at-time nil nil #'notmuch-hello t))
+    (when (null hello-buf)
+      ;; Clean up hook
+      (remove-hook 'window-configuration-change-hook
+                  #'notmuch-hello-window-configuration-change))))
+
+;; the following variable is defined as being defconst in notmuch-version.el
+(defvar notmuch-emacs-version)
+
+(defun notmuch-hello-versions ()
+  "Display the notmuch version(s)"
+  (interactive)
+  (let ((notmuch-cli-version (notmuch-cli-version)))
+    (message "notmuch version %s"
+            (if (string= notmuch-emacs-version notmuch-cli-version)
+                notmuch-cli-version
+              (concat notmuch-cli-version
+                      " (emacs mua version " notmuch-emacs-version ")")))))
+
+(defvar notmuch-hello-mode-map
+  (let ((map (if (fboundp 'make-composed-keymap)
+                ;; Inherit both widget-keymap and
+                ;; notmuch-common-keymap. We have to use
+                ;; make-sparse-keymap to force this to be a new
+                ;; keymap (so that when we modify map it does not
+                ;; modify widget-keymap).
+                (make-composed-keymap (list (make-sparse-keymap) widget-keymap))
+              ;; Before Emacs 24, keymaps didn't support multiple
+              ;; inheritance,, so just copy the widget keymap since
+              ;; it's unlikely to change.
+              (copy-keymap widget-keymap))))
+    (set-keymap-parent map notmuch-common-keymap)
+    (define-key map "v" 'notmuch-hello-versions)
+    (define-key map (kbd "<C-tab>") 'widget-backward)
+    map)
+  "Keymap for \"notmuch hello\" buffers.")
+(fset 'notmuch-hello-mode-map notmuch-hello-mode-map)
+
+(define-derived-mode notmuch-hello-mode fundamental-mode "notmuch-hello"
+ "Major mode for convenient notmuch navigation. This is your entry portal into notmuch.
+
+Saved searches are \"bookmarks\" for arbitrary queries. Hit RET
+or click on a saved search to view matching threads. Edit saved
+searches with the `edit' button. Type `\\[notmuch-jump-search]'
+in any Notmuch screen for quick access to saved searches that
+have shortcut keys.
+
+Type new searches in the search box and hit RET to view matching
+threads. Hit RET in a recent search box to re-submit a previous
+search. Edit it first if you like. Save a recent search to saved
+searches with the `save' button.
+
+Hit `\\[notmuch-search]' or `\\[notmuch-tree]' in any Notmuch
+screen to search for messages and view matching threads or
+messages, respectively. Recent searches are available in the
+minibuffer history.
+
+Expand the all tags view with the `show' button (and collapse
+again with the `hide' button). Hit RET or click on a tag name to
+view matching threads.
+
+Hit `\\[notmuch-refresh-this-buffer]' to refresh the screen and
+`\\[notmuch-bury-or-kill-this-buffer]' to quit.
+
+The screen may be customized via `\\[customize]'.
+
+Complete list of currently available key bindings:
+
+\\{notmuch-hello-mode-map}"
+ (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
+ ;;(setq buffer-read-only t)
+)
+
+(defun notmuch-hello-generate-tag-alist (&optional hide-tags)
+  "Return an alist from tags to queries to display in the all-tags section."
+  (mapcar (lambda (tag)
+           (cons tag (concat "tag:" (notmuch-escape-boolean-term tag))))
+         (notmuch-remove-if-not
+          (lambda (tag)
+            (not (member tag hide-tags)))
+          (process-lines notmuch-command "search" "--output=tags" "*"))))
+
+(defun notmuch-hello-insert-header ()
+  "Insert the default notmuch-hello header."
+  (when notmuch-show-logo
+    (let ((image notmuch-hello-logo))
+      ;; The notmuch logo uses transparency. That can display poorly
+      ;; when inserting the image into an emacs buffer (black logo on
+      ;; a black background), so force the background colour of the
+      ;; image. We use a face to represent the colour so that
+      ;; `defface' can be used to declare the different possible
+      ;; colours, which depend on whether the frame has a light or
+      ;; dark background.
+      (setq image (cons 'image
+                       (append (cdr image)
+                               (list :background (face-background 'notmuch-hello-logo-background)))))
+      (insert-image image))
+    (widget-insert "  "))
+
+  (widget-insert "Welcome to ")
+  ;; Hack the display of the links used.
+  (let ((widget-link-prefix "")
+       (widget-link-suffix ""))
+    (widget-create 'link
+                  :notify (lambda (&rest ignore)
+                            (browse-url notmuch-hello-url))
+                  :help-echo "Visit the notmuch website."
+                  "notmuch")
+    (widget-insert ". ")
+    (widget-insert "You have ")
+    (widget-create 'link
+                  :notify (lambda (&rest ignore)
+                            (notmuch-hello-update))
+                  :help-echo "Refresh"
+                  (notmuch-hello-nice-number
+                   (string-to-number (car (process-lines notmuch-command "count")))))
+    (widget-insert " messages.\n")))
+
+
+(defun notmuch-hello-insert-saved-searches ()
+  "Insert the saved-searches section."
+  (let ((searches (notmuch-hello-query-counts
+                  (if notmuch-saved-search-sort-function
+                      (funcall notmuch-saved-search-sort-function
+                               notmuch-saved-searches)
+                    notmuch-saved-searches)
+                  :show-empty-searches notmuch-show-empty-saved-searches)))
+    (when searches
+      (widget-insert "Saved searches: ")
+      (widget-create 'push-button
+                    :notify (lambda (&rest ignore)
+                              (customize-variable 'notmuch-saved-searches))
+                    "edit")
+      (widget-insert "\n\n")
+      (let ((start (point)))
+       (notmuch-hello-insert-buttons searches)
+       (indent-rigidly start (point) notmuch-hello-indent)))))
+
+(defun notmuch-hello-insert-search ()
+  "Insert a search widget."
+  (widget-insert "Search: ")
+  (widget-create 'editable-field
+                ;; Leave some space at the start and end of the
+                ;; search boxes.
+                :size (max 8 (- (window-width) notmuch-hello-indent
+                                (length "Search: ")))
+                :action (lambda (widget &rest ignore)
+                          (notmuch-hello-search (widget-value widget))))
+  ;; Add an invisible dot to make `widget-end-of-line' ignore
+  ;; trailing spaces in the search widget field.  A dot is used
+  ;; instead of a space to make `show-trailing-whitespace'
+  ;; happy, i.e. avoid it marking the whole line as trailing
+  ;; spaces.
+  (widget-insert ".")
+  (put-text-property (1- (point)) (point) 'invisible t)
+  (widget-insert "\n"))
+
+(defun notmuch-hello-insert-recent-searches ()
+  "Insert recent searches."
+  (when notmuch-search-history
+    (widget-insert "Recent searches: ")
+    (widget-create 'push-button
+                  :notify (lambda (&rest ignore)
+                            (when (y-or-n-p "Are you sure you want to clear the searches? ")
+                              (setq notmuch-search-history nil)
+                              (notmuch-hello-update)))
+                  "clear")
+    (widget-insert "\n\n")
+    (let ((start (point)))
+      (loop for i from 1 to notmuch-hello-recent-searches-max
+           for search in notmuch-search-history do
+           (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i))))
+             (set widget-symbol
+                  (widget-create 'editable-field
+                                 ;; Don't let the search boxes be
+                                 ;; less than 8 characters wide.
+                                 :size (max 8
+                                            (- (window-width)
+                                               ;; Leave some space
+                                               ;; at the start and
+                                               ;; end of the
+                                               ;; boxes.
+                                               (* 2 notmuch-hello-indent)
+                                               ;; 1 for the space
+                                               ;; before the
+                                               ;; `[save]' button. 6
+                                               ;; for the `[save]'
+                                               ;; button.
+                                               1 6
+                                               ;; 1 for the space
+                                               ;; before the `[del]'
+                                               ;; button. 5 for the
+                                               ;; `[del]' button.
+                                               1 5))
+                                 :action (lambda (widget &rest ignore)
+                                           (notmuch-hello-search (widget-value widget)))
+                                 search))
+             (widget-insert " ")
+             (widget-create 'push-button
+                            :notify (lambda (widget &rest ignore)
+                                      (notmuch-hello-add-saved-search widget))
+                            :notmuch-saved-search-widget widget-symbol
+                            "save")
+             (widget-insert " ")
+             (widget-create 'push-button
+                            :notify (lambda (widget &rest ignore)
+                                      (when (y-or-n-p "Are you sure you want to delete this search? ")
+                                        (notmuch-hello-delete-search-from-history widget)))
+                            :notmuch-saved-search-widget widget-symbol
+                            "del"))
+           (widget-insert "\n"))
+      (indent-rigidly start (point) notmuch-hello-indent))
+    nil))
+
+(defun notmuch-hello-insert-searches (title query-list &rest options)
+  "Insert a section with TITLE showing a list of buttons made from QUERY-LIST.
+
+QUERY-LIST should ideally be a plist but for backwards
+compatibility other forms are also accepted (see
+`notmuch-saved-searches' for details).  The plist should
+contain keys :name and :query; if :count-query is also present
+then it specifies an alternate query to be used to generate the
+count for the associated search.
+
+Supports the following entries in OPTIONS as a plist:
+:initially-hidden - if non-nil, section will be hidden on startup
+:show-empty-searches - show buttons with no matching messages
+:hide-if-empty - hide if no buttons would be shown
+   (only makes sense without :show-empty-searches)
+:filter - This can be a function that takes the search query as its argument and
+   returns a filter to be used in conjunction with the query for that search or nil
+   to hide the element. This can also be a string that is used as a combined with
+   each query using \"and\".
+:filter-count - Separate filter to generate the count displayed each search. Accepts
+   the same values as :filter. If :filter and :filter-count are specified, this
+   will be used instead of :filter, not in conjunction with it."
+  (widget-insert title ": ")
+  (if (and notmuch-hello-first-run (plist-get options :initially-hidden))
+      (add-to-list 'notmuch-hello-hidden-sections title))
+  (let ((is-hidden (member title notmuch-hello-hidden-sections))
+       (start (point)))
+    (if is-hidden
+       (widget-create 'push-button
+                      :notify `(lambda (widget &rest ignore)
+                                 (setq notmuch-hello-hidden-sections
+                                       (delete ,title notmuch-hello-hidden-sections))
+                                 (notmuch-hello-update))
+                      "show")
+      (widget-create 'push-button
+                    :notify `(lambda (widget &rest ignore)
+                               (add-to-list 'notmuch-hello-hidden-sections
+                                            ,title)
+                               (notmuch-hello-update))
+                    "hide"))
+    (widget-insert "\n")
+    (when (not is-hidden)
+      (let ((searches (apply 'notmuch-hello-query-counts query-list options)))
+       (when (or (not (plist-get options :hide-if-empty))
+                 searches)
+         (widget-insert "\n")
+         (notmuch-hello-insert-buttons searches)
+         (indent-rigidly start (point) notmuch-hello-indent))))))
+
+(defun notmuch-hello-insert-tags-section (&optional title &rest options)
+  "Insert a section displaying all tags with message counts.
+
+TITLE defaults to \"All tags\".
+Allowed options are those accepted by `notmuch-hello-insert-searches' and the
+following:
+
+:hide-tags - List of tags that should be excluded."
+  (apply 'notmuch-hello-insert-searches
+        (or title "All tags")
+        (notmuch-hello-generate-tag-alist (plist-get options :hide-tags))
+        options))
+
+(defun notmuch-hello-insert-inbox ()
+  "Show an entry for each saved search and inboxed messages for each tag"
+  (notmuch-hello-insert-searches "What's in your inbox"
+                                (append
+                                 notmuch-saved-searches
+                                 (notmuch-hello-generate-tag-alist))
+                                :filter "tag:inbox"))
+
+(defun notmuch-hello-insert-alltags ()
+  "Insert a section displaying all tags and associated message counts"
+  (notmuch-hello-insert-tags-section
+   nil
+   :initially-hidden (not notmuch-show-all-tags-list)
+   :hide-tags notmuch-hello-hide-tags
+   :filter notmuch-hello-tag-list-make-query))
+
+(defun notmuch-hello-insert-footer ()
+  "Insert the notmuch-hello footer."
+  (let ((start (point)))
+    (widget-insert "Hit `?' for context-sensitive help in any Notmuch screen.\n")
+    (widget-insert "Customize ")
+    (widget-create 'link
+                  :notify (lambda (&rest ignore)
+                            (customize-group 'notmuch))
+                  :button-prefix "" :button-suffix ""
+                  "Notmuch")
+    (widget-insert " or ")
+    (widget-create 'link
+                  :notify (lambda (&rest ignore)
+                            (customize-variable 'notmuch-hello-sections))
+                  :button-prefix "" :button-suffix ""
+                  "this page.")
+    (let ((fill-column (- (window-width) notmuch-hello-indent)))
+      (center-region start (point)))))
+
+;;;###autoload
+(defun notmuch-hello (&optional no-display)
+  "Run notmuch and display saved searches, known tags, etc."
+  (interactive)
+
+  (notmuch-assert-cli-sane)
+  ;; This may cause a window configuration change, so if the
+  ;; auto-refresh hook is already installed, avoid recursive refresh.
+  (let ((notmuch-hello-auto-refresh nil))
+    (if no-display
+       (set-buffer "*notmuch-hello*")
+      (switch-to-buffer "*notmuch-hello*")))
+
+  ;; Install auto-refresh hook
+  (when notmuch-hello-auto-refresh
+    (add-hook 'window-configuration-change-hook
+             #'notmuch-hello-window-configuration-change))
+
+  (let ((target-line (line-number-at-pos))
+       (target-column (current-column))
+       (inhibit-read-only t))
+
+    ;; Delete all editable widget fields.  Editable widget fields are
+    ;; tracked in a buffer local variable `widget-field-list' (and
+    ;; others).  If we do `erase-buffer' without properly deleting the
+    ;; widgets, some widget-related functions are confused later.
+    (mapc 'widget-delete widget-field-list)
+
+    (erase-buffer)
+
+    (unless (eq major-mode 'notmuch-hello-mode)
+      (notmuch-hello-mode))
+
+    (let ((all (overlay-lists)))
+      ;; Delete all the overlays.
+      (mapc 'delete-overlay (car all))
+      (mapc 'delete-overlay (cdr all)))
+
+    (mapc
+     (lambda (section)
+       (let ((point-before (point)))
+        (if (functionp section)
+            (funcall section)
+          (apply (car section) (cdr section)))
+        ;; don't insert a newline when the previous section didn't
+        ;; show anything.
+        (unless (eq (point) point-before)
+          (widget-insert "\n"))))
+     notmuch-hello-sections)
+    (widget-setup)
+
+    ;; Move point back to where it was before refresh. Use line and
+    ;; column instead of point directly to be insensitive to additions
+    ;; and removals of text within earlier lines.
+    (goto-char (point-min))
+    (forward-line (1- target-line))
+    (move-to-column target-column))
+  (run-hooks 'notmuch-hello-refresh-hook)
+  (setq notmuch-hello-first-run nil))
+
+(defun notmuch-folder ()
+  "Deprecated function for invoking notmuch---calling `notmuch' is preferred now."
+  (interactive)
+  (notmuch-hello))
+
+;;
+
+(provide 'notmuch-hello)
+
+;;; notmuch-hello.el ends here
diff --git a/emacs/notmuch-jump.el b/emacs/notmuch-jump.el
new file mode 100644 (file)
index 0000000..3e20b8c
--- /dev/null
@@ -0,0 +1,211 @@
+;;; 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 <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Austin Clements <aclements@csail.mit.edu>
+;;          David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+
+(require 'notmuch-lib)
+(require 'notmuch-hello)
+
+(eval-and-compile
+  (unless (fboundp 'window-body-width)
+    ;; Compatibility for Emacs pre-24
+    (defalias 'window-body-width 'window-width)))
+
+;;;###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
+                       (if (eq (plist-get saved-search :search-type) 'tree)
+                           `(lambda () (notmuch-tree ',query))
+                         `(lambda () (notmuch-search ',query ',oldest-first))))
+                 action-map)))))
+    (setq action-map (nreverse action-map))
+
+    (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 prompt))
+        ;; 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)
+    (define-key map (kbd "DEL") 'exit-minibuffer)
+    map)
+  "Base keymap for notmuch-jump's minibuffer keymap.")
+
+(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)
+      (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))
+
+;;
+
+(provide 'notmuch-jump)
+
+;;; notmuch-jump.el ends here
diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el
new file mode 100644 (file)
index 0000000..25d83fd
--- /dev/null
@@ -0,0 +1,1016 @@
+;;; notmuch-lib.el --- common variables, functions and function declarations
+;;
+;; Copyright © Carl Worth
+;;
+;; 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: Carl Worth <cworth@cworth.org>
+
+;; This is an part of an emacs-based interface to the notmuch mail system.
+
+;;; Code:
+
+(require 'mm-util)
+(require 'mm-view)
+(require 'mm-decode)
+(require 'cl)
+(require 'notmuch-compat)
+
+(unless (require 'notmuch-version nil t)
+  (defconst notmuch-emacs-version "unknown"
+    "Placeholder variable when notmuch-version.el[c] is not available."))
+
+(autoload 'notmuch-jump-search "notmuch-jump"
+  "Jump to a saved search by shortcut key." t)
+
+(defgroup notmuch nil
+  "Notmuch mail reader for Emacs."
+  :group 'mail)
+
+(defgroup notmuch-hello nil
+  "Overview of saved searches, tags, etc."
+  :group 'notmuch)
+
+(defgroup notmuch-search nil
+  "Searching and sorting mail."
+  :group 'notmuch)
+
+(defgroup notmuch-show nil
+  "Showing messages and threads."
+  :group 'notmuch)
+
+(defgroup notmuch-send nil
+  "Sending messages from Notmuch."
+  :group 'notmuch)
+
+(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)
+
+(defgroup notmuch-hooks nil
+  "Running custom code on well-defined occasions."
+  :group 'notmuch)
+
+(defgroup notmuch-external nil
+  "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)
+
+(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.
+
+This variable defines the default sort order for displaying
+search results. Note that any filtered searches created by
+`notmuch-search-filter' retain the search order of the parent
+search."
+  :type 'boolean
+  :group 'notmuch-search)
+
+(defcustom notmuch-poll-script nil
+  "[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')
+to incorporate new mail into the notmuch database.
+
+If set to nil (the default), new mail is processed by invoking
+\"notmuch new\". Otherwise, this should be set to a string that
+gives the name of an external script that processes new mail. If
+set to the empty string, no command will be run.
+
+The external script could do any of the following depending on
+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"
+  :type '(choice (const :tag "notmuch new" nil)
+                (const :tag "Disabled" "")
+                (string :tag "Custom script"))
+  :group 'notmuch-external)
+
+;;
+
+(defvar notmuch-search-history nil
+  "Variable to store notmuch searches history.")
+
+(defcustom notmuch-archive-tags '("-inbox")
+  "List of tag changes to apply to a message or a thread when it is archived.
+
+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 or thread being archived.
+
+For example, if you wanted to remove an \"inbox\" tag and add an
+\"archived\" tag, you would set:
+    (\"-inbox\" \"+archived\")"
+  :type '(repeat string)
+  :group 'notmuch-search
+  :group 'notmuch-show)
+
+(defvar notmuch-common-keymap
+  (let ((map (make-sparse-keymap)))
+    (define-key map "?" 'notmuch-help)
+    (define-key map "q" 'notmuch-bury-or-kill-this-buffer)
+    (define-key map "s" 'notmuch-search)
+    (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)
+  "Keymap shared by all notmuch modes.")
+
+;; By default clicking on a button does not select the window
+;; containing the button (as opposed to clicking on a widget which
+;; does). This means that the button action is then executed in the
+;; current selected window which can cause problems if the button
+;; changes the buffer (e.g., id: links) or moves point.
+;;
+;; This provides a button type which overrides mouse-action so that
+;; the button's window is selected before the action is run. Other
+;; notmuch buttons can get the same behaviour by inheriting from this
+;; button type.
+(define-button-type 'notmuch-button-type
+  'mouse-action (lambda (button)
+                 (select-window (posn-window (event-start last-input-event)))
+                 (button-activate button)))
+
+(defun notmuch-command-to-string (&rest args)
+  "Synchronously invoke \"notmuch\" with the given list of arguments.
+
+If notmuch exits with a non-zero status, output from the process
+will appear in a buffer named \"*Notmuch errors*\" and an error
+will be signaled.
+
+Otherwise the output will be returned"
+  (with-temp-buffer
+    (let* ((status (apply #'call-process notmuch-command nil t nil args))
+          (output (buffer-string)))
+      (notmuch-check-exit-status status (cons notmuch-command args) output)
+      output)))
+
+(defvar notmuch--cli-sane-p nil
+  "Cache whether the CLI seems to be configured sanely.")
+
+(defun notmuch-cli-sane-p ()
+  "Return t if the cli seems to be configured sanely."
+  (unless notmuch--cli-sane-p
+    (let ((status (call-process notmuch-command nil nil nil
+                               "config" "get" "user.primary_email")))
+      (setq notmuch--cli-sane-p (= status 0))))
+  notmuch--cli-sane-p)
+
+(defun notmuch-assert-cli-sane ()
+  (unless (notmuch-cli-sane-p)
+    (notmuch-logged-error
+     "notmuch cli seems misconfigured or unconfigured."
+"Perhaps you haven't run \"notmuch setup\" yet? Try running this
+on the command line, and then retry your notmuch command")))
+
+(defun notmuch-cli-version ()
+  "Return a string with the notmuch cli command version number."
+  (let ((long-string
+        ;; Trim off the trailing newline.
+        (substring (notmuch-command-to-string "--version") 0 -1)))
+    (if (string-match "^notmuch\\( version\\)? \\(.*\\)$"
+                     long-string)
+       (match-string 2 long-string)
+      "unknown")))
+
+(defun notmuch-config-get (item)
+  "Return a value from the notmuch configuration."
+  (let* ((val (notmuch-command-to-string "config" "get" item))
+        (len (length val)))
+    ;; Trim off the trailing newline (if the value is empty or not
+    ;; configured, there will be no newline)
+    (if (and (> len 0) (= (aref val (- len 1)) ?\n))
+       (substring val 0 -1)
+      val)))
+
+(defun notmuch-database-path ()
+  "Return the database.path value from the notmuch configuration."
+  (notmuch-config-get "database.path"))
+
+(defun notmuch-user-name ()
+  "Return the user.name value from the notmuch configuration."
+  (notmuch-config-get "user.name"))
+
+(defun notmuch-user-primary-email ()
+  "Return the user.primary_email value from the notmuch configuration."
+  (notmuch-config-get "user.primary_email"))
+
+(defun notmuch-user-other-email ()
+  "Return the user.other_email value (as a list) from the notmuch configuration."
+  (split-string (notmuch-config-get "user.other_email") "\n" t))
+
+(defun notmuch-user-emails ()
+  (cons (notmuch-user-primary-email) (notmuch-user-other-email)))
+
+(defun notmuch-poll ()
+  "Run \"notmuch new\" or an external script to import mail.
+
+Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
+depending on the value of `notmuch-poll-script'."
+  (interactive)
+  (if (stringp notmuch-poll-script)
+      (unless (string= notmuch-poll-script "")
+       (unless (equal (call-process notmuch-poll-script nil nil) 0)
+         (error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
+    (notmuch-call-notmuch-process "new")))
+
+(defun notmuch-bury-or-kill-this-buffer ()
+  "Undisplay the current buffer.
+
+Bury the current buffer, unless there is only one window showing
+it, in which case it is killed."
+  (interactive)
+  (if (> (length (get-buffer-window-list nil nil t)) 1)
+      (bury-buffer)
+    (kill-buffer)))
+
+(defun notmuch-documentation-first-line (symbol)
+  "Return the first line of the documentation string for SYMBOL."
+  (let ((doc (documentation symbol)))
+    (if doc
+       (with-temp-buffer
+         (insert (documentation symbol t))
+         (goto-char (point-min))
+         (let ((beg (point)))
+           (end-of-line)
+           (buffer-substring beg (point))))
+      "")))
+
+(defun notmuch-prefix-key-description (key)
+  "Given a prefix key code, return a human-readable string representation.
+
+This is basically just `format-kbd-macro' but we also convert ESC to M-."
+  (let* ((key-vector (if (vectorp key) key (vector key)))
+        (desc (format-kbd-macro key-vector)))
+    (if (string= desc "ESC")
+       "M-"
+      (concat desc " "))))
+
+
+(defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
+  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL
+
+It does not prepend if ACTUAL-KEY is already listed in TAIL."
+  (let ((key-string (concat prefix (format-kbd-macro actual-key))))
+    ;; We don't include documentation if the key-binding is
+    ;; over-ridden. Note, over-riding a binding automatically hides the
+    ;; prefixed version too.
+    (unless (assoc key-string tail)
+      (when (and ua-keys (symbolp binding)
+                (get binding 'notmuch-prefix-doc))
+       ;; Documentation for prefixed command
+       (let ((ua-desc (key-description ua-keys)))
+         (push (cons (concat ua-desc " " prefix (format-kbd-macro actual-key))
+                     (get binding 'notmuch-prefix-doc))
+               tail)))
+      ;; Documentation for command
+      (push (cons key-string
+                 (or (and (symbolp binding) (get binding 'notmuch-doc))
+                     (notmuch-documentation-first-line binding)))
+           tail)))
+    tail)
+
+(defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail)
+  ;; Remappings are represented as a binding whose first "event" is
+  ;; 'remap.  Hence, if the keymap has any remappings, it will have a
+  ;; binding whose "key" is 'remap, and whose "binding" is itself a
+  ;; keymap that maps not from keys to commands, but from old (remapped)
+  ;; functions to the commands to use in their stead.
+  (map-keymap
+   (lambda (command binding)
+     (mapc
+      (lambda (actual-key)
+       (setq tail (notmuch-describe-key actual-key binding prefix ua-keys tail)))
+      (where-is-internal command base-keymap)))
+   remap-keymap)
+  tail)
+
+(defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail)
+  "Return a list of cons cells, each describing one binding in KEYMAP.
+
+Each cons cell consists of a string giving a human-readable
+description of the key, and a one-line description of the bound
+function.  See `notmuch-help' for an overview of how this
+documentation is extracted.
+
+UA-KEYS should be a key sequence bound to `universal-argument'.
+It will be used to describe bindings of commands that support a
+prefix argument.  PREFIX and TAIL are used internally."
+  (map-keymap
+   (lambda (key binding)
+     (cond ((mouse-event-p key) nil)
+          ((keymapp binding)
+           (setq tail
+                 (if (eq key 'remap)
+                     (notmuch-describe-remaps
+                      binding ua-keys base-keymap prefix tail)
+                   (notmuch-describe-keymap
+                    binding ua-keys base-keymap (notmuch-prefix-key-description key) tail))))
+          (binding
+           (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail)))))
+   keymap)
+  tail)
+
+(defun notmuch-substitute-command-keys (doc)
+  "Like `substitute-command-keys' but with documentation, not function names."
+  (let ((beg 0))
+    (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
+      (let ((desc
+            (save-match-data
+              (let* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
+                     (keymap (symbol-value (intern keymap-name)))
+                     (ua-keys (where-is-internal 'universal-argument keymap t))
+                     (desc-alist (notmuch-describe-keymap keymap ua-keys keymap))
+                     (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist)))
+                (mapconcat #'identity desc-list "\n")))))
+       (setq doc (replace-match desc 1 1 doc)))
+      (setq beg (match-end 0)))
+    doc))
+
+(defun notmuch-help ()
+  "Display help for the current notmuch mode.
+
+This is similar to `describe-function' for the current major
+mode, but bindings tables are shown with documentation strings
+rather than command names.  By default, this uses the first line
+of each command's documentation string.  A command can override
+this by setting the 'notmuch-doc property of its command symbol.
+A command that supports a prefix argument can explicitly document
+its prefixed behavior by setting the 'notmuch-prefix-doc property
+of its command symbol."
+  (interactive)
+  (let* ((mode major-mode)
+        (doc (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t)))))
+    (with-current-buffer (generate-new-buffer "*notmuch-help*")
+      (insert doc)
+      (goto-char (point-min))
+      (set-buffer-modified-p nil)
+      (view-buffer (current-buffer) 'kill-buffer-if-not-modified))))
+
+(defun notmuch-subkeymap-help ()
+  "Show help for a subkeymap."
+  (interactive)
+  (let* ((key (this-command-keys-vector))
+       (prefix (make-vector (1- (length key)) nil))
+       (i 0))
+    (while (< i (length prefix))
+      (aset prefix i (aref key i))
+      (setq i (1+ i)))
+
+    (let* ((subkeymap (key-binding prefix))
+          (ua-keys (where-is-internal 'universal-argument nil t))
+          (prefix-string (notmuch-prefix-key-description prefix))
+          (desc-alist (notmuch-describe-keymap subkeymap ua-keys subkeymap prefix-string))
+          (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist))
+          (desc (mapconcat #'identity desc-list "\n")))
+      (with-help-window (help-buffer)
+       (with-current-buffer standard-output
+         (insert "\nPress 'q' to quit this window.\n\n")
+         (insert desc)))
+      (pop-to-buffer (help-buffer)))))
+
+(defvar notmuch-buffer-refresh-function nil
+  "Function to call to refresh the current buffer.")
+(make-variable-buffer-local 'notmuch-buffer-refresh-function)
+
+(defun notmuch-refresh-this-buffer ()
+  "Refresh the current buffer."
+  (interactive)
+  (when 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."
+  (interactive)
+  (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.
+  (save-match-data
+    (if (and subject
+            (string-match "^[ \t]*$" subject))
+       "[No Subject]"
+      subject)))
+
+(defun notmuch-sanitize (str)
+  "Sanitize control character in STR.
+
+This includes newlines, tabs, and other funny characters."
+  (replace-regexp-in-string "[[:cntrl:]\x7f\u2028\u2029]+" " " str))
+
+(defun notmuch-escape-boolean-term (term)
+  "Escape a boolean term for use in a query.
+
+The caller is responsible for prepending the term prefix and a
+colon.  This performs minimal escaping in order to produce
+user-friendly queries."
+
+  (save-match-data
+    (if (or (equal term "")
+           ;; To be pessimistic, only pass through terms composed
+           ;; entirely of ASCII printing characters other than ", (,
+           ;; and ).
+           (string-match "[^!#-'*-~]" term))
+       ;; Requires escaping
+       (concat "\"" (replace-regexp-in-string "\"" "\"\"" term t t) "\"")
+      term)))
+
+(defun notmuch-id-to-query (id)
+  "Return a query that matches the message with id ID."
+  (concat "id:" (notmuch-escape-boolean-term id)))
+
+(defun notmuch-hex-encode (str)
+  "Hex-encode STR (e.g., as used by batch tagging).
+
+This replaces spaces, percents, and double quotes in STR with
+%NN where NN is the hexadecimal value of the character."
+  (replace-regexp-in-string
+   "[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
+
+;;
+
+(defun notmuch-common-do-stash (text)
+  "Common function to stash text in kill ring, and display in minibuffer."
+  (if text
+      (progn
+       (kill-new text)
+       (message "Stashed: %s" text))
+    ;; There is nothing to stash so stash an empty string so the user
+    ;; doesn't accidentally paste something else somewhere.
+    (kill-new "")
+    (message "Nothing to stash!")))
+
+;;
+
+(defun notmuch-remove-if-not (predicate list)
+  "Return a copy of LIST with all items not satisfying PREDICATE removed."
+  (let (out)
+    (while list
+      (when (funcall predicate (car list))
+        (push (car list) out))
+      (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 "/"))
+
+(defun notmuch-match-content-type (t1 t2)
+  "Return t if t1 and t2 are matching content types, taking wildcards into account"
+  (let ((st1 (notmuch-split-content-type t1))
+       (st2 (notmuch-split-content-type t2)))
+    (if (or (string= (cadr st1) "*")
+           (string= (cadr st2) "*"))
+       ;; Comparison of content types should be case insensitive.
+       (string= (downcase (car st1)) (downcase (car st2)))
+      (string= (downcase t1) (downcase t2)))))
+
+(defvar notmuch-multipart/alternative-discouraged
+  '(
+    ;; Avoid HTML parts.
+    "text/html"
+    ;; multipart/related usually contain a text/html part and some associated graphics.
+    "multipart/related"
+    ))
+
+(defun notmuch-multipart/alternative-determine-discouraged (msg)
+  "Return the discouraged alternatives for the specified message."
+  ;; If a function, return the result of calling it.
+  (if (functionp notmuch-multipart/alternative-discouraged)
+      (funcall notmuch-multipart/alternative-discouraged msg)
+    ;; Otherwise simply return the value of the variable, which is
+    ;; assumed to be a list of discouraged alternatives. This is the
+    ;; default behaviour.
+    notmuch-multipart/alternative-discouraged))
+
+(defun notmuch-multipart/alternative-choose (msg types)
+  "Return a list of preferred types from the given list of types
+for this message, if present."
+  ;; Based on `mm-preferred-alternative-precedence'.
+  (let ((discouraged (notmuch-multipart/alternative-determine-discouraged msg))
+       (seq types))
+    (dolist (pref (reverse discouraged))
+      (dolist (elem (copy-sequence seq))
+       (when (string-match pref elem)
+         (setq seq (nconc (delete elem seq) (list elem))))))
+    seq))
+
+(defun notmuch-parts-filter-by-type (parts type)
+  "Given a list of message parts, return a list containing the ones matching
+the given type."
+  (remove-if-not
+   (lambda (part) (notmuch-match-content-type (plist-get part :content-type) type))
+   parts))
+
+(defun notmuch--get-bodypart-raw (msg part process-crypto binaryp cache)
+  (let* ((plist-elem (if binaryp :content-binary :content))
+        (data (or (plist-get part plist-elem)
+                  (with-temp-buffer
+                    ;; Emacs internally uses a UTF-8-like multibyte string
+                    ;; representation by default (regardless of the coding
+                    ;; system, which only affects how it goes from outside data
+                    ;; to this internal representation).  This *almost* never
+                    ;; matters.  Annoyingly, it does matter if we use this data
+                    ;; in an image descriptor, since Emacs will use its internal
+                    ;; data buffer directly and this multibyte representation
+                    ;; corrupts binary image formats.  Since the caller is
+                    ;; asking for binary data, a unibyte string is a more
+                    ;; appropriate representation anyway.
+                    (when binaryp
+                      (set-buffer-multibyte nil))
+                    (let ((args `("show" "--format=raw"
+                                  ,(format "--part=%s" (plist-get part :id))
+                                  ,@(when process-crypto '("--decrypt=true"))
+                                  ,(notmuch-id-to-query (plist-get msg :id))))
+                          (coding-system-for-read
+                           (if binaryp 'no-conversion
+                             (let ((coding-system (mm-charset-to-coding-system
+                                                   (plist-get part :content-charset))))
+                               ;; Sadly,
+                               ;; `mm-charset-to-coding-system' seems
+                               ;; to return things that are not
+                               ;; considered acceptable values for
+                               ;; `coding-system-for-read'.
+                               (if (coding-system-p coding-system)
+                                   coding-system
+                                 ;; RFC 2047 says that the default
+                                 ;; charset is US-ASCII. RFC6657
+                                 ;; complicates this somewhat.
+                                 'us-ascii)))))
+                      (apply #'call-process notmuch-command nil '(t nil) nil args)
+                      (buffer-string))))))
+    (when (and cache data)
+      (plist-put part plist-elem data))
+    data))
+
+(defun notmuch-get-bodypart-binary (msg part process-crypto &optional cache)
+  "Return the unprocessed content of PART in MSG as a unibyte string.
+
+This returns the \"raw\" content of the given part after content
+transfer decoding, but with no further processing (see the
+discussion of --format=raw in man notmuch-show).  In particular,
+this does no charset conversion.
+
+If CACHE is non-nil, the content of this part will be saved in
+MSG (if it isn't already)."
+  (notmuch--get-bodypart-raw msg part process-crypto t cache))
+
+(defun notmuch-get-bodypart-text (msg part process-crypto &optional cache)
+  "Return the text content of PART in MSG.
+
+This returns the content of the given part as a multibyte Lisp
+string after performing content transfer decoding and any
+necessary charset decoding.
+
+If CACHE is non-nil, the content of this part will be saved in
+MSG (if it isn't already)."
+  (notmuch--get-bodypart-raw msg part process-crypto nil cache))
+
+;; Workaround: The call to `mm-display-part' below triggers a bug in
+;; Emacs 24 if it attempts to use the shr renderer to display an HTML
+;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and
+;; Fedora 17, though unreproducable in other configurations).
+;; `mm-shr' references the variable `gnus-inhibit-images' without
+;; first loading gnus-art, which defines it, resulting in a
+;; void-variable error.  Hence, we advise `mm-shr' to ensure gnus-art
+;; is loaded.
+(if (>= emacs-major-version 24)
+    (defadvice mm-shr (before load-gnus-arts activate)
+      (require 'gnus-art nil t)
+      (ad-disable-advice 'mm-shr 'before 'load-gnus-arts)
+      (ad-activate 'mm-shr)))
+
+(defun notmuch-mm-display-part-inline (msg part content-type process-crypto)
+  "Use the mm-decode/mm-view functions to display a part in the
+current buffer, if possible."
+  (let ((display-buffer (current-buffer)))
+    (with-temp-buffer
+      ;; In case we already have :content, use it and tell mm-* that
+      ;; it's already been charset-decoded by using the fake
+      ;; `gnus-decoded' charset.  Otherwise, we'll fetch the binary
+      ;; part content and let mm-* decode it.
+      (let* ((have-content (plist-member part :content))
+            (charset (if have-content 'gnus-decoded
+                       (plist-get part :content-charset)))
+            (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset)))))
+       ;; If the user wants the part inlined, insert the content and
+       ;; test whether we are able to inline it (which includes both
+       ;; capability and suitability tests).
+       (when (mm-inlined-p handle)
+         (if have-content
+             (insert (notmuch-get-bodypart-text msg part process-crypto))
+           (insert (notmuch-get-bodypart-binary msg part process-crypto)))
+         (when (mm-inlinable-p handle)
+           (set-buffer display-buffer)
+           (mm-display-part handle)
+           t))))))
+
+;; Converts a plist of headers to an alist of headers. The input plist should
+;; have symbols of the form :Header as keys, and the resulting alist will have
+;; symbols of the form 'Header as keys.
+(defun notmuch-headers-plist-to-alist (plist)
+  (loop for (key value . rest) on plist by #'cddr
+       collect (cons (intern (substring (symbol-name key) 1)) value)))
+
+(defun notmuch-face-ensure-list-form (face)
+  "Return FACE in face list form.
+
+If FACE is already a face list, it will be returned as-is.  If
+FACE is a face name or face plist, it will be returned as a
+single element face list."
+  (if (and (listp face) (not (keywordp (car face))))
+      face
+    (list face)))
+
+(defun notmuch-apply-face (object face &optional below start end)
+  "Combine FACE into the 'face text property of OBJECT between START and END.
+
+This function combines FACE with any existing faces between START
+and END in OBJECT.  Attributes specified by FACE take precedence
+over existing attributes unless BELOW is non-nil.
+
+OBJECT may be a string, a buffer, or nil (which means the current
+buffer).  If object is a string, START and END are 0-based;
+otherwise they are buffer positions (integers or markers).  FACE
+must be a face name (a symbol or string), a property list of face
+attributes, or a list of these.  If START and/or END are omitted,
+they default to the beginning/end of OBJECT.  For convenience
+when applied to strings, this returns OBJECT."
+
+  ;; A face property can have three forms: a face name (a string or
+  ;; symbol), a property list, or a list of these two forms.  In the
+  ;; list case, the faces will be combined, with the earlier faces
+  ;; taking precedent.  Here we canonicalize everything to list form
+  ;; to make it easy to combine.
+  (let ((pos (cond (start start)
+                  ((stringp object) 0)
+                  (t 1)))
+       (end (cond (end end)
+                  ((stringp object) (length object))
+                  (t (1+ (buffer-size object)))))
+       (face-list (notmuch-face-ensure-list-form face)))
+    (while (< pos end)
+      (let* ((cur (get-text-property pos 'face object))
+            (cur-list (notmuch-face-ensure-list-form cur))
+            (new (cond ((null cur-list) face)
+                       (below (append cur-list face-list))
+                       (t (append face-list cur-list))))
+            (next (next-single-property-change pos 'face object end)))
+       (put-text-property pos next 'face new object)
+       (setq pos next))))
+  object)
+
+(defun notmuch-map-text-property (start end prop func &optional object)
+  "Transform text property PROP using FUNC.
+
+Applies FUNC to each distinct value of the text property PROP
+between START and END of OBJECT, setting PROP to the value
+returned by FUNC."
+  (while (< start end)
+    (let ((value (get-text-property start prop object))
+         (next (next-single-property-change start prop object end)))
+      (put-text-property start next prop (funcall func value) object)
+      (setq start next))))
+
+(defun notmuch-logged-error (msg &optional extra)
+  "Log MSG and EXTRA to *Notmuch errors* and signal MSG.
+
+This logs MSG and EXTRA to the *Notmuch errors* buffer and
+signals MSG as an error.  If EXTRA is non-nil, text referring the
+user to the *Notmuch errors* buffer will be appended to the
+signaled error.  This function does not return."
+
+  (with-current-buffer (get-buffer-create "*Notmuch errors*")
+    (goto-char (point-max))
+    (unless (bobp)
+      (newline))
+    (save-excursion
+      (insert "[" (current-time-string) "]\n" msg)
+      (unless (bolp)
+       (newline))
+      (when extra
+       (insert extra)
+       (unless (bolp)
+         (newline)))))
+  (error "%s" (concat msg (when extra
+                           " (see *Notmuch errors* for more details)"))))
+
+(defun notmuch-check-async-exit-status (proc msg &optional command err)
+  "If PROC exited abnormally, pop up an error buffer and signal an error.
+
+This is a wrapper around `notmuch-check-exit-status' for
+asynchronous process sentinels.  PROC and MSG must be the
+arguments passed to the sentinel.  COMMAND and ERR, if provided,
+are passed to `notmuch-check-exit-status'.  If COMMAND is not
+provided, it is taken from `process-command'."
+  (let ((exit-status
+        (case (process-status proc)
+          ((exit) (process-exit-status proc))
+          ((signal) msg))))
+    (when exit-status
+      (notmuch-check-exit-status exit-status (or command (process-command proc))
+                                nil err))))
+
+(defun notmuch-check-exit-status (exit-status command &optional output err)
+  "If EXIT-STATUS is non-zero, pop up an error buffer and signal an error.
+
+If EXIT-STATUS is non-zero, pop up a notmuch error buffer
+describing the error and signal an Elisp error.  EXIT-STATUS must
+be a number indicating the exit status code of a process or a
+string describing the signal that terminated the process (such as
+returned by `call-process').  COMMAND must be a list giving the
+command and its arguments.  OUTPUT, if provided, is a string
+giving the output of command.  ERR, if provided, is the error
+output of command.  OUTPUT and ERR will be included in the error
+message."
+
+  (cond
+   ((eq exit-status 0) t)
+   ((eq exit-status 20)
+    (notmuch-logged-error "notmuch CLI version mismatch
+Emacs requested an older output format than supported by the notmuch CLI.
+You may need to restart Emacs or upgrade your notmuch Emacs package."))
+   ((eq exit-status 21)
+    (notmuch-logged-error "notmuch CLI version mismatch
+Emacs requested a newer output format than supported by the notmuch CLI.
+You may need to restart Emacs or upgrade your notmuch package."))
+   (t
+    (let* ((command-string
+           (mapconcat (lambda (arg)
+                        (shell-quote-argument
+                         (cond ((stringp arg) arg)
+                               ((symbolp arg) (symbol-name arg))
+                               (t "*UNKNOWN ARGUMENT*"))))
+                      command " "))
+          (extra
+           (concat "command: " command-string "\n"
+            (if (integerp exit-status)
+                (format "exit status: %s\n" exit-status)
+              (format "exit signal: %s\n" exit-status))
+            (when err
+              (concat "stderr:\n" err))
+            (when output
+              (concat "stdout:\n" output)))))
+       (if err
+           ;; We have an error message straight from the CLI.
+           (notmuch-logged-error
+            (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra)
+         ;; We only have combined output from the CLI; don't inundate
+         ;; the user with it.  Mimic `process-lines'.
+         (notmuch-logged-error (format "%s exited with status %s"
+                                       (car command) exit-status)
+                               extra))
+       ;; `notmuch-logged-error' does not return.
+       ))))
+
+(defun notmuch-call-notmuch--helper (destination args)
+  "Helper for synchronous notmuch invocation commands.
+
+This wraps `call-process'.  DESTINATION has the same meaning as
+for `call-process'.  ARGS is as described for
+`notmuch-call-notmuch-process'."
+
+  (let (stdin-string)
+    (while (keywordp (car args))
+      (case (car args)
+       (:stdin-string (setq stdin-string (cadr args)
+                            args (cddr args)))
+       (otherwise
+        (error "Unknown keyword argument: %s" (car args)))))
+    (if (null stdin-string)
+       (apply #'call-process notmuch-command nil destination nil args)
+      (insert stdin-string)
+      (apply #'call-process-region (point-min) (point-max)
+            notmuch-command t destination nil args))))
+
+(defun notmuch-call-notmuch-process (&rest args)
+  "Synchronously invoke `notmuch-command' with ARGS.
+
+The caller may provide keyword arguments before ARGS.  Currently
+supported keyword arguments are:
+
+  :stdin-string STRING - Write STRING to stdin
+
+If notmuch exits with a non-zero status, output from the process
+will appear in a buffer named \"*Notmuch errors*\" and an error
+will be signaled."
+  (with-temp-buffer
+    (let ((status (notmuch-call-notmuch--helper t args)))
+      (notmuch-check-exit-status status (cons notmuch-command args)
+                                (buffer-string)))))
+
+(defun notmuch-call-notmuch-sexp (&rest args)
+  "Invoke `notmuch-command' with ARGS and return the parsed S-exp output.
+
+This is equivalent to `notmuch-call-notmuch-process', but parses
+notmuch's output as an S-expression and returns the parsed value.
+Like `notmuch-call-notmuch-process', if notmuch exits with a
+non-zero status, this will report its output and signal an
+error."
+
+  (with-temp-buffer
+    (let ((err-file (make-temp-file "nmerr")))
+      (unwind-protect
+         (let ((status (notmuch-call-notmuch--helper (list t err-file) args))
+               (err (with-temp-buffer
+                      (insert-file-contents err-file)
+                      (unless (eobp)
+                        (buffer-string)))))
+           (notmuch-check-exit-status status (cons notmuch-command args)
+                                      (buffer-string) err)
+           (goto-char (point-min))
+           (read (current-buffer)))
+       (delete-file err-file)))))
+
+(defun notmuch-start-notmuch (name buffer sentinel &rest args)
+  "Start and return an asynchronous notmuch command.
+
+This starts and returns an asynchronous process running
+`notmuch-command' with ARGS.  The exit status is checked via
+`notmuch-check-async-exit-status'.  Output written to stderr is
+redirected and displayed when the process exits (even if the
+process exits successfully).  NAME and BUFFER are the same as in
+`start-process'.  SENTINEL is a process sentinel function to call
+when the process exits, or nil for none.  The caller must *not*
+invoke `set-process-sentinel' directly on the returned process,
+as that will interfere with the handling of stderr and the exit
+status."
+
+  (let (err-file err-buffer proc err-proc
+       ;; Find notmuch using Emacs' `exec-path'
+       (command (or (executable-find notmuch-command)
+                    (error "Command not found: %s" notmuch-command))))
+    (if (fboundp 'make-process)
+       (progn
+         (setq err-buffer (generate-new-buffer " *notmuch-stderr*"))
+         ;; Emacs 25 and newer has `make-process', which allows
+         ;; redirecting stderr independently from stdout to a
+         ;; separate buffer. As this allows us to avoid using a
+         ;; temporary file and shell invocation, use it when
+         ;; available.
+         (setq proc (make-process
+                     :name name
+                     :buffer buffer
+                     :command (cons command args)
+                     :connection-type 'pipe
+                     :stderr err-buffer)
+               err-proc (get-buffer-process err-buffer))
+         (process-put proc 'err-buffer err-buffer)
+
+         (process-put err-proc 'err-file err-file)
+         (process-put err-proc 'err-buffer err-buffer)
+         (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel))
+
+      ;; On Emacs versions before 25, there is no way to capture
+      ;; stdout and stderr separately for asynchronous processes, or
+      ;; even to redirect stderr to a file, so we use a trivial shell
+      ;; wrapper to send stderr to a temporary file and clean things
+      ;; up in the sentinel.
+      (setq err-file (make-temp-file "nmerr"))
+      (let ((process-connection-type nil)) ;; Use a pipe
+       (setq proc (apply #'start-process name buffer
+                         "/bin/sh" "-c"
+                         "exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
+                         command err-file args)))
+      (process-put proc 'err-file err-file))
+
+    (process-put proc 'sub-sentinel sentinel)
+    (process-put proc 'real-command (cons notmuch-command args))
+    (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
+    proc))
+
+(defun notmuch-start-notmuch-sentinel (proc event)
+  "Process sentinel function used by `notmuch-start-notmuch'."
+  (let* ((err-file (process-get proc 'err-file))
+        (err-buffer (or (process-get proc 'err-buffer)
+                        (find-file-noselect err-file)))
+        (err (when (not (zerop (buffer-size err-buffer)))
+               (with-current-buffer err-buffer (buffer-string))))
+        (sub-sentinel (process-get proc 'sub-sentinel))
+        (real-command (process-get proc 'real-command)))
+    (condition-case err
+       (progn
+         ;; Invoke the sub-sentinel, if any
+         (when sub-sentinel
+           (funcall sub-sentinel proc event))
+         ;; Check the exit status.  This will signal an error if the
+         ;; exit status is non-zero.  Don't do this if the process
+         ;; buffer is dead since that means Emacs killed the process
+         ;; and there's no point in telling the user that (but we
+         ;; still check for and report stderr output below).
+         (when (buffer-live-p (process-buffer proc))
+           (notmuch-check-async-exit-status proc event real-command err))
+         ;; If that didn't signal an error, then any error output was
+         ;; really warning output.  Show warnings, if any.
+         (let ((warnings
+                (when err
+                  (with-current-buffer err-buffer
+                    (goto-char (point-min))
+                    (end-of-line)
+                    ;; Show first line; stuff remaining lines in the
+                    ;; errors buffer.
+                    (let ((l1 (buffer-substring (point-min) (point))))
+                      (skip-chars-forward "\n")
+                      (cons l1 (unless (eobp)
+                                 (buffer-substring (point) (point-max)))))))))
+           (when warnings
+             (notmuch-logged-error (car warnings) (cdr warnings)))))
+      (error
+       ;; Emacs behaves strangely if an error escapes from a sentinel,
+       ;; so turn errors into messages.
+       (message "%s" (error-message-string err))))
+    (when err-file (ignore-errors (delete-file err-file)))))
+
+(defun notmuch-start-notmuch-error-sentinel (proc event)
+  (let* ((err-file (process-get proc 'err-file))
+        ;; When `make-process' is available, use the error buffer
+        ;; associated with the process, otherwise the error file.
+        (err-buffer (or (process-get proc 'err-buffer)
+                        (find-file-noselect err-file))))
+    (when err-buffer (kill-buffer err-buffer))))
+
+;; This variable is used only buffer local, but it needs to be
+;; declared globally first to avoid compiler warnings.
+(defvar notmuch-show-process-crypto nil)
+(make-variable-buffer-local 'notmuch-show-process-crypto)
+
+(provide 'notmuch-lib)
+
+;; Local Variables:
+;; byte-compile-warnings: (not cl-functions)
+;; End:
+
+;;; notmuch-lib.el ends here
diff --git a/emacs/notmuch-logo.png b/emacs/notmuch-logo.png
new file mode 100644 (file)
index 0000000..53b5e6a
Binary files /dev/null and b/emacs/notmuch-logo.png differ
diff --git a/emacs/notmuch-maildir-fcc.el b/emacs/notmuch-maildir-fcc.el
new file mode 100644 (file)
index 0000000..1551e8b
--- /dev/null
@@ -0,0 +1,375 @@
+;;; notmuch-maildir-fcc.el ---
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation; either version 2, or (at your
+;; 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 GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; To use this as the fcc handler for message-mode,
+;; customize the notmuch-fcc-dirs variable
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'message)
+
+(require 'notmuch-lib)
+
+(defvar notmuch-maildir-fcc-count 0)
+
+(defcustom notmuch-fcc-dirs "sent"
+ "Determines the Fcc Header which says where to save outgoing mail.
+
+Three types of values are permitted:
+
+- nil: no Fcc header is added,
+
+- a string: the value of `notmuch-fcc-dirs' is the Fcc header to
+  be used.
+
+- a list: the folder is chosen based on the From address of the
+  current message using a list of regular expressions and
+  corresponding folders:
+
+     ((\"Sebastian@SSpaeth.de\" . \"privat\")
+      (\"spaetz@sspaeth.de\" . \"OUTBOX.OSS\")
+      (\".*\" . \"defaultinbox\"))
+
+  If none of the regular expressions match the From address, no
+  Fcc header will be added.
+
+If `notmuch-maildir-use-notmuch-insert' is set (the default) then
+the header should be of the form \"folder +tag1 -tag2\" where
+folder is the folder (relative to the notmuch mailstore) to store
+the message in, and tag1 and tag2 are tag changes to apply to the
+stored message. This string is split using `split-string-and-unquote',
+so a folder name containing spaces can be specified by
+quoting each space with an immediately preceding backslash
+or surrounding the entire folder name in double quotes.
+
+If `notmuch-maildir-use-notmuch-insert' is nil then the Fcc
+header should be the directory where the message should be
+saved. A relative directory will be understood to specify a
+directory within the notmuch mail store, (as set by the
+database.path option in the notmuch configuration file).
+
+In all cases you will be prompted to create the folder or
+directory if it does not exist yet when sending a mail."
+
+ :type '(choice
+        (const :tag "No FCC header" nil)
+        (string :tag "A single folder")
+        (repeat :tag "A folder based on the From header"
+                (cons regexp (string :tag "Folder"))))
+ :require 'notmuch-fcc-initialization
+ :group 'notmuch-send)
+
+(defcustom notmuch-maildir-use-notmuch-insert 't
+  "Should fcc use notmuch insert instead of simple fcc"
+  :type '(choice :tag "Fcc Method"
+                (const :tag "Use notmuch insert" t)
+                (const :tag "Use simple fcc" nil))
+  :group 'notmuch-send)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions which set up the fcc header in the message buffer.
+
+(defun notmuch-fcc-header-setup ()
+  "Add an Fcc header to the current message buffer.
+
+Sets the Fcc header based on the values of `notmuch-fcc-dirs'.
+
+Originally intended to be use a hook function, but now called directly
+by notmuch-mua-mail"
+
+  (let ((subdir
+        (cond
+         ((or (not notmuch-fcc-dirs)
+              (message-field-value "Fcc"))
+          ;; Nothing set or an existing header.
+          nil)
+
+         ((stringp notmuch-fcc-dirs)
+          notmuch-fcc-dirs)
+
+         ((and (listp notmuch-fcc-dirs)
+               (stringp (car notmuch-fcc-dirs)))
+          ;; Old style - no longer works.
+          (error "Invalid `notmuch-fcc-dirs' setting (old style)"))
+
+         ((listp notmuch-fcc-dirs)
+          (let* ((from (message-field-value "From"))
+                 (match
+                  (catch 'first-match
+                    (dolist (re-folder notmuch-fcc-dirs)
+                      (when (string-match-p (car re-folder) from)
+                        (throw 'first-match re-folder))))))
+            (if match
+                (cdr match)
+              (message "No Fcc header added.")
+              nil)))
+
+         (t
+          (error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
+
+    (when subdir
+      (if notmuch-maildir-use-notmuch-insert
+         (notmuch-maildir-add-notmuch-insert-style-fcc-header subdir)
+       (notmuch-maildir-add-file-style-fcc-header subdir)))))
+
+(defun notmuch-maildir-add-notmuch-insert-style-fcc-header (subdir)
+  ;; Notmuch insert does not accept absolute paths, so check the user
+  ;; really want this header inserted.
+
+  (when (or (not (= (elt subdir 0) ?/))
+           (y-or-n-p (format "Fcc header %s is an absolute path and notmuch insert is requested.\nInsert header anyway? "
+                             subdir)))
+    (message-add-header (concat "Fcc: " subdir))))
+
+(defun notmuch-maildir-add-file-style-fcc-header (subdir)
+  (message-add-header
+   (concat "Fcc: "
+          (file-truename
+           ;; If the resulting directory is not an absolute path,
+           ;; prepend the standard notmuch database path.
+           (if (= (elt subdir 0) ?/)
+               subdir
+             (concat (notmuch-database-path) "/" subdir))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message either using notmuch insert or file
+;; fcc. First functions common to the two cases.
+
+(defmacro with-temporary-notmuch-message-buffer (&rest body)
+  "Set-up a temporary copy of the current message-mode buffer."
+  `(let ((case-fold-search t)
+        (buf (current-buffer))
+        (mml-externalize-attachments message-fcc-externalize-attachments))
+     (with-current-buffer (get-buffer-create " *message temp*")
+       (erase-buffer)
+       (insert-buffer-substring buf)
+       ,@body)))
+
+(defun notmuch-maildir-setup-message-for-saving ()
+  "Setup message for saving. Should be called on a temporary copy.
+
+This is taken from the function message-do-fcc."
+  (message-encode-message-body)
+  (save-restriction
+    (message-narrow-to-headers)
+    (let ((mail-parse-charset message-default-charset))
+      (mail-encode-encoded-word-buffer)))
+  (goto-char (point-min))
+  (when (re-search-forward
+        (concat "^" (regexp-quote mail-header-separator) "$")
+        nil t)
+    (replace-match "" t t )))
+
+(defun notmuch-maildir-message-do-fcc ()
+  "Process Fcc headers in the current buffer.
+
+This is a rearranged version of message mode's message-do-fcc."
+  (let (list file)
+    (save-excursion
+      (save-restriction
+       (message-narrow-to-headers)
+       (setq file (message-fetch-field "fcc" t)))
+      (when file
+       (with-temporary-notmuch-message-buffer
+        (save-restriction
+          (message-narrow-to-headers)
+          (while (setq file (message-fetch-field "fcc" t))
+            (push file list)
+            (message-remove-header "fcc" nil t)))
+        (notmuch-maildir-setup-message-for-saving)
+        ;; Process FCC operations.
+        (while list
+          (setq file (pop list))
+          (notmuch-fcc-handler file))
+        (kill-buffer (current-buffer)))))))
+
+(defun notmuch-fcc-handler (fcc-header)
+  "Store message with notmuch insert or normal (file) fcc.
+
+If `notmuch-maildir-use-notmuch-insert` is set then store the
+message using notmuch insert. Otherwise store the message using
+normal fcc."
+  (message "Doing Fcc...")
+  (if notmuch-maildir-use-notmuch-insert
+      (notmuch-maildir-fcc-with-notmuch-insert fcc-header)
+    (notmuch-maildir-fcc-file-fcc fcc-header)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message using notmuch insert.
+
+(defun notmuch-maildir-notmuch-insert-current-buffer (folder &optional create tags)
+  "Use notmuch insert to put the current buffer in the database.
+
+This inserts the current buffer as a message into the notmuch
+database in folder FOLDER. If CREATE is non-nil it will supply
+the --create-folder flag to create the folder if necessary. TAGS
+should be a list of tag changes to apply to the inserted message."
+  (let* ((args (append (when create (list "--create-folder"))
+                      (list (concat "--folder=" folder))
+                      tags)))
+    (apply 'notmuch-call-notmuch-process
+          :stdin-string (buffer-string) "insert" args)))
+
+(defun notmuch-maildir-fcc-with-notmuch-insert (fcc-header &optional create)
+  "Store message with notmuch insert.
+
+The fcc-header should be of the form \"folder +tag1 -tag2\" where
+folder is the folder (relative to the notmuch mailstore) to store
+the message in, and tag1 and tag2 are tag changes to apply to the
+stored message. This string is split using `split-string-and-unquote',
+so a folder name containing spaces can be specified by
+quoting each space with an immediately preceding backslash
+or surrounding the entire folder name in double quotes.
+
+If CREATE is non-nil then create the folder if necessary."
+  (let* ((args (split-string-and-unquote fcc-header))
+        (folder (car args))
+        (tags (cdr args)))
+    (condition-case nil
+       (notmuch-maildir-notmuch-insert-current-buffer folder create tags)
+      ;; Since there are many reasons notmuch insert could fail, e.g.,
+      ;; locked database, non-existent folder (which could be due to a
+      ;; typo, or just the user want a new folder, let the user decide
+      ;; how to deal with it.
+      (error
+       (let ((response (notmuch-read-char-choice
+                       "Insert failed: (r)etry, (c)reate folder, (i)gnore, or (e)dit the header? "
+                       '(?r ?c ?i ?e))))
+        (case response
+              (?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
+              (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
+              (?i 't)
+              (?e (notmuch-maildir-fcc-with-notmuch-insert
+                   (read-from-minibuffer "Fcc header: " fcc-header)))))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions for saving a message using file fcc.
+
+(defun notmuch-maildir-fcc-host-fixer (hostname)
+  (replace-regexp-in-string "/\\|:"
+                           (lambda (s)
+                             (cond ((string-equal s "/") "\\057")
+                                   ((string-equal s ":") "\\072")
+                                   (t s)))
+                           hostname
+                           t
+                           t))
+
+(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))))
+     (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1))
+     (format "%d.%d_%d_%d.%s"
+            ftime
+            (emacs-pid)
+            microseconds
+            notmuch-maildir-fcc-count
+            hostname)))
+
+(defun notmuch-maildir-fcc-dir-is-maildir-p (dir)
+  (and (file-exists-p (concat dir "/cur/"))
+       (file-exists-p (concat dir "/new/"))
+       (file-exists-p (concat dir "/tmp/"))))
+
+(defun notmuch-maildir-fcc-create-maildir (path)
+  (cond ((or (not (file-exists-p path)) (file-directory-p path))
+        (make-directory (concat path "/cur/") t)
+        (make-directory (concat path "/new/") t)
+        (make-directory (concat path "/tmp/") t))
+       ((file-regular-p path)
+        (error "%s is a file. Can't create maildir." path))
+       (t
+        (error "I don't know how to create a maildir here"))))
+
+(defun notmuch-maildir-fcc-save-buffer-to-tmp (destdir)
+  "Returns the msg id of the message written to the temp directory
+if successful, nil if not."
+  (let ((msg-id (notmuch-maildir-fcc-make-uniq-maildir-id)))
+    (while (file-exists-p (concat destdir "/tmp/" msg-id))
+      (setq msg-id (notmuch-maildir-fcc-make-uniq-maildir-id)))
+    (cond ((notmuch-maildir-fcc-dir-is-maildir-p destdir)
+          (write-file (concat destdir "/tmp/" msg-id))
+          msg-id)
+         (t
+          (error (format "Can't write to %s. Not a maildir."
+                         destdir))
+          nil))))
+
+(defun notmuch-maildir-fcc-move-tmp-to-new (destdir msg-id)
+  (add-name-to-file
+   (concat destdir "/tmp/" msg-id)
+   (concat destdir "/new/" msg-id ":2,")))
+
+(defun notmuch-maildir-fcc-move-tmp-to-cur (destdir msg-id &optional mark-seen)
+  (add-name-to-file
+   (concat destdir "/tmp/" msg-id)
+   (concat destdir "/cur/" msg-id ":2," (when mark-seen "S"))))
+
+(defun notmuch-maildir-fcc-file-fcc (fcc-header)
+  "Write the message to the file specified by FCC-HEADER.
+
+It offers the user a chance to correct the header, or filesystem,
+if needed."
+  (if (notmuch-maildir-fcc-dir-is-maildir-p fcc-header)
+      (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't)
+    ;; The fcc-header is not a valid maildir see if the user wants to
+    ;; fix it in some way.
+    (let* ((prompt (format "Fcc %s is not a maildir: (r)etry, (c)reate folder, (i)gnore, or  (e)dit the header? "
+                          fcc-header))
+           (response (notmuch-read-char-choice prompt '(?r ?c ?i ?e))))
+        (case response
+              (?r (notmuch-maildir-fcc-file-fcc fcc-header))
+              (?c (if (file-writable-p fcc-header)
+                      (notmuch-maildir-fcc-create-maildir fcc-header)
+                    (message "No permission to create %s." fcc-header)
+                    (sit-for 2))
+                  (notmuch-maildir-fcc-file-fcc fcc-header))
+              (?i 't)
+              (?e (notmuch-maildir-fcc-file-fcc
+                   (read-from-minibuffer "Fcc header: " fcc-header)))))))
+
+(defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen)
+  "Writes the current buffer to maildir destdir. If mark-seen is
+non-nil, it will write it to cur/, and mark it as read. It should
+return t if successful, and nil otherwise."
+  (let ((orig-buffer (buffer-name)))
+    (with-temp-buffer
+      (insert-buffer-substring orig-buffer)
+      (catch 'link-error
+       (let ((msg-id (notmuch-maildir-fcc-save-buffer-to-tmp destdir)))
+         (when msg-id
+           (cond (mark-seen
+                  (condition-case err
+                      (notmuch-maildir-fcc-move-tmp-to-cur destdir msg-id t)
+                    (file-already-exists
+                     (throw 'link-error nil))))
+                 (t
+                  (condition-case err
+                      (notmuch-maildir-fcc-move-tmp-to-new destdir msg-id)
+                    (file-already-exists
+                     (throw 'link-error nil))))))
+         (delete-file (concat destdir "/tmp/" msg-id))))
+      t)))
+
+(provide 'notmuch-maildir-fcc)
+
+;;; notmuch-maildir-fcc.el ends here
diff --git a/emacs/notmuch-message.el b/emacs/notmuch-message.el
new file mode 100644 (file)
index 0000000..55e4cfe
--- /dev/null
@@ -0,0 +1,52 @@
+;;; notmuch-message.el --- message-mode functions specific to notmuch
+;;
+;; Copyright © Jesse Rosenthal
+;;
+;; 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: Jesse Rosenthal <jrosenthal@jhu.edu>
+
+;;; Code:
+
+(require 'message)
+(require 'notmuch-tag)
+(require 'notmuch-mua)
+
+(defcustom notmuch-message-replied-tags '("+replied")
+  "List of tag changes to apply to a message when it has been replied to.
+
+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 replied to.
+
+For example, if you wanted to add a \"replied\" tag and remove
+the \"inbox\" and \"todo\" tags, you would set:
+    (\"+replied\" \"-inbox\" \"-todo\"\)"
+  :type '(repeat string)
+  :group 'notmuch-send)
+
+(defun notmuch-message-mark-replied ()
+  ;; get the in-reply-to header and parse it for the message id.
+  (let ((rep (mail-header-parse-addresses (message-field-value "In-Reply-To"))))
+    (when (and notmuch-message-replied-tags rep)
+      (notmuch-tag (notmuch-id-to-query (car (car rep)))
+              (notmuch-tag-change-list notmuch-message-replied-tags)))))
+
+(add-hook 'message-send-hook 'notmuch-message-mark-replied)
+
+(provide 'notmuch-message)
+
+;;; notmuch-message.el ends here
diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el
new file mode 100644 (file)
index 0000000..df2ac44
--- /dev/null
@@ -0,0 +1,585 @@
+;;; notmuch-mua.el --- emacs style mail-user-agent
+;;
+;; Copyright © David Edmondson
+;;
+;; 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: David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(require 'message)
+(require 'mm-view)
+(require 'format-spec)
+
+(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" ())
+
+;;
+
+(defcustom notmuch-mua-send-hook '(notmuch-mua-message-send-hook)
+  "Hook run before sending messages."
+  :type 'hook
+  :group 'notmuch-send
+  :group 'notmuch-hooks)
+
+(defcustom notmuch-mua-compose-in 'current-window
+  (concat
+   "Where to create the mail buffer used to compose a new message.
+Possible values are `current-window' (default), `new-window' and
+`new-frame'. If set to `current-window', the mail buffer will be
+displayed in the current window, so the old buffer will be
+restored when the mail buffer is killed. If set to `new-window'
+or `new-frame', the mail buffer will be displayed in a new
+window/frame that will be destroyed when the buffer is killed.
+You may want to customize `message-kill-buffer-on-exit'
+accordingly."
+   (when (< emacs-major-version 24)
+          " Due to a known bug in Emacs 23, you should not set
+this to `new-window' if `message-kill-buffer-on-exit' is
+disabled: this would result in an incorrect behavior."))
+  :group 'notmuch-send
+  :type '(choice (const :tag "Compose in the current window" current-window)
+                (const :tag "Compose mail in a new window"  new-window)
+                (const :tag "Compose mail in a new frame"   new-frame)))
+
+(defcustom notmuch-mua-user-agent-function nil
+  "Function used to generate a `User-Agent:' string. If this is
+`nil' then no `User-Agent:' will be generated."
+  :type '(choice (const :tag "No user agent string" nil)
+                (const :tag "Full" notmuch-mua-user-agent-full)
+                (const :tag "Notmuch" notmuch-mua-user-agent-notmuch)
+                (const :tag "Emacs" notmuch-mua-user-agent-emacs)
+                (function :tag "Custom user agent function"
+                          :value notmuch-mua-user-agent-full))
+  :group 'notmuch-send)
+
+(defcustom notmuch-mua-hidden-headers nil
+  "Headers that are added to the `message-mode' hidden headers
+list."
+  :type '(repeat string)
+  :group 'notmuch-send)
+
+(defgroup notmuch-reply nil
+  "Replying to messages in notmuch"
+  :group 'notmuch)
+
+(defcustom notmuch-mua-cite-function 'message-cite-original
+  "*Function for citing an original message.
+Predefined functions include `message-cite-original' and
+`message-cite-original-without-signature'.
+Note that these functions use `mail-citation-hook' if that is non-nil."
+  :type '(radio (function-item message-cite-original)
+               (function-item message-cite-original-without-signature)
+               (function-item sc-cite-original)
+               (function :tag "Other"))
+  :link '(custom-manual "(message)Insertion Variables")
+  :group 'notmuch-reply)
+
+(defcustom notmuch-mua-reply-insert-header-p-function
+  'notmuch-show-reply-insert-header-p-never
+  "Function to decide which parts get a header when replying.
+
+This function specifies which parts of a mime message with
+multiple parts get a header."
+  :type '(radio (const :tag "No part headers"
+                              notmuch-show-reply-insert-header-p-never)
+               (const :tag "All except multipart/* and hidden parts"
+                              notmuch-show-reply-insert-header-p-trimmed)
+               (const :tag "Only for included text parts"
+                              notmuch-show-reply-insert-header-p-minimal)
+               (const :tag "Exactly as in show view"
+                              notmuch-show-insert-header-p)
+               (function :tag "Other"))
+  :group 'notmuch-reply)
+
+;;
+
+(defun notmuch-mua-get-switch-function ()
+  "Get a switch function according to `notmuch-mua-compose-in'."
+  (cond ((eq notmuch-mua-compose-in 'current-window)
+        'switch-to-buffer)
+       ((eq notmuch-mua-compose-in 'new-window)
+        'switch-to-buffer-other-window)
+       ((eq notmuch-mua-compose-in 'new-frame)
+        'switch-to-buffer-other-frame)
+       (t (error "Invalid value for `notmuch-mua-compose-in'"))))
+
+(defun notmuch-mua-maybe-set-window-dedicated ()
+  "Set the selected window as dedicated according to
+`notmuch-mua-compose-in'."
+  (when (or (eq notmuch-mua-compose-in 'new-frame)
+           (eq notmuch-mua-compose-in 'new-window))
+    (set-window-dedicated-p (selected-window) t)))
+
+(defun notmuch-mua-user-agent-full ()
+  "Generate a `User-Agent:' string suitable for notmuch."
+  (concat (notmuch-mua-user-agent-notmuch)
+         " "
+         (notmuch-mua-user-agent-emacs)))
+
+(defun notmuch-mua-user-agent-notmuch ()
+  "Generate a `User-Agent:' string suitable for notmuch."
+  (let ((notmuch-version (if (string= notmuch-emacs-version "unknown")
+                            (notmuch-cli-version)
+                          notmuch-emacs-version)))
+    (concat "Notmuch/" notmuch-version " (https://notmuchmail.org)")))
+
+(defun notmuch-mua-user-agent-emacs ()
+  "Generate a `User-Agent:' string suitable for notmuch."
+  (concat "Emacs/" emacs-version " (" system-configuration ")"))
+
+(defun notmuch-mua-add-more-hidden-headers ()
+  "Add some headers to the list that are hidden by default."
+  (mapc (lambda (header)
+         (when (not (member header message-hidden-headers))
+           (push header message-hidden-headers)))
+       notmuch-mua-hidden-headers))
+
+(defun notmuch-mua-reply-crypto (parts)
+  "Add mml sign-encrypt flag if any part of original message is encrypted."
+  (loop for part in parts
+       if (notmuch-match-content-type (plist-get part :content-type) "multipart/encrypted")
+         do (mml-secure-message-sign-encrypt)
+       else if (notmuch-match-content-type (plist-get part :content-type) "multipart/*")
+         do (notmuch-mua-reply-crypto (plist-get part :content))))
+
+;; There is a bug in emacs 23's message.el that results in a newline
+;; not being inserted after the References header, so the next header
+;; is concatenated to the end of it. This function fixes the problem,
+;; while guarding against the possibility that some current or future
+;; version of emacs has the bug fixed.
+(defun notmuch-mua-insert-references (original-func header references)
+  (funcall original-func header references)
+  (unless (bolp) (insert "\n")))
+
+(defun notmuch-mua-reply (query-string &optional sender reply-all)
+  (let ((args '("reply" "--format=sexp" "--format-version=4"))
+       (process-crypto notmuch-show-process-crypto)
+       reply
+       original)
+    (when process-crypto
+      (setq args (append args '("--decrypt=true"))))
+
+    (if reply-all
+       (setq args (append args '("--reply-to=all")))
+      (setq args (append args '("--reply-to=sender"))))
+    (setq args (append args (list query-string)))
+
+    ;; Get the reply object as SEXP, and parse it into an elisp object.
+    (setq reply (apply #'notmuch-call-notmuch-sexp args))
+
+    ;; Extract the original message to simplify the following code.
+    (setq original (plist-get reply :original))
+
+    ;; Extract the headers of both the reply and the original message.
+    (let* ((original-headers (plist-get original :headers))
+          (reply-headers (plist-get reply :reply-headers)))
+
+      ;; If sender is non-nil, set the From: header to its value.
+      (when sender
+       (plist-put reply-headers :From sender))
+      (let
+         ;; Overlay the composition window on that being used to read
+         ;; the original message.
+         ((same-window-regexps '("\\*mail .*")))
+
+       ;; We modify message-header-format-alist to get around a bug in message.el.
+       ;; See the comment above on notmuch-mua-insert-references.
+       (let ((message-header-format-alist
+              (loop for pair in message-header-format-alist
+                    if (eq (car pair) 'References)
+                    collect (cons 'References
+                                  (apply-partially
+                                   'notmuch-mua-insert-references
+                                   (cdr pair)))
+                    else
+                    collect pair)))
+         (notmuch-mua-mail (plist-get reply-headers :To)
+                           (notmuch-sanitize (plist-get reply-headers :Subject))
+                           (notmuch-headers-plist-to-alist reply-headers)
+                           nil (notmuch-mua-get-switch-function))))
+
+      ;; Insert the message body - but put it in front of the signature
+      ;; if one is present, and after any other content
+      ;; message*setup-hooks may have added to the message body already.
+      (save-restriction
+       (message-goto-body)
+       (narrow-to-region (point) (point-max))
+       (goto-char (point-max))
+       (if (re-search-backward message-signature-separator nil t)
+           (if message-signature-insert-empty-line
+               (forward-line -1))
+         (goto-char (point-max))))
+
+      (let ((from (plist-get original-headers :From))
+           (date (plist-get original-headers :Date))
+           (start (point)))
+
+       ;; notmuch-mua-cite-function constructs a citation line based
+       ;; on the From and Date headers of the original message, which
+       ;; are assumed to be in the buffer.
+       (insert "From: " from "\n")
+       (insert "Date: " date "\n\n")
+
+       (insert (with-temp-buffer
+                 (let
+                     ;; Don't attempt to clean up messages, excerpt
+                     ;; citations, etc. in the original message before
+                     ;; quoting.
+                     ((notmuch-show-insert-text/plain-hook nil)
+                      ;; Don't omit long parts.
+                      (notmuch-show-max-text-part-size 0)
+                      ;; Insert headers for parts as appropriate for replying.
+                      (notmuch-show-insert-header-p-function notmuch-mua-reply-insert-header-p-function)
+                      ;; 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).
+                   (letf (((symbol-function 'notmuch-crypto-insert-sigstatus-button) #'ignore)
+                          ((symbol-function 'notmuch-crypto-insert-encstatus-button) #'ignore))
+                         (notmuch-show-insert-body original (plist-get original :body) 0)
+                         (buffer-substring-no-properties (point-min) (point-max))))))
+
+       (set-mark (point))
+       (goto-char start)
+       ;; Quote the original message according to the user's configured style.
+       (funcall notmuch-mua-cite-function)))
+
+    ;; Crypto processing based crypto content of the original message
+    (when process-crypto
+      (notmuch-mua-reply-crypto (plist-get original :body))))
+
+  ;; Push mark right before signature, if any.
+  (message-goto-signature)
+  (unless (eobp)
+    (end-of-line -1))
+  (push-mark)
+
+  (message-goto-body)
+  (set-buffer-modified-p nil))
+
+(define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
+  "Notmuch message composition mode. Mostly like `message-mode'"
+  (notmuch-address-setup))
+
+(put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
+
+(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
+(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
+(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
+modified. This function is notmuch addaptation of
+`message-pop-to-buffer'."
+  (let ((buffer (get-buffer name)))
+    (if (and buffer
+            (buffer-name buffer))
+       (let ((window (get-buffer-window buffer 0)))
+         (if window
+             ;; Raise the frame already displaying the message buffer.
+             (progn
+               (gnus-select-frame-set-input-focus (window-frame window))
+               (select-window window))
+           (funcall switch-function buffer)
+           (set-buffer buffer))
+         (when (and (buffer-modified-p)
+                    (not (prog1
+                             (y-or-n-p
+                              "Message already being composed; erase? ")
+                           (message nil))))
+           (error "Message being composed")))
+      (funcall switch-function name)
+      (set-buffer name))
+    (erase-buffer)
+    (notmuch-message-mode)))
+
+(defun notmuch-mua-mail (&optional to subject other-headers continue
+                                  switch-function yank-action send-actions
+                                  return-action &rest ignored)
+  "Invoke the notmuch mail composition window."
+  (interactive)
+
+  (when notmuch-mua-user-agent-function
+    (let ((user-agent (funcall notmuch-mua-user-agent-function)))
+      (when (not (string= "" user-agent))
+       (push (cons 'User-Agent user-agent) other-headers))))
+
+  (unless (assq 'From other-headers)
+    (push (cons 'From (message-make-from
+                      (notmuch-user-name) (notmuch-user-primary-email))) other-headers))
+
+  (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
+                            (or switch-function (notmuch-mua-get-switch-function)))
+  (let ((headers
+        (append
+         ;; The following is copied from `message-mail'
+         `((To . ,(or to "")) (Subject . ,(or subject "")))
+         ;; C-h f compose-mail says that headers should be specified as
+         ;; (string . value); however all the rest of message expects
+         ;; headers to be symbols, not strings (eg message-header-format-alist).
+         ;; https://lists.gnu.org/archive/html/emacs-devel/2011-01/msg00337.html
+         ;; We need to convert any string input, eg from rmail-start-mail.
+         (dolist (h other-headers other-headers)
+           (if (stringp (car h)) (setcar h (intern (capitalize (car h))))))))
+       (args (list yank-action send-actions))
+       ;; Cause `message-setup-1' to do things relevant for mail,
+       ;; such as observe `message-default-mail-headers'.
+       (message-this-is-mail t))
+    ;; message-setup-1 in Emacs 23 does not accept return-action
+    ;; argument. Pass it only if it is supplied by the caller. This
+    ;; will never be the case when we're called by `compose-mail' in
+    ;; Emacs 23.
+    (when return-action (nconc args '(return-action)))
+    (apply 'message-setup-1 headers args))
+  (notmuch-fcc-header-setup)
+  (message-sort-headers)
+  (message-hide-headers)
+  (set-buffer-modified-p nil)
+  (notmuch-mua-maybe-set-window-dedicated)
+
+  (message-goto-to))
+
+(defcustom notmuch-identities nil
+  "Identities that can be used as the From: address when composing a new message.
+
+If this variable is left unset, then a list will be constructed from the
+name and addresses configured in the notmuch configuration file."
+  :type '(repeat string)
+  :group 'notmuch-send)
+
+(defcustom notmuch-always-prompt-for-sender nil
+  "Always prompt for the From: address when composing or forwarding a message.
+
+This is not taken into account when replying to a message, because in that case
+the From: header is already filled in by notmuch."
+  :type 'boolean
+  :group 'notmuch-send)
+
+(defvar notmuch-mua-sender-history nil)
+
+;; Workaround: Running `ido-completing-read' in emacs 23.1, 23.2 and 23.3
+;; without some explicit initialization fill freeze the operation.
+;; Hence, we advice `ido-completing-read' to ensure required initialization
+;; is done.
+(if (and (= emacs-major-version 23) (< emacs-minor-version 4))
+    (defadvice ido-completing-read (before notmuch-ido-mode-init activate)
+      (ido-init-completion-maps)
+      (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup)
+      (add-hook 'choose-completion-string-functions
+               'ido-choose-completion-string)
+      (ad-disable-advice 'ido-completing-read 'before 'notmuch-ido-mode-init)
+      (ad-activate 'ido-completing-read)))
+
+(defun notmuch-mua-prompt-for-sender ()
+  "Prompt for a sender from the user's configured identities."
+  (if notmuch-identities
+      (ido-completing-read "Send mail from: " notmuch-identities
+                          nil nil nil 'notmuch-mua-sender-history
+                          (car notmuch-identities))
+    (let* ((name (notmuch-user-name))
+          (addrs (cons (notmuch-user-primary-email)
+                       (notmuch-user-other-email)))
+          (address
+           (ido-completing-read (concat "Sender address for " name ": ") addrs
+                                nil nil nil 'notmuch-mua-sender-history
+                                (car addrs))))
+      (message-make-from name address))))
+
+(put 'notmuch-mua-new-mail 'notmuch-prefix-doc "... and prompt for sender")
+(defun notmuch-mua-new-mail (&optional prompt-for-sender)
+  "Compose new mail.
+
+If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
+the From: address first."
+  (interactive "P")
+  (let ((other-headers
+        (when (or prompt-for-sender notmuch-always-prompt-for-sender)
+          (list (cons 'From (notmuch-mua-prompt-for-sender))))))
+    (notmuch-mua-mail nil nil other-headers nil (notmuch-mua-get-switch-function))))
+
+(defun notmuch-mua-new-forward-messages (messages &optional prompt-for-sender)
+  "Compose a new message forwarding MESSAGES.
+
+If PROMPT-FOR-SENDER is non-nil, the user will be prompteed for
+the From: address."
+  (let* ((other-headers
+         (when (or prompt-for-sender notmuch-always-prompt-for-sender)
+           (list (cons 'From (notmuch-mua-prompt-for-sender)))))
+        forward-subject) ;; Comes from the first message and is
+                         ;; applied later.
+
+    ;; Generate the template for the outgoing message.
+    (notmuch-mua-mail nil "" other-headers nil (notmuch-mua-get-switch-function))
+
+    (save-excursion
+      ;; Insert all of the forwarded messages.
+      (mapc (lambda (id)
+             (let ((temp-buffer (get-buffer-create
+                                 (concat "*notmuch-fwd-raw-" id "*"))))
+               ;; Get the raw version of this message in the buffer.
+               (with-current-buffer temp-buffer
+                 (erase-buffer)
+                 (let ((coding-system-for-read 'no-conversion))
+                   (call-process notmuch-command nil t nil "show" "--format=raw" id))
+                 ;; Because we process the messages in reverse order,
+                 ;; always generate a forwarded subject, then use the
+                 ;; last (i.e. first) one.
+                 (setq forward-subject (message-make-forward-subject)))
+               ;; Make a copy ready to be forwarded in the
+               ;; composition buffer.
+               (message-forward-make-body temp-buffer)
+               ;; Kill the temporary buffer.
+               (kill-buffer temp-buffer)))
+           ;; `message-forward-make-body' always puts the message at
+           ;; the top, so do them in reverse order.
+           (reverse messages))
+
+      ;; Add in the appropriate subject.
+      (save-restriction
+       (message-narrow-to-headers)
+       (message-remove-header "Subject")
+       (message-add-header (concat "Subject: " forward-subject)))
+
+      ;; `message-forward-make-body' shows the User-agent header.  Hide
+      ;; it again.
+      (message-hide-headers)
+      (set-buffer-modified-p nil))))
+
+(defun notmuch-mua-new-reply (query-string &optional prompt-for-sender reply-all)
+  "Compose a reply to the message identified by QUERY-STRING.
+
+If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
+the From: address first.  If REPLY-ALL is non-nil, the message
+will be addressed to all recipients of the source message."
+
+;; In current emacs (24.3) select-active-regions is set to t by
+;; default. The reply insertion code sets the region to the quoted
+;; message to make it easy to delete (kill-region or C-w). These two
+;; things combine to put the quoted message in the primary selection.
+;;
+;; This is not what the user wanted and is a privacy risk (accidental
+;; pasting of the quoted message). We can avoid some of the problems
+;; by let-binding select-active-regions to nil. This fixes if the
+;; primary selection was previously in a non-emacs window but not if
+;; it was in an emacs window. To avoid the problem in the latter case
+;; we deactivate mark.
+
+  (let ((sender
+        (when prompt-for-sender
+          (notmuch-mua-prompt-for-sender)))
+       (select-active-regions nil))
+    (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")
+  (run-hooks 'notmuch-mua-send-hook)
+  (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")
+  (notmuch-mua-send-common arg 't))
+
+(defun notmuch-mua-send (&optional arg)
+  (interactive "P")
+  (notmuch-mua-send-common arg))
+
+(defun notmuch-mua-kill-buffer ()
+  (interactive)
+  (message-kill-buffer))
+
+(defun notmuch-mua-message-send-hook ()
+  "The default function used for `notmuch-mua-send-hook', this
+simply runs the corresponding `message-mode' hook functions."
+  (run-hooks 'message-send-hook))
+
+;;
+
+(define-mail-user-agent 'notmuch-user-agent
+  'notmuch-mua-mail 'notmuch-mua-send-and-exit
+  'notmuch-mua-kill-buffer 'notmuch-mua-send-hook)
+
+;; Add some more headers to the list that `message-mode' hides when
+;; composing a message.
+(notmuch-mua-add-more-hidden-headers)
+
+;;
+
+(provide 'notmuch-mua)
+
+;;; notmuch-mua.el ends here
diff --git a/emacs/notmuch-parser.el b/emacs/notmuch-parser.el
new file mode 100644 (file)
index 0000000..bb0379c
--- /dev/null
@@ -0,0 +1,211 @@
+;;; notmuch-parser.el --- streaming S-expression parser
+;;
+;; 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 <https://www.gnu.org/licenses/>.
+;;
+;; Authors: Austin Clements <aclements@csail.mit.edu>
+
+;;; Code:
+
+(require 'cl)
+
+(defun notmuch-sexp-create-parser ()
+  "Return a new streaming S-expression parser.
+
+This parser is designed to incrementally read an S-expression
+whose structure is known to the caller.  Like a typical
+S-expression parsing interface, it provides a function to read a
+complete S-expression from the input.  However, it extends this
+with an additional function that requires the next value in the
+input to be a list and descends into it, allowing its elements to
+be read one at a time or further descended into.  Both functions
+can return 'retry to indicate that not enough input is available.
+
+The parser always consumes input from point in the current
+buffer.  Hence, the caller is allowed to delete any data before
+point and may resynchronize after an error by moving point."
+
+  (vector 'notmuch-sexp-parser
+         ;; List depth
+         0
+         ;; Partial parse position marker
+         nil
+         ;; Partial parse state
+         nil))
+
+(defmacro notmuch-sexp--depth (sp)         `(aref ,sp 1))
+(defmacro notmuch-sexp--partial-pos (sp)   `(aref ,sp 2))
+(defmacro notmuch-sexp--partial-state (sp) `(aref ,sp 3))
+
+(defun notmuch-sexp-read (sp)
+  "Consume and return the value at point in the current buffer.
+
+Returns 'retry if there is insufficient input to parse a complete
+value (though it may still move point over whitespace).  If the
+parser is currently inside a list and the next token ends the
+list, this moves point just past the terminator and returns 'end.
+Otherwise, this moves point to just past the end of the value and
+returns the value."
+
+  (skip-chars-forward " \n\r\t")
+  (cond ((eobp) 'retry)
+       ((= (char-after) ?\))
+        ;; We've reached the end of a list
+        (if (= (notmuch-sexp--depth sp) 0)
+            ;; .. but we weren't in a list.  Let read signal the
+            ;; error to be consistent with all other code paths.
+            (read (current-buffer))
+          ;; Go up a level and return an end token
+          (decf (notmuch-sexp--depth sp))
+          (forward-char)
+          'end))
+       ((= (char-after) ?\()
+        ;; We're at the beginning of a list.  If we haven't started
+        ;; a partial parse yet, attempt to read the list in its
+        ;; entirety.  If this fails, or we've started a partial
+        ;; parse, extend the partial parse to figure out when we
+        ;; have a complete list.
+        (catch 'return
+          (when (null (notmuch-sexp--partial-state sp))
+            (let ((start (point)))
+              (condition-case nil
+                  (throw 'return (read (current-buffer)))
+                (end-of-file (goto-char start)))))
+          ;; Extend the partial parse
+          (let (is-complete)
+            (save-excursion
+              (let* ((new-state (parse-partial-sexp
+                                 (or (notmuch-sexp--partial-pos sp) (point))
+                                 (point-max) 0 nil
+                                 (notmuch-sexp--partial-state sp)))
+                     ;; A complete value is available if we've
+                     ;; reached depth 0.
+                     (depth (first new-state)))
+                (assert (>= depth 0))
+                (if (= depth 0)
+                    ;; Reset partial parse state
+                    (setf (notmuch-sexp--partial-state sp) nil
+                          (notmuch-sexp--partial-pos sp) nil
+                          is-complete t)
+                  ;; Update partial parse state
+                  (setf (notmuch-sexp--partial-state sp) new-state
+                        (notmuch-sexp--partial-pos sp) (point-marker)))))
+            (if is-complete
+                (read (current-buffer))
+              'retry))))
+       (t
+        ;; Attempt to read a non-compound value
+        (let ((start (point)))
+          (condition-case nil
+              (let ((val (read (current-buffer))))
+                ;; We got what looks like a complete read, but if
+                ;; we reached the end of the buffer in the process,
+                ;; we may not actually have all of the input we
+                ;; need (unless it's a string, which is delimited).
+                (if (or (stringp val) (not (eobp)))
+                    val
+                  ;; We can't be sure the input was complete
+                  (goto-char start)
+                  'retry))
+            (end-of-file
+             (goto-char start)
+             'retry))))))
+
+(defun notmuch-sexp-begin-list (sp)
+  "Parse the beginning of a list value and enter the list.
+
+Returns 'retry if there is insufficient input to parse the
+beginning of the list.  If this is able to parse the beginning of
+a list, it moves point past the token that opens the list and
+returns t.  Later calls to `notmuch-sexp-read' will return the
+elements inside the list.  If the input in buffer is not the
+beginning of a list, throw invalid-read-syntax."
+
+  (skip-chars-forward " \n\r\t")
+  (cond ((eobp) 'retry)
+       ((= (char-after) ?\()
+        (forward-char)
+        (incf (notmuch-sexp--depth sp))
+        t)
+       (t
+        ;; Skip over the bad character like `read' does
+        (forward-char)
+        (signal 'invalid-read-syntax (list (string (char-before)))))))
+
+(defun notmuch-sexp-eof (sp)
+  "Signal an error if there is more data in SP's buffer.
+
+Moves point to the beginning of any trailing data or to the end
+of the buffer if there is only trailing whitespace."
+
+  (skip-chars-forward " \n\r\t")
+  (unless (eobp)
+    (error "Trailing garbage following expression")))
+
+(defvar notmuch-sexp--parser nil
+  "The buffer-local notmuch-sexp-parser instance.
+
+Used by `notmuch-sexp-parse-partial-list'.")
+
+(defvar notmuch-sexp--state nil
+  "The buffer-local `notmuch-sexp-parse-partial-list' state.")
+
+(defun notmuch-sexp-parse-partial-list (result-function result-buffer)
+  "Incrementally parse an S-expression list from the current buffer.
+
+This function consumes an S-expression list from the current
+buffer, applying RESULT-FUNCTION in RESULT-BUFFER to each
+complete value in the list.  It operates incrementally and should
+be called whenever the input buffer has been extended with
+additional data.  The caller just needs to ensure it does not
+move point in the input buffer."
+
+  ;; Set up the initial state
+  (unless (local-variable-p 'notmuch-sexp--parser)
+    (set (make-local-variable 'notmuch-sexp--parser)
+        (notmuch-sexp-create-parser))
+    (set (make-local-variable 'notmuch-sexp--state) 'begin))
+  (let (done)
+    (while (not done)
+      (case notmuch-sexp--state
+       (begin
+        ;; Enter the list
+        (if (eq (notmuch-sexp-begin-list notmuch-sexp--parser) 'retry)
+            (setq done t)
+          (setq notmuch-sexp--state 'result)))
+       (result
+        ;; Parse a result
+        (let ((result (notmuch-sexp-read notmuch-sexp--parser)))
+          (case result
+            (retry (setq done t))
+            (end   (setq notmuch-sexp--state 'end))
+            (t     (with-current-buffer result-buffer
+                     (funcall result-function result))))))
+       (end
+        ;; Any trailing data is unexpected
+        (notmuch-sexp-eof notmuch-sexp--parser)
+        (setq done t)))))
+  ;; Clear out what we've parsed
+  (delete-region (point-min) (point)))
+
+(provide 'notmuch-parser)
+
+;; Local Variables:
+;; byte-compile-warnings: (not cl-functions)
+;; End:
+
+;;; notmuch-parser.el ends here
diff --git a/emacs/notmuch-pkg.el.tmpl b/emacs/notmuch-pkg.el.tmpl
new file mode 100644 (file)
index 0000000..de97baa
--- /dev/null
@@ -0,0 +1,6 @@
+;; %AG%
+(define-package
+  "notmuch"
+  %VERSION%
+  "Emacs based front-end (MUA) for notmuch"
+  nil)
diff --git a/emacs/notmuch-print.el b/emacs/notmuch-print.el
new file mode 100644 (file)
index 0000000..d9b3d44
--- /dev/null
@@ -0,0 +1,96 @@
+;;; notmuch-print.el --- printing messages from notmuch.
+;;
+;; Copyright © David Edmondson
+;;
+;; 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: David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(require 'notmuch-lib)
+
+(declare-function notmuch-show-get-prop "notmuch-show" (prop &optional props))
+
+(defcustom notmuch-print-mechanism 'notmuch-print-lpr
+  "How should printing be done?"
+  :group 'notmuch-show
+  :type '(choice
+         (function :tag "Use lpr" notmuch-print-lpr)
+         (function :tag "Use ps-print" notmuch-print-ps-print)
+         (function :tag "Use ps-print then evince" notmuch-print-ps-print/evince)
+         (function :tag "Use muttprint" notmuch-print-muttprint)
+         (function :tag "Use muttprint then evince" notmuch-print-muttprint/evince)
+         (function :tag "Using a custom function")))
+
+;; Utility functions:
+
+(defun notmuch-print-run-evince (file)
+  "View FILE using 'evince'."
+  (start-process "evince" nil "evince" file))
+
+(defun notmuch-print-run-muttprint (&optional output)
+  "Pass the contents of the current buffer to 'muttprint'.
+
+Optional OUTPUT allows passing a list of flags to muttprint."
+  (apply #'call-process-region (point-min) (point-max)
+        ;; Reads from stdin.
+        "muttprint"
+        nil nil nil
+        ;; Show the tags.
+        "--printed-headers" "Date_To_From_CC_Newsgroups_*Subject*_/Tags/"
+        output))
+
+;; User-visible functions:
+
+(defun notmuch-print-lpr (msg)
+  "Print a message buffer using lpr."
+  (lpr-buffer))
+
+(defun notmuch-print-ps-print (msg)
+  "Print a message buffer using the ps-print package."
+  (let ((subject (notmuch-prettify-subject
+                 (plist-get (notmuch-show-get-prop :headers msg) :Subject))))
+    (rename-buffer subject t)
+    (ps-print-buffer)))
+
+(defun notmuch-print-ps-print/evince (msg)
+  "Preview a message buffer using ps-print and evince."
+  (let ((ps-file (make-temp-file "notmuch" nil ".ps"))
+       (subject (notmuch-prettify-subject
+                 (plist-get (notmuch-show-get-prop :headers msg) :Subject))))
+    (rename-buffer subject t)
+    (ps-print-buffer ps-file)
+    (notmuch-print-run-evince ps-file)))
+
+(defun notmuch-print-muttprint (msg)
+  "Print a message using muttprint."
+  (notmuch-print-run-muttprint))
+
+(defun notmuch-print-muttprint/evince (msg)
+  "Preview a message buffer using muttprint and evince."
+  (let ((ps-file (make-temp-file "notmuch" nil ".ps")))
+    (notmuch-print-run-muttprint (list "--printer" (concat "TO_FILE:" ps-file)))
+    (notmuch-print-run-evince ps-file)))
+
+(defun notmuch-print-message (msg)
+  "Print a message using the user-selected mechanism."
+  (set-buffer-modified-p nil)
+  (funcall notmuch-print-mechanism msg))
+
+(provide 'notmuch-print)
+
+;;; notmuch-print.el ends here
diff --git a/emacs/notmuch-query.el b/emacs/notmuch-query.el
new file mode 100644 (file)
index 0000000..563e4ac
--- /dev/null
@@ -0,0 +1,80 @@
+;;; notmuch-query.el --- provide an emacs api to query notmuch
+;;
+;; 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: David Bremner <david@tethera.net>
+
+;;; Code:
+
+(require 'notmuch-lib)
+
+(defun notmuch-query-get-threads (search-terms)
+  "Return a list of threads of messages matching SEARCH-TERMS.
+
+A thread is a forest or list of trees. A tree is a two element
+list where the first element is a message, and the second element
+is a possibly empty forest of replies.
+"
+  (let ((args '("show" "--format=sexp" "--format-version=4")))
+    (if notmuch-show-process-crypto
+       (setq args (append args '("--decrypt=true"))))
+    (setq args (append args search-terms))
+    (apply #'notmuch-call-notmuch-sexp args)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Mapping functions across collections of messages.
+
+(defun notmuch-query-map-aux  (mapper function seq)
+  "private function to do the actual mapping and flattening"
+  (apply 'append
+        (mapcar
+          (lambda (tree)
+            (funcall mapper function tree))
+          seq)))
+
+(defun notmuch-query-map-threads (fn threads)
+  "apply FN to every thread in  THREADS. Flatten results to a list.
+
+See the function notmuch-query-get-threads for more information."
+  (notmuch-query-map-aux 'notmuch-query-map-forest fn threads))
+
+(defun notmuch-query-map-forest (fn forest)
+  "apply function to every message in a forest. Flatten results to a list.
+
+See the function notmuch-query-get-threads for more information.
+"
+  (notmuch-query-map-aux 'notmuch-query-map-tree fn forest))
+
+(defun notmuch-query-map-tree (fn tree)
+  "Apply function FN to every message in TREE. Flatten results to a list
+
+See the function notmuch-query-get-threads for more information."
+  (cons (funcall fn (car tree)) (notmuch-query-map-forest fn (cadr tree))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Predefined queries
+
+(defun notmuch-query-get-message-ids (&rest search-terms)
+  "Return a list of message-ids of messages that match SEARCH-TERMS"
+  (notmuch-query-map-threads
+   (lambda (msg) (plist-get msg :id))
+   (notmuch-query-get-threads search-terms)))
+
+(provide 'notmuch-query)
+
+;;; notmuch-query.el ends here
diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el
new file mode 100644 (file)
index 0000000..a0a5837
--- /dev/null
@@ -0,0 +1,2524 @@
+;;; notmuch-show.el --- displaying notmuch forests.
+;;
+;; Copyright © Carl Worth
+;; Copyright © David Edmondson
+;;
+;; 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: Carl Worth <cworth@cworth.org>
+;;          David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'mm-view)
+(require 'message)
+(require 'mm-decode)
+(require 'mailcap)
+(require 'icalendar)
+(require 'goto-addr)
+
+(require 'notmuch-lib)
+(require 'notmuch-tag)
+(require 'notmuch-query)
+(require 'notmuch-wash)
+(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)
+(declare-function notmuch-search-previous-thread "notmuch" nil)
+(declare-function notmuch-search-show-thread "notmuch" nil)
+(declare-function notmuch-foreach-mime-part "notmuch" (function mm-handle))
+(declare-function notmuch-count-attachments "notmuch" (mm-handle))
+(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)
+(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.
+
+For an open message, all of these headers will be made visible
+according to `notmuch-message-headers-visible' or can be toggled
+with `notmuch-show-toggle-visibility-headers'. For a closed message,
+only the first header in the list will be visible."
+  :type '(repeat string)
+  :group 'notmuch-show)
+
+(defcustom notmuch-message-headers-visible t
+  "Should the headers be visible by default?
+
+If this value is non-nil, then all of the headers defined in
+`notmuch-message-headers' will be visible by default in the display
+of each message. Otherwise, these headers will be hidden and
+`notmuch-show-toggle-visibility-headers' can be used to make them
+visible for any given message."
+  :type 'boolean
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-relative-dates t
+  "Display relative dates in the message summary line."
+  :type 'boolean
+  :group 'notmuch-show)
+
+(defvar notmuch-show-markup-headers-hook '(notmuch-show-colour-headers)
+  "A list of functions called to decorate the headers listed in
+`notmuch-message-headers'.")
+
+(defcustom notmuch-show-hook '(notmuch-show-turn-on-visual-line-mode)
+  "Functions called after populating a `notmuch-show' buffer."
+  :type 'hook
+  :options '(notmuch-show-turn-on-visual-line-mode)
+  :group 'notmuch-show
+  :group 'notmuch-hooks)
+
+(defcustom notmuch-show-insert-text/plain-hook '(notmuch-wash-wrap-long-lines
+                                                notmuch-wash-tidy-citations
+                                                notmuch-wash-elide-blank-lines
+                                                notmuch-wash-excerpt-citations)
+  "Functions used to improve the display of text/plain parts."
+  :type 'hook
+  :options '(notmuch-wash-convert-inline-patch-to-part
+            notmuch-wash-wrap-long-lines
+            notmuch-wash-tidy-citations
+            notmuch-wash-elide-blank-lines
+            notmuch-wash-excerpt-citations)
+  :group 'notmuch-show
+  :group 'notmuch-hooks)
+
+(defcustom notmuch-show-max-text-part-size 100000
+  "Maximum size of a text part to be shown by default in characters.
+
+Set to 0 to show the part regardless of size."
+  :type 'integer
+  :group 'notmuch-show)
+
+;; Mostly useful for debugging.
+(defcustom notmuch-show-all-multipart/alternative-parts nil
+  "Should all parts of multipart/alternative parts be shown?"
+  :type 'boolean
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-indent-messages-width 1
+  "Width of message indentation in threads.
+
+Messages are shown indented according to their depth in a thread.
+This variable determines the width of this indentation measured
+in number of blanks.  Defaults to `1', choose `0' to disable
+indentation."
+  :type 'integer
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-indent-multipart nil
+  "Should the sub-parts of a multipart/* part be indented?"
+  ;; dme: Not sure which is a good default.
+  :type 'boolean
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-part-button-default-action 'notmuch-show-save-part
+  "Default part header button action (on ENTER or mouse click)."
+  :group 'notmuch-show
+  :type '(choice (const :tag "Save part"
+                       notmuch-show-save-part)
+                (const :tag "View part"
+                       notmuch-show-view-part)
+                (const :tag "View interactively"
+                       notmuch-show-interactively-view-part)))
+
+(defcustom notmuch-show-only-matching-messages nil
+  "Only matching messages are shown by default."
+  :type 'boolean
+  :group 'notmuch-show)
+
+;; By default, block all external images to prevent privacy leaks and
+;; potential attacks.
+(defcustom notmuch-show-text/html-blocked-images "."
+  "Remote images that have URLs matching this regexp will be blocked."
+  :type '(choice (const nil) regexp)
+  :group 'notmuch-show)
+
+(defvar notmuch-show-thread-id nil)
+(make-variable-buffer-local 'notmuch-show-thread-id)
+
+(defvar notmuch-show-parent-buffer nil)
+(make-variable-buffer-local 'notmuch-show-parent-buffer)
+
+(defvar notmuch-show-query-context nil)
+(make-variable-buffer-local 'notmuch-show-query-context)
+
+(defvar notmuch-show-process-crypto nil)
+(make-variable-buffer-local 'notmuch-show-process-crypto)
+
+(defvar notmuch-show-elide-non-matching-messages nil)
+(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
+
+(defvar notmuch-show-indent-content t)
+(make-variable-buffer-local 'notmuch-show-indent-content)
+
+(defvar notmuch-show-attachment-debug nil
+  "If t log stdout and stderr from attachment handlers
+
+When set to nil (the default) stdout and stderr from attachment
+handlers is discarded. When set to t the stdout and stderr from
+each attachment handler is logged in buffers with names beginning
+\" *notmuch-part*\". This option requires emacs version at least
+24.3 to work.")
+
+(defcustom notmuch-show-stash-mlarchive-link-alist
+  '(("Gmane" . "https://mid.gmane.org/")
+    ("MARC" . "https://marc.info/?i=")
+    ("Mail Archive, The" . "https://mid.mail-archive.com/")
+    ("LKML" . "https://lkml.kernel.org/r/")
+    ;; FIXME: can these services be searched by `Message-Id' ?
+    ;; ("MarkMail" . "http://markmail.org/")
+    ;; ("Nabble" . "http://nabble.com/")
+    ;; ("opensubscriber" . "http://opensubscriber.com/")
+    )
+  "List of Mailing List Archives to use when stashing links.
+
+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 (choice
+                            (string :tag "URL")
+                            (function :tag "Function returning the URL")))
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-stash-mlarchive-link-default "Gmane"
+  "Default Mailing List Archive to use when stashing links.
+
+This is used when `notmuch-show-stash-mlarchive-link' isn't
+provided with an MLA argument nor `completing-read' input."
+  :type `(choice
+         ,@(mapcar
+            (lambda (mla)
+              (list 'const :tag (car mla) :value (car mla)))
+            notmuch-show-stash-mlarchive-link-alist))
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-mark-read-tags '("-unread")
+  "List of tag changes to apply to a message when it is marked as read.
+
+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 marked as read.
+
+For example, if you wanted to remove an \"unread\" tag and add a
+\"read\" tag (which would make little sense), you would set:
+    (\"-unread\" \"+read\")"
+  :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.
+
+The function should take two arguments START and END which will
+be the start and end of the visible portion of the buffer and
+should mark the appropriate messages read by applying
+`notmuch-show-mark-read'. This function will be called after
+every user interaction with notmuch."
+  :type 'function
+  :group 'notmuch-show)
+
+(defcustom notmuch-show-imenu-indent nil
+  "Should Imenu display messages indented.
+
+By default, Imenu (see Info node `(emacs) Imenu') in a
+notmuch-show buffer displays all messages straight.  This is
+because the default Emacs frontend for Imenu makes it difficult
+to select an Imenu entry with spaces in front.  Other imenu
+frontends such as counsel-imenu does not have this limitation.
+In these cases, Imenu entries can be indented to reflect the
+position of the message in the thread."
+  :type 'boolean
+  :group 'notmuch-show)
+
+(defmacro with-current-notmuch-show-message (&rest body)
+  "Evaluate body with current buffer set to the text of current message"
+  `(save-excursion
+     (let ((id (notmuch-show-get-message-id)))
+       (let ((buf (generate-new-buffer (concat "*notmuch-msg-" id "*"))))
+         (with-current-buffer buf
+          (let ((coding-system-for-read 'no-conversion))
+            (call-process notmuch-command nil t nil "show" "--format=raw" id))
+          ,@body)
+        (kill-buffer buf)))))
+
+(defun notmuch-show-turn-on-visual-line-mode ()
+  "Enable Visual Line mode."
+  (visual-line-mode t))
+
+;; DEPRECATED in Notmuch 0.16 since we now have convenient part
+;; commands.  We'll keep the command around for a version or two in
+;; case people want to bind it themselves.
+(defun notmuch-show-view-all-mime-parts ()
+  "Use external viewers to view all attachments from the current message."
+  (interactive)
+  (with-current-notmuch-show-message
+   ;; We override the mm-inline-media-tests to indicate which message
+   ;; parts are already sufficiently handled by the original
+   ;; presentation of the message in notmuch-show mode. These parts
+   ;; will be inserted directly into the temporary buffer of
+   ;; with-current-notmuch-show-message and silently discarded.
+   ;;
+   ;; Any MIME part not explicitly mentioned here will be handled by an
+   ;; external viewer as configured in the various mailcap files.
+   (let ((mm-inline-media-tests '(
+                                 ("text/.*" ignore identity)
+                                 ("application/pgp-signature" ignore identity)
+                                 ("multipart/alternative" ignore identity)
+                                 ("multipart/mixed" ignore identity)
+                                 ("multipart/related" ignore identity)
+                                )))
+     (mm-display-parts (mm-dissect-buffer)))))
+
+(defun notmuch-show-save-attachments ()
+  "Save all attachments from the current message."
+  (interactive)
+  (with-current-notmuch-show-message
+   (let ((mm-handle (mm-dissect-buffer)))
+     (notmuch-save-attachments
+      mm-handle (> (notmuch-count-attachments mm-handle) 1))))
+  (message "Done"))
+
+(defun notmuch-show-with-message-as-text (fn)
+  "Apply FN to a text representation of the current message.
+
+FN is called with one argument, the message properties. It should
+operation on the contents of the current buffer."
+
+  ;; Remake the header to ensure that all information is available.
+  (let* ((to (notmuch-show-get-to))
+        (cc (notmuch-show-get-cc))
+        (from (notmuch-show-get-from))
+        (subject (notmuch-show-get-subject))
+        (date (notmuch-show-get-date))
+        (tags (notmuch-show-get-tags))
+        (depth (notmuch-show-get-depth))
+
+        (header (concat
+                 "Subject: " subject "\n"
+                 "To: " to "\n"
+                 (if (not (string= cc ""))
+                     (concat "Cc: " cc "\n")
+                   "")
+                 "From: " from "\n"
+                 "Date: " date "\n"
+                 (if tags
+                     (concat "Tags: "
+                             (mapconcat #'identity tags ", ") "\n")
+                   "")))
+        (all (buffer-substring (notmuch-show-message-top)
+                               (notmuch-show-message-bottom)))
+
+        (props (notmuch-show-get-message-properties))
+        (indenting notmuch-show-indent-content))
+    (with-temp-buffer
+      (insert all)
+      (if indenting
+         (indent-rigidly (point-min) (point-max) (- (* notmuch-show-indent-messages-width depth))))
+      ;; Remove the original header.
+      (goto-char (point-min))
+      (re-search-forward "^$" (point-max) nil)
+      (delete-region (point-min) (point))
+      (insert header)
+      (funcall fn props))))
+
+(defun notmuch-show-print-message ()
+  "Print the current message."
+  (interactive)
+  (notmuch-show-with-message-as-text 'notmuch-print-message))
+
+(defun notmuch-show-fontify-header ()
+  (let ((face (cond
+              ((looking-at "[Tt]o:")
+               'message-header-to)
+              ((looking-at "[Bb]?[Cc][Cc]:")
+               'message-header-cc)
+              ((looking-at "[Ss]ubject:")
+               'message-header-subject)
+              (t
+               'message-header-other))))
+
+    (overlay-put (make-overlay (point) (re-search-forward ":"))
+                'face 'message-header-name)
+    (overlay-put (make-overlay (point) (re-search-forward ".*$"))
+                'face face)))
+
+(defun notmuch-show-colour-headers ()
+  "Apply some colouring to the current headers."
+  (goto-char (point-min))
+  (while (looking-at "^[A-Za-z][-A-Za-z0-9]*:")
+    (notmuch-show-fontify-header)
+    (forward-line)))
+
+(defun notmuch-show-spaces-n (n)
+  "Return a string comprised of `n' spaces."
+  (make-string n ? ))
+
+(defun notmuch-show-update-tags (tags)
+  "Update the displayed tags of the current message."
+  (save-excursion
+    (goto-char (notmuch-show-message-top))
+    (if (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
+       (let ((inhibit-read-only t))
+         (replace-match (concat "("
+                                (notmuch-tag-format-tags tags (notmuch-show-get-prop :orig-tags))
+                                ")"))))))
+
+(defun notmuch-clean-address (address)
+  "Try to clean a single email ADDRESS for display. Return a cons
+cell of (AUTHOR_EMAIL AUTHOR_NAME). Return (ADDRESS nil) if
+parsing fails."
+  (condition-case nil
+    (let (p-name p-address)
+      ;; It would be convenient to use `mail-header-parse-address',
+      ;; but that expects un-decoded mailbox parts, whereas our
+      ;; mailbox parts are already decoded (and hence may contain
+      ;; UTF-8). Given that notmuch should handle most of the awkward
+      ;; cases, some simple string deconstruction should be sufficient
+      ;; here.
+      (cond
+       ;; "User <user@dom.ain>" style.
+       ((string-match "\\(.*\\) <\\(.*\\)>" address)
+       (setq p-name (match-string 1 address)
+             p-address (match-string 2 address)))
+
+       ;; "<user@dom.ain>" style.
+       ((string-match "<\\(.*\\)>" address)
+       (setq p-address (match-string 1 address)))
+
+       ;; Everything else.
+       (t
+       (setq p-address address)))
+
+      (when p-name
+       ;; Remove elements of the mailbox part that are not relevant for
+       ;; display, even if they are required during transport:
+       ;;
+       ;; Backslashes.
+       (setq p-name (replace-regexp-in-string "\\\\" "" p-name))
+
+       ;; Outer single and double quotes, which might be nested.
+       (loop
+        with start-of-loop
+        do (setq start-of-loop p-name)
+
+        when (string-match "^\"\\(.*\\)\"$" p-name)
+        do (setq p-name (match-string 1 p-name))
+
+        when (string-match "^'\\(.*\\)'$" p-name)
+        do (setq p-name (match-string 1 p-name))
+
+        until (string= start-of-loop p-name)))
+
+      ;; If the address is 'foo@bar.com <foo@bar.com>' then show just
+      ;; 'foo@bar.com'.
+      (when (string= p-name p-address)
+       (setq p-name nil))
+
+      (cons p-address p-name))
+    (error (cons address nil))))
+
+(defun notmuch-show-clean-address (address)
+  "Try to clean a single email ADDRESS for display.  Return
+unchanged ADDRESS if parsing fails."
+  (let* ((clean-address (notmuch-clean-address address))
+        (p-address (car clean-address))
+        (p-name (cdr clean-address)))
+    ;; If no name, return just the address.
+    (if (not p-name)
+       p-address
+      ;; Otherwise format the name and address together.
+      (concat p-name " <" p-address ">"))))
+
+(defun notmuch-show-insert-headerline (headers date tags depth)
+  "Insert a notmuch style headerline based on HEADERS for a
+message at DEPTH in the current thread."
+  (let ((start (point)))
+    (insert (notmuch-show-spaces-n (* notmuch-show-indent-messages-width depth))
+           (notmuch-sanitize
+            (notmuch-show-clean-address (plist-get headers :From)))
+           " ("
+           date
+           ") ("
+           (notmuch-tag-format-tags tags tags)
+           ")\n")
+    (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))
+
+(defun notmuch-show-insert-header (header header-value)
+  "Insert a single header."
+  (insert header ": " (notmuch-sanitize header-value) "\n"))
+
+(defun notmuch-show-insert-headers (headers)
+  "Insert the headers of the current message."
+  (let ((start (point)))
+    (mapc (lambda (header)
+           (let* ((header-symbol (intern (concat ":" header)))
+                  (header-value (plist-get headers header-symbol)))
+             (if (and header-value
+                      (not (string-equal "" header-value)))
+                 (notmuch-show-insert-header header header-value))))
+         notmuch-message-headers)
+    (save-excursion
+      (save-restriction
+       (narrow-to-region start (point-max))
+       (run-hooks 'notmuch-show-markup-headers-hook)))))
+
+(define-button-type 'notmuch-show-part-button-type
+  'action 'notmuch-show-part-button-default
+  'follow-link t
+  'face 'message-mml
+  :supertype 'notmuch-button-type)
+
+(defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)
+  (let ((button)
+       (base-label (concat (when name (concat name ": "))
+                           declared-type
+                           (unless (string-equal declared-type content-type)
+                             (concat " (as " content-type ")"))
+                           comment)))
+
+    (setq button
+         (insert-button
+          (concat "[ " base-label " ]")
+          :base-label base-label
+          :type 'notmuch-show-part-button-type
+          :notmuch-part-hidden nil))
+    (insert "\n")
+    ;; return button
+    button))
+
+(defun notmuch-show-toggle-part-invisibility (&optional button)
+  (interactive)
+  (let ((button (or button (button-at (point)))))
+    (when button
+      (let ((overlay (button-get button 'overlay))
+           (lazy-part (button-get button :notmuch-lazy-part)))
+       ;; We have a part to toggle if there is an overlay or if there is a lazy part.
+       ;; If neither is present we cannot toggle the part so we just return nil.
+       (when (or overlay lazy-part)
+         (let* ((show (button-get button :notmuch-part-hidden))
+                (new-start (button-start button))
+                (button-label (button-get button :base-label))
+                (old-point (point))
+                (properties (text-properties-at (button-start button)))
+                (inhibit-read-only t))
+           ;; Toggle the button itself.
+           (button-put button :notmuch-part-hidden (not show))
+           (goto-char new-start)
+           (insert "[ " button-label (if show " ]" " (hidden) ]"))
+           (set-text-properties new-start (point) properties)
+           (let ((old-end (button-end button)))
+             (move-overlay button new-start (point))
+             (delete-region (point) old-end))
+           (goto-char (min old-point (1- (button-end button))))
+           ;; Return nil if there is a lazy-part, it is empty, and we are
+           ;; trying to show it.  In all other cases return t.
+           (if lazy-part
+               (when show
+                 (button-put button :notmuch-lazy-part nil)
+                 (notmuch-show-lazy-part lazy-part button))
+             ;; else there must be an overlay.
+             (overlay-put overlay 'invisible (not show))
+             t)))))))
+
+;; Part content ID handling
+
+(defvar notmuch-show--cids nil
+  "Alist from raw content ID to (MSG PART).")
+(make-variable-buffer-local 'notmuch-show--cids)
+
+(defun notmuch-show--register-cids (msg part)
+  "Register content-IDs in PART and all of PART's sub-parts."
+  (let ((content-id (plist-get part :content-id)))
+    (when content-id
+      ;; Note that content-IDs are globally unique, except when they
+      ;; aren't: RFC 2046 section 5.1.4 permits children of a
+      ;; multipart/alternative to have the same content-ID, in which
+      ;; case the MUA is supposed to pick the best one it can render.
+      ;; We simply add the content-ID to the beginning of our alist;
+      ;; so if this happens, we'll take the last (and "best")
+      ;; alternative (even if we can't render it).
+      (push (list content-id msg part) notmuch-show--cids)))
+  ;; Recurse on sub-parts
+  (let ((ctype (notmuch-split-content-type
+               (downcase (plist-get part :content-type)))))
+    (cond ((equal (first ctype) "multipart")
+          (mapc (apply-partially #'notmuch-show--register-cids msg)
+                (plist-get part :content)))
+         ((equal ctype '("message" "rfc822"))
+          (notmuch-show--register-cids
+           msg
+           (first (plist-get (first (plist-get part :content)) :body)))))))
+
+(defun notmuch-show--get-cid-content (cid)
+  "Return a list (CID-content content-type) or nil.
+
+This will only find parts from messages that have been inserted
+into the current buffer.  CID must be a raw content ID, without
+enclosing angle brackets, a cid: prefix, or URL encoding.  This
+will return nil if the CID is unknown or cannot be retrieved."
+  (let ((descriptor (cdr (assoc cid notmuch-show--cids))))
+    (when descriptor
+      (let* ((msg (first descriptor))
+            (part (second descriptor))
+            ;; Request caching for this content, as some messages
+            ;; reference the same cid: part many times (hundreds!).
+            (content (notmuch-get-bodypart-binary
+                      msg part notmuch-show-process-crypto 'cache))
+            (content-type (plist-get part :content-type)))
+       (list content content-type)))))
+
+(defun notmuch-show-setup-w3m ()
+  "Instruct w3m how to retrieve content from a \"related\" part of a message."
+  (interactive)
+  (if (boundp 'w3m-cid-retrieve-function-alist)
+    (unless (assq 'notmuch-show-mode w3m-cid-retrieve-function-alist)
+      (push (cons 'notmuch-show-mode #'notmuch-show--cid-w3m-retrieve)
+           w3m-cid-retrieve-function-alist)))
+  (setq mm-inline-text-html-with-images t))
+
+(defvar w3m-current-buffer) ;; From `w3m.el'.
+(defun notmuch-show--cid-w3m-retrieve (url &rest args)
+  ;; url includes the cid: prefix and is URL encoded (see RFC 2392).
+  (let* ((cid (url-unhex-string (substring url 4)))
+        (content-and-type
+         (with-current-buffer w3m-current-buffer
+           (notmuch-show--get-cid-content cid))))
+    (when content-and-type
+      (insert (first content-and-type))
+      (second content-and-type))))
+
+;; MIME part renderers
+
+(defun notmuch-show-multipart/*-to-list (part)
+  (mapcar (lambda (inner-part) (plist-get inner-part :content-type))
+         (plist-get part :content)))
+
+(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
+  (let ((chosen-type (car (notmuch-multipart/alternative-choose msg (notmuch-show-multipart/*-to-list part))))
+       (inner-parts (plist-get part :content))
+       (start (point)))
+    ;; This inserts all parts of the chosen type rather than just one,
+    ;; but it's not clear that this is the wrong thing to do - which
+    ;; should be chosen if there are more than one that match?
+    (mapc (lambda (inner-part)
+           (let* ((inner-type (plist-get inner-part :content-type))
+                 (hide (not (or notmuch-show-all-multipart/alternative-parts
+                          (string= chosen-type inner-type)))))
+             (notmuch-show-insert-bodypart msg inner-part depth hide)))
+         inner-parts)
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button)
+  (let ((inner-parts (plist-get part :content))
+       (start (point)))
+
+    ;; Render the primary part.  FIXME: Support RFC 2387 Start header.
+    (notmuch-show-insert-bodypart msg (car inner-parts) depth)
+    ;; Add hidden buttons for the rest
+    (mapc (lambda (inner-part)
+           (notmuch-show-insert-bodypart msg inner-part depth t))
+         (cdr inner-parts))
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button)
+  (when button
+    (button-put button 'face 'notmuch-crypto-part-header))
+
+  ;; Insert a button detailing the signature status.
+  (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
+                                         (notmuch-show-get-header :From msg))
+
+  (let ((inner-parts (plist-get part :content))
+       (start (point)))
+    ;; Show all of the parts.
+    (mapc (lambda (inner-part)
+           (notmuch-show-insert-bodypart msg inner-part depth))
+         inner-parts)
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button)
+  (when button
+    (button-put button 'face 'notmuch-crypto-part-header))
+
+  ;; Insert a button detailing the encryption status.
+  (notmuch-crypto-insert-encstatus-button (car (plist-get part :encstatus)))
+
+  ;; Insert a button detailing the signature status.
+  (notmuch-crypto-insert-sigstatus-button (car (plist-get part :sigstatus))
+                                         (notmuch-show-get-header :From msg))
+
+  (let ((inner-parts (plist-get part :content))
+       (start (point)))
+    ;; Show all of the parts.
+    (mapc (lambda (inner-part)
+           (notmuch-show-insert-bodypart msg inner-part depth))
+         inner-parts)
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-application/pgp-encrypted (msg part content-type nth depth button)
+  t)
+
+(defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth button)
+  (let ((inner-parts (plist-get part :content))
+       (start (point)))
+    ;; Show all of the parts.
+    (mapc (lambda (inner-part)
+           (notmuch-show-insert-bodypart msg inner-part depth))
+         inner-parts)
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth button)
+  (let* ((message (car (plist-get part :content)))
+        (body (car (plist-get message :body)))
+        (start (point)))
+
+    ;; Override `notmuch-message-headers' to force `From' to be
+    ;; displayed.
+    (let ((notmuch-message-headers '("From" "Subject" "To" "Cc" "Date")))
+      (notmuch-show-insert-headers (plist-get message :headers)))
+
+    ;; Blank line after headers to be compatible with the normal
+    ;; message display.
+    (insert "\n")
+
+    ;; Show the body
+    (notmuch-show-insert-bodypart msg body depth)
+
+    (when notmuch-show-indent-multipart
+      (indent-rigidly start (point) 1)))
+  t)
+
+(defun notmuch-show-insert-part-text/plain (msg part content-type nth depth button)
+  ;; For backward compatibility we want to apply the text/plain hook
+  ;; to the whole of the part including the part button if there is
+  ;; one.
+  (let ((start (if button
+                  (button-start button)
+                (point))))
+    (insert (notmuch-get-bodypart-text msg part notmuch-show-process-crypto))
+    (save-excursion
+      (save-restriction
+       (narrow-to-region start (point-max))
+       (run-hook-with-args 'notmuch-show-insert-text/plain-hook msg depth))))
+  t)
+
+(defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth button)
+  (insert (with-temp-buffer
+           (insert (notmuch-get-bodypart-text msg part notmuch-show-process-crypto))
+           ;; notmuch-get-bodypart-text does no newline conversion.
+           ;; Replace CRLF with LF before icalendar can use it.
+           (goto-char (point-min))
+           (while (re-search-forward "\r\n" nil t)
+             (replace-match "\n" nil nil))
+           (let ((file (make-temp-file "notmuch-ical"))
+                 result)
+             (unwind-protect
+                 (progn
+                   (unless (icalendar-import-buffer file t)
+                     (error "Icalendar import error. See *icalendar-errors* for more information"))
+                   (set-buffer (get-file-buffer file))
+                   (setq result (buffer-substring (point-min) (point-max)))
+                   (set-buffer-modified-p nil)
+                   (kill-buffer (current-buffer)))
+               (delete-file file))
+             result)))
+  t)
+
+;; For backwards compatibility.
+(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button)
+  (notmuch-show-insert-part-text/calendar msg part content-type nth depth button))
+
+(if (version< emacs-version "25.3")
+    ;; https://bugs.gnu.org/28350
+    ;;
+    ;; For newer emacs, we fall back to notmuch-show-insert-part-*/*
+    ;; (see notmuch-show-handlers-for)
+    (defun notmuch-show-insert-part-text/enriched (msg part content-type nth depth button)
+      ;; By requiring enriched below, we ensure that the function enriched-decode-display-prop
+      ;; is defined before it will be shadowed by the letf below. Otherwise the version
+      ;; in enriched.el may be loaded a bit later and used instead (for the first time).
+      (require 'enriched)
+      (letf (((symbol-function 'enriched-decode-display-prop)
+                (lambda (start end &optional param) (list start end))))
+       (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
+
+(defun notmuch-show-get-mime-type-of-application/octet-stream (part)
+  ;; If we can deduce a MIME type from the filename of the attachment,
+  ;; we return that.
+  (if (plist-get part :filename)
+      (let ((extension (file-name-extension (plist-get part :filename)))
+           mime-type)
+       (if extension
+           (progn
+             (mailcap-parse-mimetypes)
+             (setq mime-type (mailcap-extension-to-mime extension))
+             (if (and mime-type
+                      (not (string-equal mime-type "application/octet-stream")))
+                 mime-type
+               nil))
+         nil))))
+
+(defun notmuch-show-insert-part-text/html (msg part content-type nth depth button)
+  (if (eq mm-text-html-renderer 'shr)
+      ;; It's easier to drive shr ourselves than to work around the
+      ;; goofy things `mm-shr' does (like irreversibly taking over
+      ;; content ID handling).
+
+      ;; FIXME: If we block an image, offer a button to load external
+      ;; images.
+      (let ((shr-blocked-images notmuch-show-text/html-blocked-images))
+       (notmuch-show--insert-part-text/html-shr msg part))
+    ;; Otherwise, let message-mode do the heavy lifting
+    ;;
+    ;; w3m sets up a keymap which "leaks" outside the invisible region
+    ;; and causes strange effects in notmuch. We set
+    ;; mm-inline-text-html-with-w3m-keymap to nil to tell w3m not to
+    ;; set a keymap (so the normal notmuch-show-mode-map remains).
+    (let ((mm-inline-text-html-with-w3m-keymap nil)
+         ;; FIXME: If we block an image, offer a button to load external
+         ;; images.
+         (gnus-blocked-images notmuch-show-text/html-blocked-images))
+      (notmuch-show-insert-part-*/* msg part content-type nth depth button))))
+
+;; These functions are used by notmuch-show--insert-part-text/html-shr
+(declare-function libxml-parse-html-region "xml.c")
+(declare-function shr-insert-document "shr")
+
+(defun notmuch-show--insert-part-text/html-shr (msg part)
+  ;; Make sure shr is loaded before we start let-binding its globals
+  (require 'shr)
+  (let ((dom (let ((process-crypto notmuch-show-process-crypto))
+              (with-temp-buffer
+                (insert (notmuch-get-bodypart-text msg part process-crypto))
+                (libxml-parse-html-region (point-min) (point-max)))))
+       (shr-content-function
+        (lambda (url)
+          ;; shr strips the "cid:" part of URL, but doesn't
+          ;; URL-decode it (see RFC 2392).
+          (let ((cid (url-unhex-string url)))
+            (first (notmuch-show--get-cid-content cid))))))
+    (shr-insert-document dom)
+    t))
+
+(defun notmuch-show-insert-part-*/* (msg part content-type nth depth button)
+  ;; This handler _must_ succeed - it is the handler of last resort.
+  (notmuch-mm-display-part-inline msg part content-type notmuch-show-process-crypto)
+  t)
+
+;; Functions for determining how to handle MIME parts.
+
+(defun notmuch-show-handlers-for (content-type)
+  "Return a list of content handlers for a part of type CONTENT-TYPE."
+  (let (result)
+    (mapc (lambda (func)
+           (if (functionp func)
+               (push func result)))
+         ;; Reverse order of prefrence.
+         (list (intern (concat "notmuch-show-insert-part-*/*"))
+               (intern (concat
+                        "notmuch-show-insert-part-"
+                        (car (notmuch-split-content-type content-type))
+                        "/*"))
+               (intern (concat "notmuch-show-insert-part-" content-type))))
+    result))
+
+;; \f
+
+(defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
+  ;; Run the handlers until one of them succeeds.
+  (loop for handler in (notmuch-show-handlers-for content-type)
+       until (condition-case err
+                 (funcall handler msg part content-type nth depth button)
+               ;; Specifying `debug' here lets the debugger run if
+               ;; `debug-on-error' is non-nil.
+               ((debug error)
+                (insert "!!! Bodypart handler `" (prin1-to-string handler) "' threw an error:\n"
+                        "!!! " (error-message-string err) "\n")
+                nil))))
+
+(defun notmuch-show-create-part-overlays (button beg end)
+  "Add an overlay to the part between BEG and END"
+
+  ;; If there is no button (i.e., the part is text/plain and the first
+  ;; part) or if the part has no content then we don't make the part
+  ;; toggleable.
+  (when (and button (/= beg end))
+    (button-put button 'overlay (make-overlay beg end))
+    ;; Return true if we created an overlay.
+    t))
+
+(defun notmuch-show-record-part-information (part beg end)
+  "Store PART as a text property from BEG to END"
+
+  ;; Record part information.  Since we already inserted subparts,
+  ;; don't override existing :notmuch-part properties.
+  (notmuch-map-text-property beg end :notmuch-part
+                            (lambda (v) (or v part)))
+  ;; Make :notmuch-part front sticky and rear non-sticky so it stays
+  ;; applied to the beginning of each line when we indent the
+  ;; message.  Since we're operating on arbitrary renderer output,
+  ;; watch out for sticky specs of t, which means all properties are
+  ;; front-sticky/rear-nonsticky.
+  (notmuch-map-text-property beg end 'front-sticky
+                            (lambda (v) (if (listp v)
+                                            (pushnew :notmuch-part v)
+                                          v)))
+  (notmuch-map-text-property beg end 'rear-nonsticky
+                            (lambda (v) (if (listp v)
+                                            (pushnew :notmuch-part v)
+                                          v))))
+
+(defun notmuch-show-lazy-part (part-args button)
+  ;; Insert the lazy part after the button for the part. We would just
+  ;; move to the start of the new line following the button and insert
+  ;; the part but that point might have text properties (eg colours
+  ;; from a message header etc) so instead we start from the last
+  ;; character of the button by adding a newline and finish by
+  ;; removing the extra newline from the end of the part.
+  (save-excursion
+    (goto-char (button-end button))
+    (insert "\n")
+    (let* ((inhibit-read-only t)
+          ;; We need to use markers for the start and end of the part
+          ;; because the part insertion functions do not guarantee
+          ;; to leave point at the end of the part.
+          (part-beg (copy-marker (point) nil))
+          (part-end (copy-marker (point) t))
+          ;; We have to save the depth as we can't find the depth
+          ;; when narrowed.
+          (depth (notmuch-show-get-depth)))
+      (save-restriction
+       (narrow-to-region part-beg part-end)
+       (delete-region part-beg part-end)
+       (apply #'notmuch-show-insert-bodypart-internal part-args)
+       (indent-rigidly part-beg part-end (* notmuch-show-indent-messages-width depth)))
+      (goto-char part-end)
+      (delete-char 1)
+      (notmuch-show-record-part-information (second part-args)
+                                           (button-start button)
+                                           part-end)
+      ;; Create the overlay. If the lazy-part turned out to be empty/not
+      ;; showable this returns nil.
+      (notmuch-show-create-part-overlays button part-beg part-end))))
+
+(defun notmuch-show-mime-type (part)
+  "Return the correct mime-type to use for PART."
+  (let ((content-type (downcase (plist-get part :content-type))))
+    (or (and (string= content-type "application/octet-stream")
+            (notmuch-show-get-mime-type-of-application/octet-stream part))
+       (and (string= content-type "inline patch")
+            "text/x-diff")
+       content-type)))
+
+;; The following variable can be overridden by let bindings.
+(defvar notmuch-show-insert-header-p-function 'notmuch-show-insert-header-p
+  "Specify which function decides which part headers get inserted.
+
+The function should take two parameters, PART and HIDE, and
+should return non-NIL if a header button should be inserted for
+this part.")
+
+(defun notmuch-show-insert-header-p (part hide)
+  ;; Show all part buttons except for the first part if it is text/plain.
+  (let ((mime-type (notmuch-show-mime-type part)))
+    (not (and (string= mime-type "text/plain")
+             (<= (plist-get part :id) 1)))))
+
+(defun notmuch-show-reply-insert-header-p-never (part hide)
+  nil)
+
+(defun notmuch-show-reply-insert-header-p-trimmed (part hide)
+  (let ((mime-type (notmuch-show-mime-type part)))
+    (and (not (notmuch-match-content-type mime-type "multipart/*"))
+        (not hide))))
+
+(defun notmuch-show-reply-insert-header-p-minimal (part hide)
+  (let ((mime-type (notmuch-show-mime-type part)))
+    (and (notmuch-match-content-type mime-type "text/*")
+        (not hide))))
+
+(defun notmuch-show-insert-bodypart (msg part depth &optional hide)
+  "Insert the body part PART at depth DEPTH in the current thread.
+
+HIDE determines whether to show or hide the part and the button
+as follows: If HIDE is nil, show the part and the button. If HIDE
+is t, hide the part initially and show the button."
+
+  (let* ((content-type (downcase (plist-get part :content-type)))
+        (mime-type (notmuch-show-mime-type part))
+        (nth (plist-get part :id))
+        (long (and (notmuch-match-content-type mime-type "text/*")
+                   (> notmuch-show-max-text-part-size 0)
+                   (> (length (plist-get part :content)) notmuch-show-max-text-part-size)))
+        (beg (point))
+        ;; This default header-p function omits the part button for
+        ;; the first (or only) part if this is text/plain.
+        (button (when (funcall notmuch-show-insert-header-p-function part hide)
+                  (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename))))
+        ;; Hide the part initially if HIDE is t, or if it is too long
+        ;; and we have a button to allow toggling.
+        (show-part (not (or (equal hide t)
+                            (and long button))))
+        (content-beg (point)))
+
+    ;; Store the computed mime-type for later use (e.g. by attachment handlers).
+    (plist-put part :computed-type mime-type)
+
+    (if show-part
+        (notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
+      (when button
+       (button-put button :notmuch-lazy-part
+                   (list msg part mime-type nth depth button))))
+
+    ;; Some of the body part handlers leave point somewhere up in the
+    ;; part, so we make sure that we're down at the end.
+    (goto-char (point-max))
+    ;; Ensure that the part ends with a carriage return.
+    (unless (bolp)
+      (insert "\n"))
+    ;; We do not create the overlay for hidden (lazy) parts until
+    ;; they are inserted.
+    (if show-part
+       (notmuch-show-create-part-overlays button content-beg (point))
+      (save-excursion
+       (notmuch-show-toggle-part-invisibility button)))
+    (notmuch-show-record-part-information part beg (point))))
+
+(defun notmuch-show-insert-body (msg body depth)
+  "Insert the body BODY at depth DEPTH in the current thread."
+
+  ;; Register all content IDs for this message.  According to RFC
+  ;; 2392, content IDs are *global*, but it's okay if an MUA treats
+  ;; them as only global within a message.
+  (notmuch-show--register-cids msg (first body))
+
+  (mapc (lambda (part) (notmuch-show-insert-bodypart msg part depth)) body))
+
+(defun notmuch-show-make-symbol (type)
+  (make-symbol (concat "notmuch-show-" type)))
+
+(defun notmuch-show-strip-re (string)
+  (replace-regexp-in-string "^\\([Rr]e: *\\)+" "" string))
+
+(defvar notmuch-show-previous-subject "")
+(make-variable-buffer-local 'notmuch-show-previous-subject)
+
+(defun notmuch-show-insert-msg (msg depth)
+  "Insert the message MSG at depth DEPTH in the current thread."
+  (let* ((headers (plist-get msg :headers))
+        ;; Indentation causes the buffer offset of the start/end
+        ;; points to move, so we must use markers.
+        message-start message-end
+        content-start content-end
+        headers-start headers-end
+        (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
+
+    (setq message-start (point-marker))
+
+    (notmuch-show-insert-headerline headers
+                                   (or (if notmuch-show-relative-dates
+                                           (plist-get msg :date_relative)
+                                         nil)
+                                       (plist-get headers :Date))
+                                   (plist-get msg :tags) depth)
+
+    (setq content-start (point-marker))
+
+    ;; Set `headers-start' to point after the 'Subject:' header to be
+    ;; compatible with the existing implementation. This just sets it
+    ;; to after the first header.
+    (notmuch-show-insert-headers headers)
+    (save-excursion
+      (goto-char content-start)
+      ;; If the subject of this message is the same as that of the
+      ;; previous message, don't display it when this message is
+      ;; collapsed.
+      (when (not (string= notmuch-show-previous-subject
+                         bare-subject))
+       (forward-line 1))
+      (setq headers-start (point-marker)))
+    (setq headers-end (point-marker))
+
+    (setq notmuch-show-previous-subject bare-subject)
+
+    ;; A blank line between the headers and the body.
+    (insert "\n")
+    (notmuch-show-insert-body msg (plist-get msg :body)
+                             (if notmuch-show-indent-content depth 0))
+    ;; Ensure that the body ends with a newline.
+    (unless (bolp)
+      (insert "\n"))
+    (setq content-end (point-marker))
+
+    ;; Indent according to the depth in the thread.
+    (if notmuch-show-indent-content
+       (indent-rigidly content-start content-end (* notmuch-show-indent-messages-width depth)))
+
+    (setq message-end (point-max-marker))
+
+    ;; Save the extents of this message over the whole text of the
+    ;; message.
+    (put-text-property message-start message-end :notmuch-message-extent (cons message-start message-end))
+
+    ;; Create overlays used to control visibility
+    (plist-put msg :headers-overlay (make-overlay headers-start headers-end))
+    (plist-put msg :message-overlay (make-overlay headers-start content-end))
+
+    (plist-put msg :depth depth)
+
+    ;; Save the properties for this message. Currently this saves the
+    ;; entire message (augmented it with other stuff), which seems
+    ;; like overkill. We might save a reduced subset (for example, not
+    ;; the content).
+    (notmuch-show-set-message-properties msg)
+
+    ;; Set header visibility.
+    (notmuch-show-headers-visible msg notmuch-message-headers-visible)
+
+    ;; Message visibility depends on whether it matched the search
+    ;; criteria.
+    (notmuch-show-message-visible msg (and (plist-get msg :match)
+                                          (not (plist-get msg :excluded))))))
+
+(defun notmuch-show-toggle-process-crypto ()
+  "Toggle the processing of cryptographic MIME parts."
+  (interactive)
+  (setq notmuch-show-process-crypto (not notmuch-show-process-crypto))
+  (message (if notmuch-show-process-crypto
+              "Processing cryptographic MIME parts."
+            "Not processing cryptographic MIME parts."))
+  (notmuch-show-refresh-view))
+
+(defun notmuch-show-toggle-elide-non-matching ()
+  "Toggle the display of non-matching messages."
+  (interactive)
+  (setq notmuch-show-elide-non-matching-messages (not notmuch-show-elide-non-matching-messages))
+  (message (if notmuch-show-elide-non-matching-messages
+              "Showing matching messages only."
+            "Showing all messages."))
+  (notmuch-show-refresh-view))
+
+(defun notmuch-show-toggle-thread-indentation ()
+  "Toggle the indentation of threads."
+  (interactive)
+  (setq notmuch-show-indent-content (not notmuch-show-indent-content))
+  (message (if notmuch-show-indent-content
+              "Content is indented."
+            "Content is not indented."))
+  (notmuch-show-refresh-view))
+
+(defun notmuch-show-insert-tree (tree depth)
+  "Insert the message tree TREE at depth DEPTH in the current thread."
+  (let ((msg (car tree))
+       (replies (cadr tree)))
+    ;; We test whether there is a message or just some replies.
+    (when msg
+      (notmuch-show-insert-msg msg depth))
+    (notmuch-show-insert-thread replies (1+ depth))))
+
+(defun notmuch-show-insert-thread (thread depth)
+  "Insert the thread THREAD at depth DEPTH in the current forest."
+  (mapc (lambda (tree) (notmuch-show-insert-tree tree depth)) thread))
+
+(defun notmuch-show-insert-forest (forest)
+  "Insert the forest of threads FOREST."
+  (mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
+
+(defvar notmuch-id-regexp
+  (concat
+   ;; Match the id: prefix only if it begins a word (to disallow, for
+   ;; example, matching cid:).
+   "\\<id:\\("
+   ;; If the term starts with a ", then parse Xapian's quoted boolean
+   ;; term syntax, which allows for anything as long as embedded
+   ;; double quotes escaped by doubling them.  We also disallow
+   ;; newlines (which Xapian allows) to prevent runaway terms.
+   "\"\\([^\"\n]\\|\"\"\\)*\""
+   ;; Otherwise, parse Xapian's unquoted syntax, which goes up to the
+   ;; next space or ).  We disallow [.,;] as the last character
+   ;; because these are probably part of the surrounding text, and not
+   ;; part of the id.  This doesn't match single character ids; meh.
+   "\\|[^\"[:space:])][^[:space:])]*[^])[:space:].,:;?!]"
+   "\\)")
+  "The regexp used to match id: links in messages.")
+
+(defvar notmuch-mid-regexp
+  ;; goto-address-url-regexp matched cid: links, which have the same
+  ;; grammar as the message ID part of a mid: link.  Construct the
+  ;; regexp using the same technique as goto-address-url-regexp.
+  (concat "\\<mid:\\(" thing-at-point-url-path-regexp "\\)")
+  "The regexp used to match mid: links in messages.
+
+See RFC 2392.")
+
+(defun notmuch-show-buttonise-links (start end)
+  "Buttonise URLs and mail addresses between START and END.
+
+This also turns id:\"<message id>\"-parts and mid: links into
+buttons for a corresponding notmuch search."
+  (goto-address-fontify-region start end)
+  (save-excursion
+    (let (links
+         (beg-line (progn (goto-char start) (line-beginning-position)))
+         (end-line (progn (goto-char end) (line-end-position))))
+      (goto-char beg-line)
+      (while (re-search-forward notmuch-id-regexp end-line t)
+       (push (list (match-beginning 0) (match-end 0)
+                   (match-string-no-properties 0)) links))
+      (goto-char beg-line)
+      (while (re-search-forward notmuch-mid-regexp end-line t)
+       (let* ((mid-cid (match-string-no-properties 1))
+              (mid (save-match-data
+                     (string-match "^[^/]*" mid-cid)
+                     (url-unhex-string (match-string 0 mid-cid)))))
+         (push (list (match-beginning 0) (match-end 0)
+                     (notmuch-id-to-query mid)) links)))
+      (dolist (link links)
+       ;; Remove the overlay created by goto-address-mode
+       (remove-overlays (first link) (second link) 'goto-address t)
+       (make-text-button (first link) (second link)
+                         :type 'notmuch-button-type
+                         'action `(lambda (arg)
+                                    (notmuch-show ,(third link) current-prefix-arg))
+                         'follow-link t
+                         'help-echo "Mouse-1, RET: search for this message"
+                         'face goto-address-mail-face)))))
+
+;;;###autoload
+(defun notmuch-show (thread-id &optional elide-toggle parent-buffer query-context buffer-name)
+  "Run \"notmuch show\" with the given thread ID and display results.
+
+ELIDE-TOGGLE, if non-nil, inverts the default elide behavior.
+
+The optional PARENT-BUFFER is the notmuch-search buffer from
+which this notmuch-show command was executed, (so that the
+next thread from that buffer can be show when done with this
+one).
+
+The optional QUERY-CONTEXT is a notmuch search term. Only
+messages from the thread matching this search term are shown if
+non-nil.
+
+The optional BUFFER-NAME provides the name of the buffer in
+which the message thread is shown. If it is nil (which occurs
+when the command is called interactively) the argument to the
+function is used.
+
+Returns the buffer containing the messages, or NIL if no messages
+matched."
+  (interactive "sNotmuch show: \nP")
+  (let ((buffer-name (generate-new-buffer-name
+                     (or buffer-name
+                         (concat "*notmuch-" thread-id "*"))))
+       ;; 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)
+
+    (notmuch-show-mode)
+
+    ;; Set various buffer local variables to their appropriate initial
+    ;; state. Do this after enabling `notmuch-show-mode' so that they
+    ;; aren't wiped out.
+    (setq notmuch-show-thread-id thread-id
+         notmuch-show-parent-buffer parent-buffer
+         notmuch-show-query-context (if (or (string= query-context "")
+                                            (string= query-context "*"))
+                                        nil query-context)
+
+         notmuch-show-process-crypto notmuch-crypto-process-mime
+         ;; If `elide-toggle', invert the default value.
+         notmuch-show-elide-non-matching-messages
+         (if elide-toggle
+             (not notmuch-show-only-matching-messages)
+           notmuch-show-only-matching-messages))
+
+    (add-hook 'post-command-hook #'notmuch-show-command-hook nil t)
+    (jit-lock-register #'notmuch-show-buttonise-links)
+
+    (notmuch-tag-clear-cache)
+
+    (let ((inhibit-read-only t))
+      (if (notmuch-show--build-buffer)
+         ;; Messages were inserted into the buffer.
+         (current-buffer)
+
+       ;; No messages were inserted - presumably none matched the
+       ;; query.
+       (kill-buffer (current-buffer))
+       (ding)
+       (message "No messages matched the query!")
+       nil))))
+
+(defun notmuch-show--build-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.
+
+Apply the previously saved STATE if supplied, otherwise show the
+first relevant message.
+
+If no messages match the query return NIL."
+  (let* ((cli-args (cons "--exclude=false"
+                        (when notmuch-show-elide-non-matching-messages
+                          (list "--entire-thread=false"))))
+        (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)
+
+      ;; Store the original tags for each message so that we can
+      ;; display changes.
+      (notmuch-show-mapc
+       (lambda () (notmuch-show-set-prop :orig-tags (notmuch-show-get-tags))))
+
+      ;; Set the header line to the subject of the first message.
+      (setq header-line-format
+           (replace-regexp-in-string "%" "%%"
+                                     (notmuch-sanitize
+                                      (notmuch-show-strip-re
+                                       (notmuch-show-get-subject)))))
+
+      (run-hooks 'notmuch-show-hook)
+
+      (if state
+         (notmuch-show-apply-state state)
+       ;; With no state to apply, just go to the first message.
+       (notmuch-show-goto-first-wanted-message)))
+
+    ;; Report back to the caller whether any messages matched.
+    forest))
+
+(defun notmuch-show-capture-state ()
+  "Capture the state of the current buffer.
+
+This includes:
+ - the list of 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"
+  (if notmuch-show-query-context
+      (concat notmuch-show-thread-id
+             " and ("
+             notmuch-show-query-context
+             ")")
+    notmuch-show-thread-id))
+
+(defun notmuch-show-goto-message (msg-id)
+  "Go to message with msg-id."
+  (goto-char (point-min))
+  (unless (loop if (string= msg-id (notmuch-show-get-message-id))
+               return t
+               until (not (notmuch-show-goto-message-next)))
+    (goto-char (point-min))
+    (message "Message-id not found."))
+  (notmuch-show-message-adjust))
+
+(defun notmuch-show-apply-state (state)
+  "Apply STATE to the current buffer.
+
+This includes:
+ - opening the messages previously opened,
+ - closing all other messages,
+ - moving to the correct current message in every displayed window."
+  (let ((win-msg-alist (car state))
+       (open (cadr state)))
+
+    ;; Open those that were open.
+    (goto-char (point-min))
+    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
+                                          (member (notmuch-show-get-message-id) open))
+         until (not (notmuch-show-goto-message-next)))
+
+    (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.
+
+Refreshes the current view, observing changes in display
+preferences. If invoked with a prefix argument (or RESET-STATE is
+non-nil) then the state of the buffer (open/closed messages) is
+reset based on the original query."
+  (interactive "P")
+  (let ((inhibit-read-only t)
+       (state (unless reset-state
+                (notmuch-show-capture-state))))
+    ;; `erase-buffer' does not seem to remove overlays, which can lead
+    ;; to weird effects such as remaining images, so remove them
+    ;; manually.
+    (remove-overlays)
+    (erase-buffer)
+
+    (unless (notmuch-show--build-buffer state)
+      ;; No messages were inserted.
+      (kill-buffer (current-buffer))
+      (ding)
+      (message "Refreshing the buffer resulted in no messages!"))))
+
+(defvar notmuch-show-stash-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "c" 'notmuch-show-stash-cc)
+    (define-key map "d" 'notmuch-show-stash-date)
+    (define-key map "F" 'notmuch-show-stash-filename)
+    (define-key map "f" 'notmuch-show-stash-from)
+    (define-key map "i" 'notmuch-show-stash-message-id)
+    (define-key map "I" 'notmuch-show-stash-message-id-stripped)
+    (define-key map "s" 'notmuch-show-stash-subject)
+    (define-key map "T" 'notmuch-show-stash-tags)
+    (define-key map "t" 'notmuch-show-stash-to)
+    (define-key map "l" 'notmuch-show-stash-mlarchive-link)
+    (define-key map "L" 'notmuch-show-stash-mlarchive-link-and-go)
+    (define-key map "G" 'notmuch-show-stash-git-send-email)
+    (define-key map "?" 'notmuch-subkeymap-help)
+    map)
+  "Submap for stash commands")
+(fset 'notmuch-show-stash-map notmuch-show-stash-map)
+
+(defvar notmuch-show-part-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "s" 'notmuch-show-save-part)
+    (define-key map "v" 'notmuch-show-view-part)
+    (define-key map "o" 'notmuch-show-interactively-view-part)
+    (define-key map "|" 'notmuch-show-pipe-part)
+    (define-key map "m" 'notmuch-show-choose-mime-of-part)
+    (define-key map "?" 'notmuch-subkeymap-help)
+    map)
+  "Submap for part commands")
+(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 "F" 'notmuch-show-forward-open-messages)
+    (define-key map "b" 'notmuch-show-resend-message)
+    (define-key map "l" 'notmuch-show-filter-thread)
+    (define-key map "r" 'notmuch-show-reply-sender)
+    (define-key map "R" 'notmuch-show-reply)
+    (define-key map "|" 'notmuch-show-pipe-message)
+    (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)
+    (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)
+
+(define-derived-mode notmuch-show-mode fundamental-mode "notmuch-show"
+  "Major mode for viewing a thread with notmuch.
+
+This buffer contains the results of the \"notmuch show\" command
+for displaying a single thread of email from your email archives.
+
+By default, various components of email messages, (citations,
+signatures, already-read messages), are hidden. You can make
+these parts visible by clicking with the mouse button or by
+pressing RET after positioning the cursor on a hidden part, (for
+which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful).
+
+Reading the thread sequentially is well-supported by pressing
+\\[notmuch-show-advance-and-archive]. This will scroll the current message (if necessary), advance
+to the next message, or advance to the next thread (if already on
+the last message of a thread).
+
+Other commands are available to read or manipulate the thread
+more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages
+without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread
+without scrolling through with \\[notmuch-show-advance-and-archive]).
+
+You can add or remove arbitrary tags from the current message with
+'\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'.
+
+All currently available key bindings:
+
+\\{notmuch-show-mode-map}"
+  (setq notmuch-buffer-refresh-function #'notmuch-show-refresh-view)
+  (setq buffer-read-only t
+       truncate-lines t)
+  (setq imenu-prev-index-position-function
+        #'notmuch-show-imenu-prev-index-position-function)
+  (setq imenu-extract-index-name-function
+        #'notmuch-show-imenu-extract-index-name-function))
+
+(defun notmuch-tree-from-show-current-query ()
+  "Call notmuch tree with the current query"
+  (interactive)
+  (notmuch-tree notmuch-show-thread-id
+               notmuch-show-query-context
+               (notmuch-show-get-message-id)))
+
+(defun notmuch-show-move-to-message-top ()
+  (goto-char (notmuch-show-message-top)))
+
+(defun notmuch-show-move-to-message-bottom ()
+  (goto-char (notmuch-show-message-bottom)))
+
+(defun notmuch-show-message-adjust ()
+  (recenter 0))
+
+;; Movement related functions.
+
+;; There's some strangeness here where a text property applied to a
+;; region a->b is not found when point is at b. We walk backwards
+;; until finding the property.
+(defun notmuch-show-message-extent ()
+  (let (r)
+    (save-excursion
+      (while (not (setq r (get-text-property (point) :notmuch-message-extent)))
+       (backward-char)))
+    r))
+
+(defun notmuch-show-message-top ()
+  (car (notmuch-show-message-extent)))
+
+(defun notmuch-show-message-bottom ()
+  (cdr (notmuch-show-message-extent)))
+
+(defun notmuch-show-goto-message-next ()
+  (let ((start (point)))
+    (notmuch-show-move-to-message-bottom)
+    (if (not (eobp))
+       t
+      (goto-char start)
+      nil)))
+
+(defun notmuch-show-goto-message-previous ()
+  (notmuch-show-move-to-message-top)
+  (if (bobp)
+      nil
+    (backward-char)
+    (notmuch-show-move-to-message-top)
+    t))
+
+(defun notmuch-show-mapc (function)
+  "Iterate through all messages in the current thread with
+`notmuch-show-goto-message-next' and call FUNCTION for side
+effects."
+  (save-excursion
+    (goto-char (point-min))
+    (loop do (funcall function)
+         while (notmuch-show-goto-message-next))))
+
+;; Functions relating to the visibility of messages and their
+;; components.
+
+(defun notmuch-show-message-visible (props visible-p)
+  (overlay-put (plist-get props :message-overlay) 'invisible (not visible-p))
+  (notmuch-show-set-prop :message-visible visible-p props))
+
+(defun notmuch-show-headers-visible (props visible-p)
+  (overlay-put (plist-get props :headers-overlay) 'invisible (not visible-p))
+  (notmuch-show-set-prop :headers-visible visible-p props))
+
+;; Functions for setting and getting attributes of the current
+;; message.
+
+(defun notmuch-show-set-message-properties (props)
+  (save-excursion
+    (notmuch-show-move-to-message-top)
+    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+
+(defun notmuch-show-get-message-properties ()
+  "Return the properties of the current message as a plist.
+
+Some useful entries are:
+:headers - Property list containing the headers :Date, :Subject, :From, etc.
+:body - Body of the message
+:tags - Tags for this message"
+  (save-excursion
+    (notmuch-show-move-to-message-top)
+    (get-text-property (point) :notmuch-message-properties)))
+
+(defun notmuch-show-get-part-properties ()
+  "Return the properties of the innermost part containing point.
+
+This is the part property list retrieved from the CLI.  Signals
+an error if there is no part containing point."
+  (or (get-text-property (point) :notmuch-part)
+      (error "No message part here")))
+
+(defun notmuch-show-set-prop (prop val &optional props)
+  (let ((inhibit-read-only t)
+       (props (or props
+                  (notmuch-show-get-message-properties))))
+    (plist-put props prop val)
+    (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
+                  (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)
+  "Return an id: query for the Message-Id of the current message.
+
+If optional argument BARE is non-nil, return
+the Message-Id without id: prefix and escaping."
+  (if bare
+      (notmuch-show-get-prop :id)
+    (notmuch-id-to-query (notmuch-show-get-prop :id))))
+
+(defun notmuch-show-get-messages-ids ()
+  "Return all id: queries of messages in the current thread."
+  (let ((message-ids))
+    (notmuch-show-mapc
+     (lambda () (push (notmuch-show-get-message-id) message-ids)))
+    message-ids))
+
+(defun notmuch-show-get-messages-ids-search ()
+  "Return a search string for all message ids of messages in the
+current thread."
+  (mapconcat 'identity (notmuch-show-get-messages-ids) " or "))
+
+;; dme: Would it make sense to use a macro for many of these?
+
+;; XXX TODO figure out what to do about multiple filenames
+(defun notmuch-show-get-filename ()
+  "Return the filename of the current message."
+  (car (notmuch-show-get-prop :filename)))
+
+(defun notmuch-show-get-header (header &optional props)
+  "Return the named header of the current message, if any."
+  (plist-get (notmuch-show-get-prop :headers props) header))
+
+(defun notmuch-show-get-cc ()
+  (notmuch-show-get-header :Cc))
+
+(defun notmuch-show-get-date ()
+  (notmuch-show-get-header :Date))
+
+(defun notmuch-show-get-timestamp ()
+  (notmuch-show-get-prop :timestamp))
+
+(defun notmuch-show-get-from ()
+  (notmuch-show-get-header :From))
+
+(defun notmuch-show-get-subject ()
+  (notmuch-show-get-header :Subject))
+
+(defun notmuch-show-get-to ()
+  (notmuch-show-get-header :To))
+
+(defun notmuch-show-get-depth ()
+  (notmuch-show-get-prop :depth))
+
+(defun notmuch-show-set-tags (tags)
+  "Set the tags of the current message."
+  (notmuch-show-set-prop :tags tags)
+  (notmuch-show-update-tags tags))
+
+(defun notmuch-show-get-tags ()
+  "Return the tags of the current message."
+  (notmuch-show-get-prop :tags))
+
+(defun notmuch-show-message-visible-p ()
+  "Is the current message visible?"
+  (notmuch-show-get-prop :message-visible))
+
+(defun notmuch-show-headers-visible-p ()
+  "Are the headers of the current message visible?"
+  (notmuch-show-get-prop :headers-visible))
+
+(put 'notmuch-show-mark-read 'notmuch-prefix-doc
+     "Mark the current message as unread.")
+(defun notmuch-show-mark-read (&optional unread)
+  "Mark the current message as read.
+
+Mark the current message as read by applying the tag changes in
+`notmuch-show-mark-read-tags' to it (remove the \"unread\" tag by
+default). If a prefix argument is given, the message will be
+marked as unread, i.e. the tag changes in
+`notmuch-show-mark-read-tags' will be reversed."
+  (interactive "P")
+  (when notmuch-show-mark-read-tags
+    (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)))
+
+(defvar notmuch-show--seen-has-errored nil)
+(make-variable-buffer-local 'notmuch-show--seen-has-errored)
+
+(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
+      (condition-case err
+         (funcall notmuch-show-mark-read-function (window-start) (window-end))
+       ((debug error)
+        (unless notmuch-show--seen-has-errored
+          (setq notmuch-show--seen-has-errored 't)
+          (setq header-line-format
+                (concat header-line-format
+                        (propertize "  [some mark read tag changes may have failed]"
+                                    'face font-lock-warning-face)))))))))
+
+(defun notmuch-show-filter-thread (query)
+  "Filter or LIMIT the current thread based on a new query string.
+
+Reshows the current thread with matches defined by the new query-string."
+  (interactive (list (notmuch-read-query "Filter thread: ")))
+  (let ((msg-id (notmuch-show-get-message-id)))
+    (setq notmuch-show-query-context (if (string= query "") nil query))
+    (notmuch-show-refresh-view t)
+    (notmuch-show-goto-message msg-id)))
+
+;; Functions for getting attributes of several messages in the current
+;; thread.
+
+(defun notmuch-show-get-message-ids-for-open-messages ()
+  "Return a list of all id: queries for open messages in the current thread."
+  (save-excursion
+    (let (message-ids done)
+      (goto-char (point-min))
+      (while (not done)
+       (if (notmuch-show-message-visible-p)
+           (setq message-ids (append message-ids (list (notmuch-show-get-message-id)))))
+       (setq done (not (notmuch-show-goto-message-next)))
+       )
+      message-ids
+      )))
+
+;; Commands typically bound to keys.
+
+(defun notmuch-show-advance ()
+  "Advance through thread.
+
+If the current message in the thread is not yet fully visible,
+scroll by a near screenful to read more of the message.
+
+Otherwise, (the end of the current message is already within the
+current window), advance to the next open message."
+  (interactive)
+  (let* ((end-of-this-message (notmuch-show-message-bottom))
+        (visible-end-of-this-message (1- end-of-this-message))
+        (ret nil))
+    (while (invisible-p visible-end-of-this-message)
+      (setq visible-end-of-this-message
+           (max (point-min)
+                (1- (previous-single-char-property-change
+                     visible-end-of-this-message 'invisible)))))
+    (cond
+     ;; Ideally we would test `end-of-this-message' against the result
+     ;; of `window-end', but that doesn't account for the fact that
+     ;; the end of the message might be hidden.
+     ((and visible-end-of-this-message
+          (> visible-end-of-this-message (window-end)))
+      ;; The bottom of this message is not visible - scroll.
+      (scroll-up nil))
+
+     ((not (= end-of-this-message (point-max)))
+      ;; This is not the last message - move to the next visible one.
+      (notmuch-show-next-open-message))
+
+     ((not (= (point) (point-max)))
+      ;; This is the last message, but the cursor is not at the end of
+      ;; the buffer. Move it there.
+      (goto-char (point-max)))
+
+     (t
+      ;; This is the last message - change the return value
+      (setq ret t)))
+    ret))
+
+(defun notmuch-show-advance-and-archive ()
+  "Advance through thread and archive.
+
+This command is intended to be one of the simplest ways to
+process a thread of email. It works exactly like
+notmuch-show-advance, in that it scrolls through messages in a
+show buffer, except that when it gets to the end of the buffer it
+archives the entire current thread, (apply changes in
+`notmuch-archive-tags'), kills the buffer, and displays the next
+thread from the search from which this thread was originally
+shown."
+  (interactive)
+  (if (notmuch-show-advance)
+      (notmuch-show-archive-thread-then-next)))
+
+(defun notmuch-show-rewind ()
+  "Backup through the thread (reverse scrolling compared to \\[notmuch-show-advance-and-archive]).
+
+Specifically, if the beginning of the previous email is fewer
+than `window-height' lines from the current point, move to it
+just like `notmuch-show-previous-message'.
+
+Otherwise, just scroll down a screenful of the current message.
+
+This command does not modify any message tags, (it does not undo
+any effects from previous calls to
+`notmuch-show-advance-and-archive'."
+  (interactive)
+  (let ((start-of-message (notmuch-show-message-top))
+       (start-of-window (window-start)))
+    (cond
+      ;; Either this message is properly aligned with the start of the
+      ;; window or the start of this message is not visible on the
+      ;; screen - scroll.
+     ((or (= start-of-message start-of-window)
+         (< start-of-message start-of-window))
+      (scroll-down)
+      ;; If a small number of lines from the previous message are
+      ;; visible, realign so that the top of the current message is at
+      ;; the top of the screen.
+      (when (<= (count-screen-lines (window-start) start-of-message)
+               next-screen-context-lines)
+       (goto-char (notmuch-show-message-top))
+       (notmuch-show-message-adjust))
+      ;; Move to the top left of the window.
+      (goto-char (window-start)))
+     (t
+      ;; Move to the previous message.
+      (notmuch-show-previous-message)))))
+
+(put 'notmuch-show-reply 'notmuch-prefix-doc "... and prompt for sender")
+(defun notmuch-show-reply (&optional prompt-for-sender)
+  "Reply to the sender and all recipients of the current message."
+  (interactive "P")
+  (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender t))
+
+(put 'notmuch-show-reply-sender 'notmuch-prefix-doc "... and prompt for sender")
+(defun notmuch-show-reply-sender (&optional prompt-for-sender)
+  "Reply to the sender of the current message."
+  (interactive "P")
+  (notmuch-mua-new-reply (notmuch-show-get-message-id) prompt-for-sender nil))
+
+(put 'notmuch-show-forward-message 'notmuch-prefix-doc
+     "... and prompt for sender")
+(defun notmuch-show-forward-message (&optional prompt-for-sender)
+  "Forward the current message."
+  (interactive "P")
+  (notmuch-mua-new-forward-messages (list (notmuch-show-get-message-id))
+                                   prompt-for-sender))
+
+(put 'notmuch-show-forward-open-messages 'notmuch-prefix-doc
+     "... and prompt for sender")
+(defun notmuch-show-forward-open-messages (&optional prompt-for-sender)
+  "Forward the currently open messages."
+  (interactive "P")
+  (let ((open-messages (notmuch-show-get-message-ids-for-open-messages)))
+    (unless open-messages
+      (error "No open messages to forward."))
+    (notmuch-mua-new-forward-messages open-messages prompt-for-sender)))
+
+(defun notmuch-show-resend-message (addresses)
+  "Resend the current message."
+  (interactive (list (notmuch-address-from-minibuffer "Resend to: ")))
+  (when (y-or-n-p (concat "Confirm resend to " addresses " "))
+    (notmuch-show-view-raw-message)
+    (message-resend addresses)
+    (notmuch-bury-or-kill-this-buffer)))
+
+(defun notmuch-show-next-message (&optional pop-at-end)
+  "Show the next message.
+
+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)
+      (notmuch-show-message-adjust)
+    (if pop-at-end
+       (notmuch-show-next-thread)
+      (goto-char (point-max)))))
+
+(defun notmuch-show-previous-message ()
+  "Show the previous message or the start of the current message."
+  (interactive)
+  (if (= (point) (notmuch-show-message-top))
+      (notmuch-show-goto-message-previous)
+    (notmuch-show-move-to-message-top))
+  (notmuch-show-message-adjust))
+
+(defun notmuch-show-next-open-message (&optional pop-at-end)
+  "Show the next open message.
+
+If a prefix argument is given and this is the last open message
+in the thread, navigate to the next thread in the parent search
+buffer. Return t if there was a next open message in the thread
+to show, nil otherwise."
+  (interactive "P")
+  (let (r)
+    (while (and (setq r (notmuch-show-goto-message-next))
+               (not (notmuch-show-message-visible-p))))
+    (if r
+       (notmuch-show-message-adjust)
+      (if pop-at-end
+         (notmuch-show-next-thread)
+       (goto-char (point-max))))
+    r))
+
+(defun notmuch-show-next-matching-message ()
+  "Show the next matching message."
+  (interactive)
+  (let (r)
+    (while (and (setq r (notmuch-show-goto-message-next))
+               (not (notmuch-show-get-prop :match))))
+    (if r
+       (notmuch-show-message-adjust)
+      (goto-char (point-max)))))
+
+(defun notmuch-show-open-if-matched ()
+  "Open a message if it is matched (whether or not excluded)."
+  (let ((props (notmuch-show-get-message-properties)))
+    (notmuch-show-message-visible props (plist-get props :match))))
+
+(defun notmuch-show-goto-first-wanted-message ()
+  "Move to the first open message and mark it read"
+  (goto-char (point-min))
+  (unless (notmuch-show-message-visible-p)
+    (notmuch-show-next-open-message))
+  (when (eobp)
+    ;; There are no matched non-excluded messages so open all matched
+    ;; (necessarily excluded) messages and go to the first.
+    (notmuch-show-mapc 'notmuch-show-open-if-matched)
+    (force-window-update)
+    (goto-char (point-min))
+    (unless (notmuch-show-message-visible-p)
+      (notmuch-show-next-open-message))))
+
+(defun notmuch-show-previous-open-message ()
+  "Show the previous open message."
+  (interactive)
+  (while (and (if (= (point) (notmuch-show-message-top))
+                 (notmuch-show-goto-message-previous)
+               (notmuch-show-move-to-message-top))
+             (not (notmuch-show-message-visible-p))))
+  (notmuch-show-message-adjust))
+
+(defun notmuch-show-view-raw-message ()
+  "View the original source of the current message."
+  (interactive)
+  (let* ((id (notmuch-show-get-message-id))
+        (buf (get-buffer-create (concat "*notmuch-raw-" id "*")))
+        (inhibit-read-only t))
+    (switch-to-buffer buf)
+    (erase-buffer)
+    (let ((coding-system-for-read 'no-conversion))
+      (call-process notmuch-command nil t nil "show" "--format=raw" id))
+    (goto-char (point-min))
+    (set-buffer-modified-p nil)
+    (setq buffer-read-only t)
+    (view-buffer buf 'kill-buffer-if-not-modified)))
+
+(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
+     "Pipe the thread as an mbox to a command.")
+(defun notmuch-show-pipe-message (entire-thread command)
+  "Pipe the contents of the current message (or thread) to COMMAND.
+
+COMMAND will be executed with the raw contents of the current
+email message as stdin. Anything printed by the command to stdout
+or stderr will appear in the *notmuch-pipe* buffer.
+
+If ENTIRE-THREAD is non-nil (or when invoked with a prefix
+argument), COMMAND will receive all open messages in the current
+thread (formatted as an mbox) rather than only the current
+message."
+  (interactive (let ((query-string (if current-prefix-arg
+                                      "Pipe all open messages to command: "
+                                    "Pipe message to command: ")))
+                (list current-prefix-arg (read-string query-string))))
+  (let (shell-command)
+    (if entire-thread
+       (setq shell-command
+             (concat notmuch-command " show --format=mbox --exclude=false "
+                     (shell-quote-argument
+                      (mapconcat 'identity (notmuch-show-get-message-ids-for-open-messages) " OR "))
+                     " | " command))
+      (setq shell-command
+           (concat notmuch-command " show --format=raw "
+                   (shell-quote-argument (notmuch-show-get-message-id)) " | " command)))
+    (let ((cwd default-directory)
+         (buf (get-buffer-create (concat "*notmuch-pipe*"))))
+      (with-current-buffer buf
+       (setq buffer-read-only nil)
+       (erase-buffer)
+       ;; Use the originating buffer's working directory instead of
+       ;; that of the pipe buffer.
+       (cd cwd)
+       (let ((exit-code (call-process-shell-command shell-command nil buf)))
+         (goto-char (point-max))
+         (set-buffer-modified-p nil)
+         (setq buffer-read-only t)
+         (unless (zerop exit-code)
+           (switch-to-buffer-other-window buf)
+           (message (format "Command '%s' exited abnormally with code %d"
+                            shell-command exit-code))))))))
+
+(defun notmuch-show-tag-message (&rest tag-changes)
+  "Change tags for the current message.
+
+TAG-CHANGES is a list of tag operations for `notmuch-tag'."
+  (let* ((current-tags (notmuch-show-get-tags))
+        (new-tags (notmuch-update-tags current-tags tag-changes)))
+    (unless (equal current-tags new-tags)
+      (notmuch-tag (notmuch-show-get-message-id) tag-changes)
+      (notmuch-show-set-tags new-tags))))
+
+(defun notmuch-show-tag (tag-changes)
+  "Change tags for the current message.
+
+See `notmuch-tag' for information on the format of TAG-CHANGES."
+  (interactive (list (notmuch-read-tag-changes (notmuch-show-get-tags)
+                                              "Tag message")))
+  (notmuch-tag (notmuch-show-get-message-id) tag-changes)
+  (let* ((current-tags (notmuch-show-get-tags))
+        (new-tags (notmuch-update-tags current-tags tag-changes)))
+    (unless (equal current-tags new-tags)
+      (notmuch-show-set-tags new-tags))))
+
+(defun notmuch-show-tag-all (tag-changes)
+  "Change tags for all messages in the current show buffer.
+
+See `notmuch-tag' for information on the format of TAG-CHANGES."
+  (interactive
+   (list (let (tags)
+          (notmuch-show-mapc
+           (lambda () (setq tags (append (notmuch-show-get-tags) tags))))
+          (notmuch-read-tag-changes tags "Tag thread"))))
+  (notmuch-tag (notmuch-show-get-messages-ids-search) tag-changes)
+  (notmuch-show-mapc
+   (lambda ()
+     (let* ((current-tags (notmuch-show-get-tags))
+           (new-tags (notmuch-update-tags current-tags tag-changes)))
+       (unless (equal current-tags new-tags)
+        (notmuch-show-set-tags new-tags))))))
+
+(defun notmuch-show-add-tag (tag-changes)
+  "Change tags for the current message (defaulting to add).
+
+Same as `notmuch-show-tag' but sets initial input to '+'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "+")))
+  (notmuch-show-tag tag-changes))
+
+(defun notmuch-show-remove-tag (tag-changes)
+  "Change tags for the current message (defaulting to remove).
+
+Same as `notmuch-show-tag' but sets initial input to '-'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-show-get-tags) "Tag message" "-")))
+  (notmuch-show-tag tag-changes))
+
+(defun notmuch-show-toggle-visibility-headers ()
+  "Toggle the visibility of the current message headers."
+  (interactive)
+  (let ((props (notmuch-show-get-message-properties)))
+    (notmuch-show-headers-visible
+     props
+     (not (plist-get props :headers-visible))))
+  (force-window-update))
+
+(defun notmuch-show-toggle-message ()
+  "Toggle the visibility of the current message."
+  (interactive)
+  (let ((props (notmuch-show-get-message-properties)))
+    (notmuch-show-message-visible
+     props
+     (not (plist-get props :message-visible))))
+  (force-window-update))
+
+(put 'notmuch-show-open-or-close-all 'notmuch-doc "Show all messages.")
+(put 'notmuch-show-open-or-close-all 'notmuch-prefix-doc "Hide all messages.")
+(defun notmuch-show-open-or-close-all ()
+  "Set the visibility all of the messages in the current thread.
+
+By default make all of the messages visible. With a prefix
+argument, hide all of the messages."
+  (interactive)
+  (save-excursion
+    (goto-char (point-min))
+    (loop do (notmuch-show-message-visible (notmuch-show-get-message-properties)
+                                          (not current-prefix-arg))
+         until (not (notmuch-show-goto-message-next))))
+  (force-window-update))
+
+(defun notmuch-show-next-button ()
+  "Advance point to the next button in the buffer."
+  (interactive)
+  (forward-button 1))
+
+(defun notmuch-show-previous-button ()
+  "Move point back to the previous button in the buffer."
+  (interactive)
+  (backward-button 1))
+
+(defun notmuch-show-next-thread (&optional show previous)
+  "Move to the next item in the search results, if any.
+
+If SHOW is non-nil, open the next item in a show
+buffer. Otherwise just highlight the next item in the search
+buffer. If PREVIOUS is non-nil, move to the previous item in the
+search results instead."
+  (interactive "P")
+  (let ((parent-buffer notmuch-show-parent-buffer))
+    (notmuch-bury-or-kill-this-buffer)
+    (when (buffer-live-p parent-buffer)
+      (switch-to-buffer parent-buffer)
+      (and (if previous
+              (notmuch-search-previous-thread)
+            (notmuch-search-next-thread))
+          show
+          (notmuch-search-show-thread)))))
+
+(defun notmuch-show-next-thread-show ()
+  "Show the next thread in the search results, if any."
+  (interactive)
+  (notmuch-show-next-thread t))
+
+(defun notmuch-show-previous-thread-show ()
+  "Show the previous thread in the search results, if any."
+  (interactive)
+  (notmuch-show-next-thread t t))
+
+(put 'notmuch-show-archive-thread 'notmuch-prefix-doc
+     "Un-archive each message in thread.")
+(defun notmuch-show-archive-thread (&optional unarchive)
+  "Archive each message in thread.
+
+Archive each message currently shown by applying the tag changes
+in `notmuch-archive-tags' to each. If a prefix argument is given,
+the messages will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed.
+
+Note: This command is safe from any race condition of new messages
+being delivered to the same thread. It does not archive the
+entire thread, but only the messages shown in the current
+buffer."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (notmuch-show-tag-all
+     (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+(defun notmuch-show-archive-thread-then-next ()
+  "Archive all messages in the current buffer, then show next thread from search."
+  (interactive)
+  (notmuch-show-archive-thread)
+  (notmuch-show-next-thread t))
+
+(defun notmuch-show-archive-thread-then-exit ()
+  "Archive all messages in the current buffer, then exit back to search results."
+  (interactive)
+  (notmuch-show-archive-thread)
+  (notmuch-show-next-thread))
+
+(put 'notmuch-show-archive-message 'notmuch-prefix-doc
+     "Un-archive the current message.")
+(defun notmuch-show-archive-message (&optional unarchive)
+  "Archive the current message.
+
+Archive the current message by applying the tag changes in
+`notmuch-archive-tags' to it. If a prefix argument is given, the
+message will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (apply 'notmuch-show-tag-message
+          (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+(defun notmuch-show-archive-message-then-next-or-exit ()
+  "Archive the current message, then show the next open message in the current thread.
+
+If at the last open message in the current thread, then exit back
+to search results."
+  (interactive)
+  (notmuch-show-archive-message)
+  (notmuch-show-next-open-message t))
+
+(defun notmuch-show-archive-message-then-next-or-next-thread ()
+  "Archive the current message, then show the next open message in the current thread.
+
+If at the last open message in the current thread, then show next
+thread from search."
+  (interactive)
+  (notmuch-show-archive-message)
+  (unless (notmuch-show-next-open-message)
+    (notmuch-show-next-thread t)))
+
+(defun notmuch-show-stash-cc ()
+  "Copy CC field of current message to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-cc)))
+
+(put 'notmuch-show-stash-date 'notmuch-prefix-doc
+     "Copy timestamp of current message to kill-ring.")
+(defun notmuch-show-stash-date (&optional stash-timestamp)
+  "Copy date of current message to kill-ring.
+
+If invoked with a prefix argument, copy timestamp of current
+message to kill-ring."
+  (interactive "P")
+  (if stash-timestamp
+      (notmuch-common-do-stash (format "%d" (notmuch-show-get-timestamp)))
+    (notmuch-common-do-stash (notmuch-show-get-date))))
+
+(defun notmuch-show-stash-filename ()
+  "Copy filename of current message to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-filename)))
+
+(defun notmuch-show-stash-from ()
+  "Copy From address of current message to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-from)))
+
+(put 'notmuch-show-stash-message-id 'notmuch-prefix-doc
+     "Copy thread: query matching current thread to kill-ring.")
+(defun notmuch-show-stash-message-id (&optional stash-thread-id)
+  "Copy id: query matching the current message to kill-ring.
+
+If invoked with a prefix argument (or STASH-THREAD-ID is
+non-nil), copy thread: query matching the current thread to
+kill-ring."
+  (interactive "P")
+  (if stash-thread-id
+      (notmuch-common-do-stash notmuch-show-thread-id)
+    (notmuch-common-do-stash (notmuch-show-get-message-id))))
+
+(defun notmuch-show-stash-message-id-stripped ()
+  "Copy message ID of current message (sans `id:' prefix) to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-message-id t)))
+
+(defun notmuch-show-stash-subject ()
+  "Copy Subject field of current message to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-subject)))
+
+(defun notmuch-show-stash-tags ()
+  "Copy tags of current message to kill-ring as a comma separated list."
+  (interactive)
+  (notmuch-common-do-stash (mapconcat 'identity (notmuch-show-get-tags) ",")))
+
+(defun notmuch-show-stash-to ()
+  "Copy To address of current message to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-show-get-to)))
+
+(defun notmuch-show-stash-mlarchive-link (&optional mla)
+  "Copy an ML Archive URI for the current message to the kill-ring.
+
+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)
+  (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.
+
+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-show-stash-mlarchive-link mla)
+  (browse-url (current-kill 0 t)))
+
+(defun notmuch-show-stash-git-helper (addresses prefix)
+  "Escape, trim, quote, and add PREFIX to each address in list of ADDRESSES, and return the result as a single string."
+  (mapconcat (lambda (x)
+              (concat prefix "\""
+                      ;; escape double-quotes
+                      (replace-regexp-in-string
+                       "\"" "\\\\\""
+                       ;; trim leading and trailing spaces
+                       (replace-regexp-in-string
+                        "\\(^ *\\| *$\\)" ""
+                        x)) "\""))
+            addresses " "))
+
+(put 'notmuch-show-stash-git-send-email 'notmuch-prefix-doc
+     "Copy From/To/Cc of current message to kill-ring in a form suitable for pasting to git send-email command line.")
+
+(defun notmuch-show-stash-git-send-email (&optional no-in-reply-to)
+  "Copy From/To/Cc/Message-Id of current message to kill-ring in a form suitable for pasting to git send-email command line.
+
+If invoked with a prefix argument (or NO-IN-REPLY-TO is non-nil),
+omit --in-reply-to=<Message-Id>."
+  (interactive "P")
+  (notmuch-common-do-stash
+   (mapconcat 'identity
+             (remove ""
+                     (list
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-from)) "--to=")
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-to)) "--to=")
+                      (notmuch-show-stash-git-helper
+                       (message-tokenize-header (notmuch-show-get-cc)) "--cc=")
+                      (unless no-in-reply-to
+                        (notmuch-show-stash-git-helper
+                         (list (notmuch-show-get-message-id t)) "--in-reply-to="))))
+             " ")))
+
+;; Interactive part functions and their helpers
+
+(defun notmuch-show-generate-part-buffer (msg part)
+  "Return a temporary buffer containing the specified part's content."
+  (let ((buf (generate-new-buffer " *notmuch-part*"))
+       (process-crypto notmuch-show-process-crypto))
+    (with-current-buffer buf
+      ;; This is always used in the content of mm handles, which
+      ;; expect undecoded, binary part content.
+      (insert (notmuch-get-bodypart-binary msg part process-crypto)))
+    buf))
+
+(defun notmuch-show-current-part-handle (&optional mime-type)
+  "Return an mm-handle for the part containing point.
+
+This creates a temporary buffer for the part's content; the
+caller is responsible for killing this buffer as appropriate.  If
+MIME-TYPE is given then set the handle's mime-type to MIME-TYPE."
+  (let* ((msg (notmuch-show-get-message-properties))
+        (part (notmuch-show-get-part-properties))
+        (buf (notmuch-show-generate-part-buffer msg part))
+        (computed-type (or mime-type (plist-get part :computed-type)))
+        (filename (plist-get part :filename))
+        (disposition (if filename `(attachment (filename . ,filename)))))
+    (mm-make-handle buf (list computed-type) nil nil disposition)))
+
+(defun notmuch-show-apply-to-current-part-handle (fn &optional mime-type)
+  "Apply FN to an mm-handle for the part containing point.
+
+This ensures that the temporary buffer created for the mm-handle
+is destroyed when FN returns. If MIME-TYPE is given then force
+part to be treated as if it had that mime-type."
+  (let ((handle (notmuch-show-current-part-handle mime-type)))
+    ;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
+    ;; call it from a temp-buffer, unless
+    ;; notmuch-show-attachment-debug is non-nil in which case we put
+    ;; it in " *notmuch-part*".
+    (unwind-protect
+       (if notmuch-show-attachment-debug
+           (with-current-buffer (generate-new-buffer " *notmuch-part*")
+             (funcall fn handle))
+         (with-temp-buffer
+           (funcall fn handle)))
+      (kill-buffer (mm-handle-buffer handle)))))
+
+(defun notmuch-show-part-button-default (&optional button)
+  (interactive)
+  (let ((button (or button (button-at (point)))))
+    ;; Try to toggle the part, if that fails then call the default
+    ;; action. The toggle fails if the part has no emacs renderable
+    ;; content.
+    (unless (notmuch-show-toggle-part-invisibility button)
+      (call-interactively notmuch-show-part-button-default-action))))
+
+(defun notmuch-show-save-part ()
+  "Save the MIME part containing point to a file."
+  (interactive)
+  (notmuch-show-apply-to-current-part-handle #'mm-save-part))
+
+(defun notmuch-show-view-part ()
+  "View the MIME part containing point in an external viewer."
+  (interactive)
+  ;; Set mm-inlined-types to nil to force an external viewer
+  (let ((mm-inlined-types nil))
+    (notmuch-show-apply-to-current-part-handle #'mm-display-part)))
+
+(defun notmuch-show-interactively-view-part ()
+  "View the MIME part containing point, prompting for a viewer."
+  (interactive)
+  (notmuch-show-apply-to-current-part-handle #'mm-interactively-view-part))
+
+(defun notmuch-show-pipe-part ()
+  "Pipe the MIME part containing point to an external command."
+  (interactive)
+  (notmuch-show-apply-to-current-part-handle #'mm-pipe-part))
+
+
+(defun notmuch-show--mm-display-part (handle)
+  "Use mm-display-part to display HANDLE in a new buffer.
+
+If the part is displayed in an external application then close
+the new buffer."
+  (let ((buf (get-buffer-create (generate-new-buffer-name
+                                (concat " *notmuch-internal-part*")))))
+    (switch-to-buffer buf)
+    (if (eq (mm-display-part handle) 'external)
+       (kill-buffer buf)
+      (goto-char (point-min))
+      (set-buffer-modified-p nil)
+      (view-buffer buf 'kill-buffer-if-not-modified))))
+
+(defun notmuch-show-choose-mime-of-part (mime-type)
+  "Choose the mime type to use for displaying part"
+  (interactive
+   (list (completing-read "Mime type to use (default text/plain): "
+                         (mailcap-mime-types) nil nil nil nil "text/plain")))
+  (notmuch-show-apply-to-current-part-handle #'notmuch-show--mm-display-part mime-type))
+
+(defun notmuch-show-imenu-prev-index-position-function ()
+  "Move point to previous message in notmuch-show buffer.
+This function is used as a value for
+`imenu-prev-index-position-function'."
+  (if (bobp)
+      nil
+    (notmuch-show-previous-message)
+    t))
+
+(defun notmuch-show-imenu-extract-index-name-function ()
+  "Return imenu name for line at point.
+This function is used as a value for
+`imenu-extract-index-name-function'.  Point should be at the
+beginning of the line."
+  (back-to-indentation)
+  (buffer-substring-no-properties (if notmuch-show-imenu-indent
+                                     (line-beginning-position)
+                                   (point))
+                                 (line-end-position)))
+
+(provide 'notmuch-show)
+
+;;; notmuch-show.el ends here
diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el
new file mode 100644 (file)
index 0000000..0500927
--- /dev/null
@@ -0,0 +1,552 @@
+;;; notmuch-tag.el --- tag messages within emacs
+;;
+;; Copyright © Damien Cassou
+;; Copyright © Carl Worth
+;;
+;; 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: Carl Worth <cworth@cworth.org>
+;;          Damien Cassou <damien.cassou@gmail.com>
+;;
+;;; Code:
+;;
+
+(require 'cl)
+(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")
+               :extra-offset -3
+               :value-type
+               (radio :format "%v"
+                      (const :tag "Hidden" nil)
+                      (set :tag "Modified"
+                           (string :tag "Display as")
+                           (list :tag "Face" :extra-offset -4
+                                 (const :format "" :inline t
+                                        (notmuch-apply-face tag))
+                                 (list :format "%v"
+                                       (const :format "" quote)
+                                       custom-face-edit))
+                           (list :format "%v" :extra-offset -4
+                                 (const :format "" :inline t
+                                        (notmuch-tag-format-image-data tag))
+                                 (choice :tag "Image"
+                                         (const :tag "Star"
+                                                (notmuch-tag-star-icon))
+                                         (const :tag "Empty star"
+                                                (notmuch-tag-star-empty-icon))
+                                         (const :tag "Tag"
+                                                (notmuch-tag-tag-icon))
+                                         (string :tag "Custom")))
+                           (sexp :tag "Custom")))))
+
+(defface notmuch-tag-unread
+  '((t :foreground "red"))
+  "Default face used for the unread tag.
+
+Used in the default value of `notmuch-tag-formats`."
+  :group 'notmuch-faces)
+
+(defface notmuch-tag-flagged
+  '((((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`."
+  :group 'notmuch-faces)
+
+(defcustom notmuch-tag-formats
+  '(("unread" (propertize tag 'face 'notmuch-tag-unread))
+    ("flagged" (propertize tag 'face 'notmuch-tag-flagged)
+     (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))))
+  "Custom formats for individual tags.
+
+This is an association list that maps from tag name regexps to
+lists of formatting expressions.  The first entry whose car
+regexp-matches a tag will be used to format that tag.  The regexp
+is implicitly anchored, so to match a literal tag name, just use
+that tag name (if it contains special regexp characters like
+\".\" or \"*\", these have to be escaped).  The cdr of the
+matching entry gives a list of Elisp expressions that modify the
+tag.  If the list is empty, the tag will simply be hidden.
+Otherwise, each expression will be evaluated in order: for the
+first expression, the variable `tag' will be bound to the tag
+name; for each later expression, the variable `tag' will be bound
+to the result of the previous expression.  In this way, each
+expression can build on the formatting performed by the previous
+expression.  The result of the last expression will displayed in
+place of the tag.
+
+For example, to replace a tag with another string, simply use
+that string as a formatting expression.  To change the foreground
+of a tag to red, use the expression
+  (propertize tag 'face '(:foreground \"red\"))
+
+See also `notmuch-tag-format-image', which can help replace tags
+with images."
+  :group 'notmuch-search
+  :group 'notmuch-show
+  :group 'notmuch-faces
+  :type 'notmuch-tag-format-type)
+
+(defface notmuch-tag-deleted
+  '((((class color) (supports :strike-through "red")) :strike-through "red")
+    (t :inverse-video t))
+  "Face used to display deleted tags.
+
+Used in the default value of `notmuch-tag-deleted-formats`."
+  :group 'notmuch-faces)
+
+(defcustom notmuch-tag-deleted-formats
+  '(("unread" (notmuch-apply-face bare-tag `notmuch-tag-deleted))
+    (".*" (notmuch-apply-face tag `notmuch-tag-deleted)))
+  "Custom formats for tags when deleted.
+
+For deleted tags the formats in `notmuch-tag-formats` are applied
+first and then these formats are applied on top; that is `tag'
+passed to the function is the tag with all these previous
+formattings applied. The formatted can access the original
+unformatted tag as `bare-tag'.
+
+By default this shows deleted tags with strike-through in red,
+unless strike-through is not available (e.g., emacs is running in
+a terminal) in which case it uses inverse video. To hide deleted
+tags completely set this to
+  '((\".*\" nil))
+
+See `notmuch-tag-formats' for full documentation."
+  :group 'notmuch-show
+  :group 'notmuch-faces
+  :type 'notmuch-tag-format-type)
+
+(defface notmuch-tag-added
+  '((t :underline "green"))
+  "Default face used for added tags.
+
+Used in the default value for `notmuch-tag-added-formats`."
+  :group 'notmuch-faces)
+
+(defcustom notmuch-tag-added-formats
+  '((".*" (notmuch-apply-face tag 'notmuch-tag-added)))
+  "Custom formats for tags when added.
+
+For added tags the formats in `notmuch-tag-formats` are applied
+first and then these formats are applied on top.
+
+To disable special formatting of added tags, set this variable to
+nil.
+
+See `notmuch-tag-formats' for full documentation."
+  :group 'notmuch-show
+  :group 'notmuch-faces
+  :type 'notmuch-tag-format-type)
+
+(defun notmuch-tag-format-image-data (tag data)
+  "Replace TAG with image DATA, if available.
+
+This function returns a propertized string that will display image
+DATA in place of TAG.This is designed for use in
+`notmuch-tag-formats'.
+
+DATA is the content of an SVG picture (e.g., as returned by
+`notmuch-tag-star-icon')."
+  (propertize tag 'display
+             `(image :type svg
+                     :data ,data
+                     :ascent center
+                     :mask heuristic)))
+
+(defun notmuch-tag-star-icon ()
+  "Return SVG data representing a star icon.
+This can be used with `notmuch-tag-format-image-data'."
+"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
+<svg version=\"1.1\" width=\"16\" height=\"16\">
+  <g transform=\"translate(-242.81601,-315.59635)\">
+    <path
+       d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\"
+       transform=\"matrix(0.2484147,-0.02623394,0.02623394,0.2484147,174.63605,255.37691)\"
+       style=\"fill:#ffff00;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\" />
+  </g>
+</svg>")
+
+(defun notmuch-tag-star-empty-icon ()
+  "Return SVG data representing an empty star icon.
+This can be used with `notmuch-tag-format-image-data'."
+  "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
+<svg version=\"1.1\" width=\"16\" height=\"16\">
+  <g transform=\"translate(-242.81601,-315.59635)\">
+    <path
+       d=\"m 290.25762,334.31206 -17.64143,-11.77975 -19.70508,7.85447 5.75171,-20.41814 -13.55925,-16.31348 21.19618,-0.83936 11.325,-17.93675 7.34825,19.89939 20.55849,5.22795 -16.65471,13.13786 z\"
+       transform=\"matrix(0.2484147,-0.02623394,0.02623394,0.2484147,174.63605,255.37691)\"
+       style=\"fill:#d6d6d1;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\" />
+  </g>
+</svg>")
+
+(defun notmuch-tag-tag-icon ()
+  "Return SVG data representing a tag icon.
+This can be used with `notmuch-tag-format-image-data'."
+  "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
+<svg version=\"1.1\" width=\"16\" height=\"16\">
+  <g transform=\"translate(0,-1036.3622)\">
+    <path
+       d=\"m 0.44642857,1040.9336 12.50000043,0 2.700893,3.6161 -2.700893,3.616 -12.50000043,0 z\"
+       style=\"fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1\" />
+  </g>
+</svg>")
+
+(defvar notmuch-tag--format-cache (make-hash-table :test 'equal)
+  "Cache of tag format lookup.  Internal to `notmuch-tag-format-tag'.")
+
+(defun notmuch-tag-clear-cache ()
+  "Clear the internal cache of tag formats."
+  (clrhash notmuch-tag--format-cache))
+
+(defun notmuch-tag--get-formats (tag format-alist)
+  "Find the first item whose car regexp-matches TAG."
+  (save-match-data
+    ;; Don't use assoc-default since there's no way to distinguish a
+    ;; missing key from a present key with a null cdr.
+    (assoc* tag format-alist
+           :test (lambda (tag key)
+                   (and (eq (string-match key tag) 0)
+                        (= (match-end 0) (length tag)))))))
+
+(defun notmuch-tag--do-format (tag formatted-tag formats)
+  "Apply a tag-formats entry to TAG."
+  (cond ((null formats)                ;; - Tag not in `formats',
+        formatted-tag)         ;;   the format is the tag itself.
+       ((null (cdr formats))   ;; - Tag was deliberately hidden,
+        nil)                   ;;   no format must be returned
+       (t
+        ;; Tag was found and has formats, we must apply all the
+        ;; formats.  TAG may be null so treat that as a special case.
+        (let ((bare-tag tag)
+              (tag (copy-sequence (or formatted-tag ""))))
+          (dolist (format (cdr formats))
+            (setq tag (eval format)))
+          (if (and (null formatted-tag) (equal tag ""))
+              nil
+            tag)))))
+
+(defun notmuch-tag-format-tag (tags orig-tags tag)
+  "Format TAG according to `notmuch-tag-formats'.
+
+TAGS and ORIG-TAGS are lists of the current tags and the original
+tags; tags which have been deleted (i.e., are in ORIG-TAGS but
+are not in TAGS) are shown using formats from
+`notmuch-tag-deleted-formats'; tags which have been added (i.e.,
+are in TAGS but are not in ORIG-TAGS) are shown using formats
+from `notmuch-tag-added-formats' and tags which have not been
+changed (the normal case) are shown using formats from
+`notmuch-tag-formats'"
+  (let* ((tag-state (cond ((not (member tag tags)) 'deleted)
+                         ((not (member tag orig-tags)) 'added)))
+        (formatted-tag (gethash (cons tag tag-state) notmuch-tag--format-cache 'missing)))
+    (when (eq formatted-tag 'missing)
+      (let ((base (notmuch-tag--get-formats tag notmuch-tag-formats))
+           (over (case tag-state
+                   (deleted (notmuch-tag--get-formats
+                             tag notmuch-tag-deleted-formats))
+                   (added (notmuch-tag--get-formats
+                           tag notmuch-tag-added-formats))
+                   (otherwise nil))))
+       (setq formatted-tag (notmuch-tag--do-format tag tag base))
+       (setq formatted-tag (notmuch-tag--do-format tag formatted-tag over))
+
+       (puthash (cons tag tag-state) formatted-tag notmuch-tag--format-cache)))
+    formatted-tag))
+
+(defun notmuch-tag-format-tags (tags orig-tags &optional face)
+  "Return a string representing formatted TAGS."
+  (let ((face (or face 'notmuch-tag-face))
+       (all-tags (sort (delete-dups (append tags orig-tags nil)) #'string<)))
+    (notmuch-apply-face
+     (mapconcat #'identity
+               ;; nil indicated that the tag was deliberately hidden
+               (delq nil (mapcar
+                          (apply-partially #'notmuch-tag-format-tag tags orig-tags)
+                          all-tags))
+               " ")
+     face
+     t)))
+
+(defcustom notmuch-before-tag-hook nil
+  "Hooks that are run before tags of a message are modified.
+
+'tag-changes' will contain the tags that are about to be added or removed as
+a list of strings of the form \"+TAG\" or \"-TAG\".
+'query' will be a string containing the search query that determines
+the messages that are about to be tagged"
+
+  :type 'hook
+  :options '(notmuch-hl-line-mode)
+  :group 'notmuch-hooks)
+
+(defcustom notmuch-after-tag-hook nil
+  "Hooks that are run after tags of a message are modified.
+
+'tag-changes' will contain the tags that were added or removed as
+a list of strings of the form \"+TAG\" or \"-TAG\".
+'query' will be a string containing the search query that determines
+the messages that were tagged"
+  :type 'hook
+  :options '(notmuch-hl-line-mode)
+  :group 'notmuch-hooks)
+
+(defvar notmuch-select-tag-history nil
+  "Variable to store minibuffer history for
+`notmuch-select-tag-with-completion' function.")
+
+(defvar notmuch-read-tag-changes-history nil
+  "Variable to store minibuffer history for
+`notmuch-read-tag-changes' function.")
+
+(defun notmuch-tag-completions (&rest search-terms)
+  "Return a list of tags for messages matching SEARCH-TERMS.
+
+Returns all tags if no search terms are given."
+  (if (null search-terms)
+      (setq search-terms (list "*")))
+  (split-string
+   (with-output-to-string
+     (with-current-buffer standard-output
+       (apply 'call-process notmuch-command nil t
+             nil "search" "--output=tags" "--exclude=false" search-terms)))
+   "\n+" t))
+
+(defun notmuch-select-tag-with-completion (prompt &rest search-terms)
+  (let ((tag-list (apply #'notmuch-tag-completions search-terms)))
+    (completing-read prompt tag-list nil nil nil 'notmuch-select-tag-history)))
+
+(defun notmuch-read-tag-changes (current-tags &optional prompt initial-input)
+  "Prompt for tag changes in the minibuffer.
+
+CURRENT-TAGS is a list of tags that are present on the message or
+messages to be changed.  These are offered as tag removal
+completions.  CURRENT-TAGS may contain duplicates.  PROMPT, if
+non-nil, is the query string to present in the minibuffer.  It
+defaults to \"Tags\".  INITIAL-INPUT, if non-nil, will be the
+initial input in the minibuffer."
+
+  (let* ((all-tag-list (notmuch-tag-completions))
+        (add-tag-list (mapcar (apply-partially 'concat "+") all-tag-list))
+        (remove-tag-list (mapcar (apply-partially 'concat "-") current-tags))
+        (tag-list (append add-tag-list remove-tag-list))
+        (prompt (concat (or prompt "Tags") " (+add -drop): "))
+        (crm-separator " ")
+        ;; By default, space is bound to "complete word" function.
+        ;; Re-bind it to insert a space instead.  Note that <tab>
+        ;; still does the completion.
+        (crm-local-completion-map
+         (let ((map (make-sparse-keymap)))
+           (set-keymap-parent map crm-local-completion-map)
+           (define-key map " " 'self-insert-command)
+           map)))
+    (delete "" (completing-read-multiple
+               prompt
+               ;; Append the separator to each completion so when the
+               ;; user completes a tag they can immediately begin
+               ;; entering another.  `completing-read-multiple'
+               ;; ultimately splits the input on crm-separator, so we
+               ;; don't need to strip this back off (we just need to
+               ;; delete "empty" entries caused by trailing spaces).
+               (mapcar (lambda (tag-op) (concat tag-op crm-separator)) tag-list)
+               nil nil initial-input
+               'notmuch-read-tag-changes-history))))
+
+(defun notmuch-update-tags (tags tag-changes)
+  "Return a copy of TAGS with additions and removals from TAG-CHANGES.
+
+TAG-CHANGES must be a list of tags names, each prefixed with
+either a \"+\" to indicate the tag should be added to TAGS if not
+present or a \"-\" to indicate that the tag should be removed
+from TAGS if present."
+  (let ((result-tags (copy-sequence tags)))
+    (dolist (tag-change tag-changes)
+      (let ((op (string-to-char tag-change))
+           (tag (unless (string= tag-change "") (substring tag-change 1))))
+       (case op
+         (?+ (unless (member tag result-tags)
+               (push tag result-tags)))
+         (?- (setq result-tags (delete tag result-tags)))
+         (otherwise
+          (error "Changed tag must be of the form `+this_tag' or `-that_tag'")))))
+    (sort result-tags 'string<)))
+
+(defconst notmuch-tag-argument-limit 1000
+  "Use batch tagging if the tagging query is longer than this.
+
+This limits the length of arguments passed to the notmuch CLI to
+avoid system argument length limits and performance problems.")
+
+(defun notmuch-tag (query tag-changes)
+  "Add/remove tags in TAG-CHANGES to messages matching QUERY.
+
+QUERY should be a string containing the search-terms.
+TAG-CHANGES is a list of strings of the form \"+tag\" or
+\"-tag\" to add or remove tags, respectively.
+
+Note: Other code should always use this function to alter tags of
+messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
+directly, so that hooks specified in notmuch-before-tag-hook and
+notmuch-after-tag-hook will be run."
+  ;; Perform some validation
+  (mapc (lambda (tag-change)
+         (unless (string-match-p "^[-+]\\S-+$" tag-change)
+           (error "Tag must be of the form `+this_tag' or `-that_tag'")))
+       tag-changes)
+  (unless query
+    (error "Nothing to tag!"))
+  (unless (null tag-changes)
+    (run-hooks 'notmuch-before-tag-hook)
+    (if (<= (length query) notmuch-tag-argument-limit)
+       (apply 'notmuch-call-notmuch-process "tag"
+              (append tag-changes (list "--" query)))
+      ;; Use batch tag mode to avoid argument length limitations
+      (let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ")
+                             " -- " query)))
+       (notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch")))
+    (run-hooks 'notmuch-after-tag-hook)))
+
+(defun notmuch-tag-change-list (tags &optional reverse)
+  "Convert TAGS into a list of tag changes.
+
+Add a \"+\" prefix to any tag in TAGS list that doesn't already
+begin with a \"+\" or a \"-\". If REVERSE is non-nil, replace all
+\"+\" prefixes with \"-\" and vice versa in the result."
+  (mapcar (lambda (str)
+           (let ((s (if (string-match "^[+-]" str) str (concat "+" str))))
+             (if reverse
+                 (concat (if (= (string-to-char s) ?-) "+" "-")
+                         (substring s 1))
+               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: ")))
+
+;;
+
+(provide 'notmuch-tag)
+
+;; Local Variables:
+;; byte-compile-warnings: (not cl-functions)
+;; End:
diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el
new file mode 100644 (file)
index 0000000..c00315e
--- /dev/null
@@ -0,0 +1,978 @@
+;;; notmuch-tree.el --- displaying notmuch forests.
+;;
+;; Copyright © Carl Worth
+;; Copyright © David Edmondson
+;; Copyright © Mark Walters
+;;
+;; 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: David Edmondson <dme@dme.org>
+;;          Mark Walters <markwalters1009@gmail.com>
+
+;;; Code:
+
+(require 'mail-parse)
+
+(require 'notmuch-lib)
+(require 'notmuch-query)
+(require 'notmuch-show)
+(require 'notmuch-tag)
+(require 'notmuch-parser)
+
+(eval-when-compile (require 'cl))
+(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line))
+(declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
+(declare-function notmuch-read-query "notmuch" (prompt))
+(declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
+(declare-function notmuch-search-find-subject "notmuch" ())
+
+;; the following variable is defined in notmuch.el
+(defvar notmuch-search-query-string)
+
+(defgroup notmuch-tree nil
+  "Showing message and thread structure."
+  :group 'notmuch)
+
+(defcustom notmuch-tree-show-out nil
+  "View selected messages in new window rather than split-pane."
+  :type 'boolean
+  :group 'notmuch-tree)
+
+(defcustom notmuch-tree-result-format
+  `(("date" . "%12s  ")
+    ("authors" . "%-20s")
+    ((("tree" . "%s")("subject" . "%s")) ." %-54s ")
+    ("tags" . "(%s)"))
+  "Result formatting for Tree view. Supported fields are: date,
+        authors, subject, tree, tags.  Tree means the thread tree
+        box graphics. The field may also be a list in which case
+        the formatting rules are applied recursively and then the
+        output of all the fields in the list is inserted
+        according to format-string.
+
+Note the author string should not contain
+        whitespace (put it in the neighbouring fields instead).
+        For example:
+        (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
+                                             \(\"subject\" . \"%s\"\)\)\)"
+  :type '(alist :key-type (string) :value-type (string))
+  :group 'notmuch-tree)
+
+;; Faces for messages that match the query.
+(defface notmuch-tree-match-face
+  '((t :inherit default))
+  "Default face used in tree mode face for matching messages"
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-date-face
+  nil
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-author-face
+  '((((class color)
+      (background dark))
+     (:foreground "OliveDrab1"))
+    (((class color)
+      (background light))
+     (:foreground "dark blue"))
+    (t
+     (:bold t)))
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-subject-face
+  nil
+  "Face used in tree mode for the subject in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-tree-face
+  nil
+  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-match-tag-face
+  '((((class color)
+      (background dark))
+     (:foreground "OliveDrab1"))
+    (((class color)
+      (background light))
+     (:foreground "navy blue" :bold t))
+    (t
+     (:bold t)))
+  "Face used in tree mode for tags in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+;; Faces for messages that do not match the query.
+(defface notmuch-tree-no-match-face
+  '((t (:foreground "gray")))
+  "Default face used in tree mode face for non-matching messages"
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-date-face
+  nil
+  "Face used in tree mode for non-matching dates."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-subject-face
+  nil
+  "Face used in tree mode for non-matching subjects."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-tree-face
+  nil
+  "Face used in tree mode for the thread tree block graphics in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-author-face
+  nil
+  "Face used in tree mode for the date in messages matching the query."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defface notmuch-tree-no-match-tag-face
+  nil
+  "Face used in tree mode face for non-matching tags."
+  :group 'notmuch-tree
+  :group 'notmuch-faces)
+
+(defvar notmuch-tree-previous-subject
+  "The subject of the most recent result shown during the async display")
+(make-variable-buffer-local 'notmuch-tree-previous-subject)
+
+(defvar notmuch-tree-basic-query nil
+  "A buffer local copy of argument query to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-basic-query)
+
+(defvar notmuch-tree-query-context nil
+  "A buffer local copy of argument query-context to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-query-context)
+
+(defvar notmuch-tree-target-msg nil
+  "A buffer local copy of argument target to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-target-msg)
+
+(defvar notmuch-tree-open-target nil
+  "A buffer local copy of argument open-target to the function notmuch-tree")
+(make-variable-buffer-local 'notmuch-tree-open-target)
+
+(defvar notmuch-tree-message-window nil
+  "The window of the message pane.
+
+It is set in both the tree buffer and the child show buffer. It
+is used to try and close the message pane when quitting tree view
+or the child show buffer.")
+(make-variable-buffer-local 'notmuch-tree-message-window)
+(put 'notmuch-tree-message-window 'permanent-local t)
+
+(defvar notmuch-tree-message-buffer nil
+  "The buffer name of the show buffer in the message pane.
+
+This is used to try and make sure we don't close the message pane
+if the user has loaded a different buffer in that window.")
+(make-variable-buffer-local 'notmuch-tree-message-buffer)
+(put 'notmuch-tree-message-buffer 'permanent-local t)
+
+(defun notmuch-tree-to-message-pane (func)
+  "Execute FUNC in message pane.
+
+This function returns a function (so can be used as a keybinding)
+which executes function FUNC in the message pane if it is
+open (if the message pane is closed it does nothing)."
+  `(lambda ()
+      ,(concat "(In message pane) " (documentation func t))
+     (interactive)
+     (when (window-live-p notmuch-tree-message-window)
+       (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
+
+This function does not give an error if there is no button."
+  (interactive)
+  (let ((button (or button (button-at (point)))))
+    (when button (button-activate button))))
+
+(defun notmuch-tree-close-message-pane-and (func)
+  "Close message pane and execute FUNC.
+
+This function returns a function (so can be used as a keybinding)
+which closes the message pane if open and then executes function
+FUNC."
+  `(lambda ()
+      ,(concat "(Close message pane and) " (documentation func t))
+     (interactive)
+     (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)))
+    (set-keymap-parent map notmuch-common-keymap)
+    ;; The following override the global keymap.
+    ;; Override because we want to close message pane first.
+    (define-key map [remap notmuch-help] (notmuch-tree-close-message-pane-and #'notmuch-help))
+    ;; Override because we first close message pane and then close tree buffer.
+    (define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit)
+    ;; Override because we close message pane after the search query is entered.
+    (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
+    ;; Override because we want to close message pane first.
+    (define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
+    ;; Override because we want to close message pane first.
+    (define-key map [remap notmuch-jump-search] (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
+
+    (define-key map "S" 'notmuch-search-from-tree-current-query)
+
+    ;; these use notmuch-show functions directly
+    (define-key map "|" 'notmuch-show-pipe-message)
+    (define-key map "w" 'notmuch-show-save-attachments)
+    (define-key map "v" 'notmuch-show-view-all-mime-parts)
+    (define-key map "c" 'notmuch-show-stash-map)
+    (define-key map "b" 'notmuch-show-resend-message)
+
+    ;; these apply to the message pane
+    (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 "$" (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))
+    (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
+    (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
+    (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+
+    ;; The main tree view bindings
+    (define-key map (kbd "RET") 'notmuch-tree-show-message)
+    (define-key map [mouse-1] 'notmuch-tree-show-message)
+    (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 "z" 'notmuch-tree-to-tree)
+    (define-key map "n" 'notmuch-tree-next-matching-message)
+    (define-key map "p" 'notmuch-tree-prev-matching-message)
+    (define-key map "N" 'notmuch-tree-next-message)
+    (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)
+
+(defun notmuch-tree-get-message-properties ()
+  "Return the properties of the current message as a plist.
+
+Some useful entries are:
+:headers - Property list containing the headers :Date, :Subject, :From, etc.
+:tags - Tags for this message"
+  (save-excursion
+    (beginning-of-line)
+    (get-text-property (point) :notmuch-message-properties)))
+
+(defun notmuch-tree-set-message-properties (props)
+  (save-excursion
+    (beginning-of-line)
+    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+
+(defun notmuch-tree-set-prop (prop val &optional props)
+  (let ((inhibit-read-only t)
+       (props (or props
+                  (notmuch-tree-get-message-properties))))
+    (plist-put props prop val)
+    (notmuch-tree-set-message-properties props)))
+
+(defun notmuch-tree-get-prop (prop &optional props)
+  (let ((props (or props
+                  (notmuch-tree-get-message-properties))))
+    (plist-get props prop)))
+
+(defun notmuch-tree-set-tags (tags)
+  "Set the tags of the current message."
+  (notmuch-tree-set-prop :tags tags))
+
+(defun notmuch-tree-get-tags ()
+  "Return the tags of the current message."
+  (notmuch-tree-get-prop :tags))
+
+(defun notmuch-tree-get-message-id (&optional bare)
+  "Return the message id of the current message."
+  (let ((id (notmuch-tree-get-prop :id)))
+    (if id
+       (if bare
+           id
+         (notmuch-id-to-query id))
+      nil)))
+
+(defun notmuch-tree-get-match ()
+  "Return whether the current message is a match."
+  (interactive)
+  (notmuch-tree-get-prop :match))
+
+(defun notmuch-tree-refresh-result ()
+  "Redisplay the current message line.
+
+This redisplays the current line based on the messages
+properties (as they are now). This is used when tags are
+updated."
+  (let ((init-point (point))
+       (end (line-end-position))
+       (msg (notmuch-tree-get-message-properties))
+       (inhibit-read-only t))
+    (beginning-of-line)
+    ;; This is a little tricky: we override
+    ;; notmuch-tree-previous-subject to get the decision between
+    ;; ... and a subject right and it stops notmuch-tree-insert-msg
+    ;; from overwriting the buffer local copy of
+    ;; notmuch-tree-previous-subject if this is called while the
+    ;; buffer is displaying.
+    (let ((notmuch-tree-previous-subject (notmuch-tree-get-prop :previous-subject)))
+      (delete-region (point) (1+ (line-end-position)))
+      (notmuch-tree-insert-msg msg))
+    (let ((new-end (line-end-position)))
+      (goto-char (if (= init-point end)
+                    new-end
+                  (min init-point (- new-end 1)))))))
+
+(defun notmuch-tree-tag-update-display (&optional tag-changes)
+  "Update display for TAG-CHANGES to current message.
+
+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))
+        (tree-msg-id (notmuch-tree-get-message-id)))
+    (unless (equal current-tags new-tags)
+      (notmuch-tree-set-tags new-tags)
+      (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"
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message")))
+  (notmuch-tag (notmuch-tree-get-message-id) tag-changes)
+  (notmuch-tree-tag-update-display tag-changes))
+
+(defun notmuch-tree-add-tag (tag-changes)
+  "Same as `notmuch-tree-tag' but sets initial input to '+'."
+  (interactive
+   (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message" "+")))
+  (notmuch-tree-tag tag-changes))
+
+(defun notmuch-tree-remove-tag (tag-changes)
+  "Same as `notmuch-tree-tag' but sets initial input to '-'."
+  (interactive
+   (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
+;; something in the message window).
+
+(defun notmuch-tree-to-search ()
+  "Run \"notmuch search\" with the given `query' and display results."
+  (interactive)
+  (let ((query (notmuch-read-query "Notmuch search: ")))
+    (notmuch-tree-close-message-window)
+    (notmuch-search query)))
+
+(defun notmuch-tree-to-tree ()
+  "Run a query and display results in Tree view"
+  (interactive)
+  (let ((query (notmuch-read-query "Notmuch tree view search: ")))
+    (notmuch-tree-close-message-window)
+    (notmuch-tree query)))
+
+(defun notmuch-search-from-tree-current-query ()
+  "Call notmuch search with the current query"
+  (interactive)
+  (notmuch-tree-close-message-window)
+  (notmuch-search (notmuch-tree-get-query)))
+
+(defun notmuch-tree-message-window-kill-hook ()
+  "Close the message pane when exiting the show buffer."
+  (let ((buffer (current-buffer)))
+    (when (and (window-live-p notmuch-tree-message-window)
+              (eq (window-buffer notmuch-tree-message-window) buffer))
+      ;; We do not want an error if this is the sole window in the
+      ;; frame and I do not know how to test for that in emacs pre
+      ;; 24. Hence we just ignore-errors.
+      (ignore-errors
+       (delete-window notmuch-tree-message-window)))))
+
+(defun notmuch-tree-command-hook ()
+  (when (eq major-mode 'notmuch-tree-mode)
+    ;; We just run the notmuch-show-command-hook on the message pane.
+    (when (buffer-live-p notmuch-tree-message-buffer)
+      (with-current-buffer notmuch-tree-message-buffer
+       (notmuch-show-command-hook)))))
+
+(defun notmuch-tree-show-message-in ()
+  "Show the current message (in split-pane)."
+  (interactive)
+  (let ((id (notmuch-tree-get-message-id))
+       (inhibit-read-only t)
+       buffer)
+    (when id
+      ;; We close and reopen the window to kill off un-needed buffers
+      ;; this might cause flickering but seems ok.
+      (notmuch-tree-close-message-window)
+      (setq notmuch-tree-message-window
+           (split-window-vertically (/ (window-height) 4)))
+      (with-selected-window notmuch-tree-message-window
+       ;; Since we are only displaying one message do not indent.
+       (let ((notmuch-show-indent-messages-width 0)
+             (notmuch-show-only-matching-messages t))
+         (setq buffer (notmuch-show id))))
+      ;; We need the `let' as notmuch-tree-message-window is buffer local.
+      (let ((window notmuch-tree-message-window))
+       (with-current-buffer buffer
+         (setq notmuch-tree-message-window window)
+         (add-hook 'kill-buffer-hook 'notmuch-tree-message-window-kill-hook)))
+      (when notmuch-show-mark-read-tags
+       (notmuch-tree-tag-update-display notmuch-show-mark-read-tags))
+      (setq notmuch-tree-message-buffer buffer))))
+
+(defun notmuch-tree-show-message-out ()
+  "Show the current message (in whole window)."
+  (interactive)
+  (let ((id (notmuch-tree-get-message-id))
+       (inhibit-read-only t)
+       buffer)
+    (when id
+      ;; We close the window to kill off un-needed buffers.
+      (notmuch-tree-close-message-window)
+      (notmuch-show id))))
+
+(defun notmuch-tree-show-message (arg)
+  "Show the current message.
+
+Shows in split pane or whole window according to value of
+`notmuch-tree-show-out'. A prefix argument reverses the choice."
+  (interactive "P")
+  (if (or (and notmuch-tree-show-out  (not arg))
+         (and (not notmuch-tree-show-out) arg))
+      (notmuch-tree-show-message-out)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-scroll-message-window ()
+  "Scroll the message window (if it exists)"
+  (interactive)
+  (when (window-live-p notmuch-tree-message-window)
+    (with-selected-window notmuch-tree-message-window
+      (if (pos-visible-in-window-p (point-max))
+         t
+       (scroll-up)))))
+
+(defun notmuch-tree-scroll-message-window-back ()
+  "Scroll the message window back(if it exists)"
+  (interactive)
+  (when (window-live-p notmuch-tree-message-window)
+    (with-selected-window notmuch-tree-message-window
+      (if (pos-visible-in-window-p (point-min))
+         t
+       (scroll-down)))))
+
+(defun notmuch-tree-scroll-or-next ()
+  "Scroll the message window. If it at end go to next message."
+  (interactive)
+  (when (notmuch-tree-scroll-message-window)
+    (notmuch-tree-next-matching-message)))
+
+(defun notmuch-tree-quit ()
+  "Close the split view or exit tree."
+  (interactive)
+  (unless (notmuch-tree-close-message-window)
+    (kill-buffer (current-buffer))))
+
+(defun notmuch-tree-close-message-window ()
+  "Close the message-window. Return t if close succeeds."
+  (interactive)
+  (when (and (window-live-p notmuch-tree-message-window)
+            (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer))
+    (delete-window notmuch-tree-message-window)
+    (unless (get-buffer-window-list notmuch-tree-message-buffer)
+      (kill-buffer notmuch-tree-message-buffer))
+    t))
+
+(defun notmuch-tree-archive-message (&optional unarchive)
+  "Archive the current message.
+
+Archive the current message by applying the tag changes in
+`notmuch-archive-tags' to it. If a prefix argument is given, the
+message will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (notmuch-tree-tag (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+(defun notmuch-tree-archive-message-then-next (&optional unarchive)
+  "Archive the current message and move to next matching message."
+  (interactive "P")
+  (notmuch-tree-archive-message unarchive)
+  (notmuch-tree-next-matching-message))
+
+(defun notmuch-tree-next-message ()
+  "Move to next message."
+  (interactive)
+  (forward-line)
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-prev-message ()
+  "Move to previous message."
+  (interactive)
+  (forward-line -1)
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-prev-matching-message ()
+  "Move to previous matching message."
+  (interactive)
+  (forward-line -1)
+  (while (and (not (bobp)) (not (notmuch-tree-get-match)))
+    (forward-line -1))
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-next-matching-message ()
+  "Move to next matching message."
+  (interactive)
+  (forward-line)
+  (while (and (not (eobp)) (not (notmuch-tree-get-match)))
+    (forward-line))
+  (when (window-live-p notmuch-tree-message-window)
+    (notmuch-tree-show-message-in)))
+
+(defun notmuch-tree-refresh-view ()
+  "Refresh view."
+  (interactive)
+  (when (get-buffer-process (current-buffer))
+    (error "notmuch tree process already running for current buffer"))
+  (let ((inhibit-read-only t)
+       (basic-query notmuch-tree-basic-query)
+       (query-context notmuch-tree-query-context)
+       (target (notmuch-tree-get-message-id)))
+    (erase-buffer)
+    (notmuch-tree-worker basic-query
+                        query-context
+                        target)))
+
+(defun notmuch-tree-thread-top ()
+  (when (notmuch-tree-get-message-properties)
+    (while (not (or (notmuch-tree-get-prop :first) (eobp)))
+      (forward-line -1))))
+
+(defun notmuch-tree-prev-thread ()
+  (interactive)
+  (forward-line -1)
+  (notmuch-tree-thread-top))
+
+(defun notmuch-tree-next-thread ()
+  (interactive)
+  (forward-line 1)
+  (while (not (or (notmuch-tree-get-prop :first) (eobp)))
+    (forward-line 1)))
+
+(defun notmuch-tree-thread-mapcar (function)
+  "Iterate through all messages in the current thread
+ and call FUNCTION for side effects."
+  (save-excursion
+    (notmuch-tree-thread-top)
+    (loop collect (funcall function)
+         do (forward-line)
+         while (and (notmuch-tree-get-message-properties)
+                    (not (notmuch-tree-get-prop :first))))))
+
+(defun notmuch-tree-get-messages-ids-thread-search ()
+  "Return a search string for all message ids of messages in the current thread."
+  (mapconcat 'identity
+            (notmuch-tree-thread-mapcar 'notmuch-tree-get-message-id)
+            " or "))
+
+(defun notmuch-tree-tag-thread (tag-changes)
+  "Tag all messages in the current thread"
+  (interactive
+   (let ((tags (apply #'append (notmuch-tree-thread-mapcar
+                               (lambda () (notmuch-tree-get-tags))))))
+     (list (notmuch-read-tag-changes tags "Tag thread"))))
+  (when (notmuch-tree-get-message-properties)
+    (notmuch-tag (notmuch-tree-get-messages-ids-thread-search) tag-changes)
+    (notmuch-tree-thread-mapcar
+     (lambda () (notmuch-tree-tag-update-display tag-changes)))))
+
+(defun notmuch-tree-archive-thread (&optional unarchive)
+  "Archive each message in thread.
+
+Archive each message currently shown by applying the tag changes
+in `notmuch-archive-tags' to each. If a prefix argument is given,
+the messages will be \"unarchived\", i.e. the tag changes in
+`notmuch-archive-tags' will be reversed.
+
+Note: This command is safe from any race condition of new messages
+being delivered to the same thread. It does not archive the
+entire thread, but only the messages shown in the current
+buffer."
+  (interactive "P")
+  (when notmuch-archive-tags
+    (notmuch-tree-tag-thread
+     (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+
+;; Functions below here display the tree buffer itself.
+
+(defun notmuch-tree-clean-address (address)
+  "Try to clean a single email ADDRESS for display. Return
+AUTHOR_NAME if present, otherwise return AUTHOR_EMAIL. Return
+unchanged ADDRESS if parsing fails."
+  (let* ((clean-address (notmuch-clean-address address))
+        (p-address (car clean-address))
+        (p-name (cdr clean-address)))
+
+    ;; If we have a name return that otherwise return the address.
+    (or p-name p-address)))
+
+(defun notmuch-tree-format-field (field format-string msg)
+  "Format a FIELD of MSG according to FORMAT-STRING and return string"
+  (let* ((headers (plist-get msg :headers))
+        (match (plist-get msg :match)))
+    (cond
+     ((listp field)
+      (format format-string (notmuch-tree-format-field-list field msg)))
+
+     ((string-equal field "date")
+      (let ((face (if match
+                     'notmuch-tree-match-date-face
+                   'notmuch-tree-no-match-date-face)))
+       (propertize (format format-string (plist-get msg :date_relative)) 'face face)))
+
+     ((string-equal field "tree")
+      (let ((tree-status (plist-get msg :tree-status))
+           (face (if match
+                     'notmuch-tree-match-tree-face
+                   'notmuch-tree-no-match-tree-face)))
+
+       (propertize (format format-string
+                           (mapconcat #'identity (reverse tree-status) ""))
+                   'face face)))
+
+     ((string-equal field "subject")
+      (let ((bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))
+           (previous-subject notmuch-tree-previous-subject)
+           (face (if match
+                     'notmuch-tree-match-subject-face
+                   'notmuch-tree-no-match-subject-face)))
+
+       (setq notmuch-tree-previous-subject bare-subject)
+       (propertize (format format-string
+                           (if (string= previous-subject bare-subject)
+                               " ..."
+                             bare-subject))
+                   'face face)))
+
+     ((string-equal field "authors")
+      (let ((author (notmuch-tree-clean-address (plist-get headers :From)))
+           (len (length (format format-string "")))
+           (face (if match
+                     'notmuch-tree-match-author-face
+                   'notmuch-tree-no-match-author-face)))
+       (when (> (length author) len)
+         (setq author (substring author 0 len)))
+       (propertize (format format-string author) 'face face)))
+
+     ((string-equal field "tags")
+      (let ((tags (plist-get msg :tags))
+           (orig-tags (plist-get msg :orig-tags))
+           (face (if match
+                     'notmuch-tree-match-tag-face
+                   'notmuch-tree-no-match-tag-face)))
+       (format format-string (notmuch-tag-format-tags tags orig-tags face)))))))
+
+(defun notmuch-tree-format-field-list (field-list msg)
+  "Format fields of MSG according to FIELD-LIST and return string"
+  (let ((face (if (plist-get msg :match)
+                 'notmuch-tree-match-face
+               'notmuch-tree-no-match-face))
+       (result-string))
+    (dolist (spec field-list result-string)
+      (let ((field-string (notmuch-tree-format-field (car spec) (cdr spec) msg)))
+       (setq result-string (concat result-string field-string))))
+    (notmuch-apply-face result-string face t)))
+
+(defun notmuch-tree-insert-msg (msg)
+  "Insert the message MSG according to notmuch-tree-result-format"
+  ;; We need to save the previous subject as it will get overwritten
+  ;; by the insert-field calls.
+  (let ((previous-subject notmuch-tree-previous-subject))
+    (insert (notmuch-tree-format-field-list notmuch-tree-result-format msg))
+    (notmuch-tree-set-message-properties msg)
+    (notmuch-tree-set-prop :previous-subject previous-subject)
+    (insert "\n")))
+
+(defun notmuch-tree-goto-and-insert-msg (msg)
+  "Insert msg at the end of the buffer. Move point to msg if it is the target"
+  (save-excursion
+    (goto-char (point-max))
+    (notmuch-tree-insert-msg msg))
+  (let ((msg-id (notmuch-id-to-query (plist-get msg :id)))
+       (target notmuch-tree-target-msg))
+    (when (or (and (not target) (plist-get msg :match))
+             (string= msg-id target))
+      (setq notmuch-tree-target-msg "found")
+      (goto-char (point-max))
+      (forward-line -1)
+      (when notmuch-tree-open-target
+       (notmuch-tree-show-message-in)))))
+
+(defun notmuch-tree-insert-tree (tree depth tree-status first last)
+  "Insert the message tree TREE at depth DEPTH in the current thread.
+
+A message tree is another name for a single sub-thread: i.e., a
+message together with all its descendents."
+  (let ((msg (car tree))
+       (replies (cadr tree)))
+
+      (cond
+       ((and (< 0 depth) (not last))
+       (push "├" tree-status))
+       ((and (< 0 depth) last)
+       (push "╰" tree-status))
+       ((and (eq 0 depth) first last)
+;;       (push "─" tree-status)) choice between this and next line is matter of taste.
+       (push " " tree-status))
+       ((and (eq 0 depth) first (not last))
+         (push "┬" tree-status))
+       ((and (eq 0 depth) (not first) last)
+       (push "╰" tree-status))
+       ((and (eq 0 depth) (not first) (not last))
+       (push "├" tree-status)))
+
+      (push (concat (if replies "┬" "─") "►") tree-status)
+      (setq msg (plist-put msg :first (and first (eq 0 depth))))
+      (setq msg (plist-put msg :tree-status tree-status))
+      (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
+      (notmuch-tree-goto-and-insert-msg msg)
+      (pop tree-status)
+      (pop tree-status)
+
+      (if last
+         (push " " tree-status)
+       (push "│" tree-status))
+
+    (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
+
+(defun notmuch-tree-insert-thread (thread depth tree-status)
+  "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
+  (let ((n (length thread)))
+    (loop for tree in thread
+         for count from 1 to n
+
+         do (notmuch-tree-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
+
+(defun notmuch-tree-insert-forest-thread (forest-thread)
+  "Insert a single complete thread."
+  (let (tree-status)
+    ;; Reset at the start of each main thread.
+    (setq notmuch-tree-previous-subject nil)
+    (notmuch-tree-insert-thread forest-thread 0 tree-status)))
+
+(defun notmuch-tree-insert-forest (forest)
+  "Insert a forest of threads.
+
+This function inserts a collection of several complete threads as
+passed to it by notmuch-tree-process-filter."
+  (mapc 'notmuch-tree-insert-forest-thread forest))
+
+(define-derived-mode notmuch-tree-mode fundamental-mode "notmuch-tree"
+  "Major mode displaying messages (as opposed to threads) of a notmuch search.
+
+This buffer contains the results of a \"notmuch tree\" of your
+email archives. Each line in the buffer represents a single
+message giving the relative date, the author, subject, and any
+tags.
+
+Pressing \\[notmuch-tree-show-message] on any line displays that message.
+
+Complete list of currently available key bindings:
+
+\\{notmuch-tree-mode-map}"
+
+  (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
+  (hl-line-mode 1)
+  (setq buffer-read-only t
+       truncate-lines t))
+
+(defun notmuch-tree-process-sentinel (proc msg)
+  "Add a message to let user know when \"notmuch tree\" exits"
+  (let ((buffer (process-buffer proc))
+       (status (process-status proc))
+       (exit-status (process-exit-status proc))
+       (never-found-target-thread nil))
+    (when (memq status '(exit signal))
+        (kill-buffer (process-get proc 'parse-buf))
+       (if (buffer-live-p buffer)
+           (with-current-buffer buffer
+             (save-excursion
+               (let ((inhibit-read-only t)
+                     (atbob (bobp)))
+                 (goto-char (point-max))
+                 (if (eq status 'signal)
+                     (insert "Incomplete search results (tree view process was killed).\n"))
+                 (when (eq status 'exit)
+                   (insert "End of search results.")
+                   (unless (= exit-status 0)
+                     (insert (format " (process returned %d)" exit-status)))
+                   (insert "\n")))))))))
+
+(defun notmuch-tree-process-filter (proc string)
+  "Process and filter the output of \"notmuch show\" for tree view"
+  (let ((results-buf (process-buffer proc))
+        (parse-buf (process-get proc 'parse-buf))
+        (inhibit-read-only t)
+        done)
+    (if (not (buffer-live-p results-buf))
+        (delete-process proc)
+      (with-current-buffer parse-buf
+        ;; Insert new data
+        (save-excursion
+          (goto-char (point-max))
+          (insert string))
+       (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
+                                        results-buf)))))
+
+(defun notmuch-tree-worker (basic-query &optional query-context target open-target)
+  "Insert the tree view of the search in the current buffer.
+
+This is is a helper function for notmuch-tree. The arguments are
+the same as for the function notmuch-tree."
+  (interactive)
+  (notmuch-tree-mode)
+  (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
+  (setq notmuch-tree-basic-query basic-query)
+  (setq notmuch-tree-query-context (if (or (string= query-context "")
+                                          (string= query-context "*"))
+                                      nil query-context))
+  (setq notmuch-tree-target-msg target)
+  (setq notmuch-tree-open-target open-target)
+  ;; Set the default value for `notmuch-show-process-crypto' in this
+  ;; buffer. Although we don't use this some of the functions we call
+  ;; (such as reply) do. It is a buffer local variable so setting it
+  ;; will not affect genuine show buffers.
+  (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
+
+  (erase-buffer)
+  (goto-char (point-min))
+  (let* ((search-args (concat basic-query
+                      (if query-context (concat " and (" query-context ")"))
+                      ))
+        (message-arg "--entire-thread"))
+    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")
+       (setq search-args basic-query))
+    (notmuch-tag-clear-cache)
+    (let ((proc (notmuch-start-notmuch
+                "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
+                "show" "--body=false" "--format=sexp" "--format-version=4"
+                message-arg search-args))
+         ;; Use a scratch buffer to accumulate partial output.
+         ;; This buffer will be killed by the sentinel, which
+         ;; should be called no matter how the process dies.
+         (parse-buf (generate-new-buffer " *notmuch tree parse*")))
+      (process-put proc 'parse-buf parse-buf)
+      (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.
+
+The arguments are:
+  QUERY: the main query. This can be any query but in many cases will be
+      a single thread. If nil this is read interactively from the minibuffer.
+  QUERY-CONTEXT: is an additional term for the query. The query used
+      is QUERY and QUERY-CONTEXT unless that does not match any messages
+      in which case we fall back to just QUERY.
+  TARGET: A message ID (with the id: prefix) that will be made
+      current if it appears in the tree view results.
+  BUFFER-NAME: the name of the buffer to display the tree view. If
+      it is nil \"*notmuch-tree\" followed by QUERY is used.
+  OPEN-TARGET: If TRUE open the target message in the message pane."
+  (interactive)
+  (if (null query)
+      (setq query (notmuch-read-query "Notmuch tree view search: ")))
+  (let ((buffer (get-buffer-create (generate-new-buffer-name
+                                   (or buffer-name
+                                       (concat "*notmuch-tree-" query "*")))))
+       (inhibit-read-only t))
+
+    (switch-to-buffer buffer))
+  ;; Don't track undo information for this buffer
+  (set 'buffer-undo-list t)
+
+  (notmuch-tree-worker query query-context target open-target)
+
+  (setq truncate-lines t))
+
+
+;;
+
+(provide 'notmuch-tree)
+
+;;; notmuch-tree.el ends here
diff --git a/emacs/notmuch-version.el.tmpl b/emacs/notmuch-version.el.tmpl
new file mode 100644 (file)
index 0000000..abf52f1
--- /dev/null
@@ -0,0 +1,28 @@
+;;; notmuch-version.el --- Version of notmuch
+;; -*- emacs-lisp -*-
+;;
+;; %AG%
+;;
+;; 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/>.
+
+;;; Code:
+
+(defconst notmuch-emacs-version %VERSION%
+  "Version of Notmuch Emacs MUA.")
+
+(provide 'notmuch-version)
+
+;;; notmuch-version.el ends here
diff --git a/emacs/notmuch-wash.el b/emacs/notmuch-wash.el
new file mode 100644 (file)
index 0000000..5f8b926
--- /dev/null
@@ -0,0 +1,431 @@
+;;; notmuch-wash.el --- cleaning up message bodies
+;;
+;; Copyright © Carl Worth
+;; Copyright © David Edmondson
+;;
+;; 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: Carl Worth <cworth@cworth.org>
+;;          David Edmondson <dme@dme.org>
+
+;;; Code:
+
+(require 'coolj)
+
+(declare-function notmuch-show-insert-bodypart "notmuch-show" (msg part depth &optional hide))
+(defvar notmuch-show-indent-messages-width)
+
+;;
+
+(defgroup notmuch-wash nil
+  "Cleaning up messages for display."
+  :group 'notmuch)
+
+(defcustom notmuch-wash-signature-regexp "^\\(-- ?\\|_+\\)$"
+  "Pattern to match a line that separates content from signature."
+  :type 'regexp
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-citation-regexp "\\(^[[:space:]]*>.*\n\\)+"
+  "Pattern to match citation lines."
+  :type 'regexp
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-original-regexp "^\\(--+\s?[oO]riginal [mM]essage\s?--+\\)$"
+  "Pattern to match a line that separates original message from
+reply in top-posted message."
+  :type 'regexp
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-signature-hidden-format
+  "[ %d-line signature. Click/Enter to show. ]"
+  "String used to construct button text for hidden signatures.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-signature-visible-format
+  "[ %d-line signature. Click/Enter to hide. ]"
+  "String used to construct button text for visible signatures.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-citation-hidden-format
+  "[ %d more citation lines. Click/Enter to show. ]"
+  "String used to construct button text for hidden citations.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-citation-visible-format
+  "[ %d more citation lines. Click/Enter to hide. ]"
+  "String used to construct button text for visible citations.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-original-hidden-format
+  "[ %d-line hidden original message. Click/Enter to show. ]"
+  "String used to construct button text for hidden citations.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-button-original-visible-format
+  "[ %d-line original message. Click/Enter to hide. ]"
+  "String used to construct button text for visible citations.
+Can use up to one integer format parameter, i.e. %d."
+  :type 'string
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-signature-lines-max 12
+  "Maximum length of signature that will be hidden by default."
+  :type 'integer
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-citation-lines-prefix 3
+  "Always show at least this many lines from the start of a citation.
+
+If there is one more line than the sum of
+`notmuch-wash-citation-lines-prefix' and
+`notmuch-wash-citation-lines-suffix', show that, otherwise
+collapse the remaining lines into a button."
+  :type 'integer
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-citation-lines-suffix 3
+  "Always show at least this many lines from the end of a citation.
+
+If there is one more line than the sum of
+`notmuch-wash-citation-lines-prefix' and
+`notmuch-wash-citation-lines-suffix', show that, otherwise
+collapse the remaining lines into a button."
+  :type 'integer
+  :group 'notmuch-wash)
+
+(defcustom notmuch-wash-wrap-lines-length nil
+  "Wrap line after at most this many characters.
+
+If this is nil, lines in messages will be wrapped to fit in the
+current window. If this is a number, lines will be wrapped after
+this many characters (ignoring indentation due to thread depth)
+or at the window width (whichever one is lower)."
+  :type '(choice (const :tag "window width" nil)
+                (integer :tag "number of characters"))
+  :group 'notmuch-wash)
+
+(defface notmuch-wash-toggle-button
+  '((t (:inherit font-lock-comment-face)))
+  "Face used for buttons toggling the visibility of washed away
+message parts."
+  :group 'notmuch-wash
+  :group 'notmuch-faces)
+
+(defface notmuch-wash-cited-text
+  '((t (:inherit message-cited-text)))
+  "Face used for cited text."
+  :group 'notmuch-wash
+  :group 'notmuch-faces)
+
+(defun notmuch-wash-toggle-invisible-action (cite-button)
+  ;; Toggle overlay visibility
+  (let ((overlay (button-get cite-button 'overlay)))
+    (overlay-put overlay 'invisible (not (overlay-get overlay 'invisible))))
+  ;; Update button text
+  (let* ((new-start (button-start cite-button))
+        (overlay (button-get cite-button 'overlay))
+        (button-label (notmuch-wash-button-label overlay))
+        (old-point (point))
+        (properties (text-properties-at (point)))
+        (inhibit-read-only t))
+    (goto-char new-start)
+    (insert button-label)
+    (set-text-properties new-start (point) properties)
+    (let ((old-end (button-end cite-button)))
+      (move-overlay cite-button new-start (point))
+      (delete-region (point) old-end))
+    (goto-char (min old-point (1- (button-end cite-button))))))
+
+(define-button-type 'notmuch-wash-button-invisibility-toggle-type
+  'action 'notmuch-wash-toggle-invisible-action
+  'follow-link t
+  'face 'notmuch-wash-toggle-button
+  :supertype 'notmuch-button-type)
+
+(define-button-type 'notmuch-wash-button-citation-toggle-type
+  'help-echo "mouse-1, RET: Show citation"
+  :supertype 'notmuch-wash-button-invisibility-toggle-type)
+
+(define-button-type 'notmuch-wash-button-signature-toggle-type
+  'help-echo "mouse-1, RET: Show signature"
+  :supertype 'notmuch-wash-button-invisibility-toggle-type)
+
+(define-button-type 'notmuch-wash-button-original-toggle-type
+  'help-echo "mouse-1, RET: Show original message"
+  :supertype 'notmuch-wash-button-invisibility-toggle-type)
+
+(defun notmuch-wash-region-isearch-show (overlay)
+  (notmuch-wash-toggle-invisible-action
+   (overlay-get overlay 'notmuch-wash-button)))
+
+(defun notmuch-wash-button-label (overlay)
+  (let* ((type (overlay-get overlay 'type))
+        (invis-spec (overlay-get overlay 'invisible))
+        (state (if (invisible-p invis-spec) "hidden" "visible"))
+        (label-format (symbol-value (intern-soft (concat "notmuch-wash-button-"
+                                                         type "-" state "-format"))))
+        (lines-count (count-lines (overlay-start overlay) (overlay-end overlay))))
+    (format label-format lines-count)))
+
+(defun notmuch-wash-region-to-button (msg beg end type &optional prefix)
+  "Auxiliary function to do the actual making of overlays and buttons
+
+BEG and END are buffer locations. TYPE should a string, either
+\"citation\" or \"signature\". Optional PREFIX is some arbitrary
+text to insert before the button, probably for indentation.  Note
+that PREFIX should not include a newline."
+
+  ;; This uses some slightly tricky conversions between strings and
+  ;; symbols because of the way the button code works. Note that
+  ;; replacing intern-soft with make-symbol will cause this to fail,
+  ;; since the newly created symbol has no plist.
+
+  (let ((overlay (make-overlay beg end))
+       (button-type (intern-soft (concat "notmuch-wash-button-"
+                                         type "-toggle-type"))))
+    (overlay-put overlay 'invisible t)
+    (overlay-put overlay 'isearch-open-invisible #'notmuch-wash-region-isearch-show)
+    (overlay-put overlay 'type type)
+    (goto-char (1+ end))
+    (save-excursion
+      (goto-char beg)
+      (if prefix
+         (insert-before-markers prefix))
+      (let ((button-beg (point)))
+       (insert-before-markers (notmuch-wash-button-label overlay) "\n")
+       (let ((button (make-button button-beg (1- (point))
+                                  'overlay overlay
+                                  :type button-type)))
+         (overlay-put overlay 'notmuch-wash-button button))))))
+
+(defun notmuch-wash-excerpt-citations (msg depth)
+  "Excerpt citations and up to one signature."
+  (goto-char (point-min))
+  (beginning-of-line)
+  (if (and (< (point) (point-max))
+          (re-search-forward notmuch-wash-original-regexp nil t))
+      (let* ((msg-start (match-beginning 0))
+            (msg-end (point-max))
+            (msg-lines (count-lines msg-start msg-end)))
+       (notmuch-wash-region-to-button
+        msg msg-start msg-end "original")))
+  (while (and (< (point) (point-max))
+             (re-search-forward notmuch-wash-citation-regexp nil t))
+    (let* ((cite-start (match-beginning 0))
+          (cite-end (match-end 0))
+          (cite-lines (count-lines cite-start cite-end)))
+      (overlay-put (make-overlay cite-start cite-end) 'face 'notmuch-wash-cited-text)
+      (when (> cite-lines (+ notmuch-wash-citation-lines-prefix
+                            notmuch-wash-citation-lines-suffix
+                            1))
+       (goto-char cite-start)
+       (forward-line notmuch-wash-citation-lines-prefix)
+       (let ((hidden-start (point-marker)))
+         (goto-char cite-end)
+         (forward-line (- notmuch-wash-citation-lines-suffix))
+         (notmuch-wash-region-to-button
+          msg hidden-start (point-marker)
+          "citation")))))
+  (if (and (not (eobp))
+          (re-search-forward notmuch-wash-signature-regexp nil t))
+      (let* ((sig-start (match-beginning 0))
+            (sig-end (match-end 0))
+            (sig-lines (count-lines sig-start (point-max))))
+       (if (<= sig-lines notmuch-wash-signature-lines-max)
+           (let ((sig-start-marker (make-marker))
+                 (sig-end-marker (make-marker)))
+             (set-marker sig-start-marker sig-start)
+             (set-marker sig-end-marker (point-max))
+             (overlay-put (make-overlay sig-start-marker sig-end-marker) 'face 'message-cited-text)
+             (notmuch-wash-region-to-button
+              msg sig-start-marker sig-end-marker
+              "signature"))))))
+
+;;
+
+(defun notmuch-wash-elide-blank-lines (msg depth)
+  "Elide leading, trailing and successive blank lines."
+
+  ;; Algorithm derived from `article-strip-multiple-blank-lines' in
+  ;; `gnus-art.el'.
+
+  ;; Make all blank lines empty.
+  (goto-char (point-min))
+  (while (re-search-forward "^[[:space:]\t]+$" nil t)
+    (replace-match "" nil t))
+
+  ;; Replace multiple empty lines with a single empty line.
+  (goto-char (point-min))
+  (while (re-search-forward "^\n\\(\n+\\)" nil t)
+    (delete-region (match-beginning 1) (match-end 1)))
+
+  ;; Remove a leading blank line.
+  (goto-char (point-min))
+  (if (looking-at "\n")
+      (delete-region (match-beginning 0) (match-end 0)))
+
+  ;; Remove a trailing blank line.
+  (goto-char (point-max))
+  (if (looking-at "\n")
+      (delete-region (match-beginning 0) (match-end 0))))
+
+;;
+
+(defun notmuch-wash-tidy-citations (msg depth)
+  "Improve the display of cited regions of a message.
+
+Perform several transformations on the message body:
+
+- Remove lines of repeated citation leaders with no other
+  content,
+- Remove citation leaders standing alone before a block of cited
+  text,
+- Remove citation trailers standing alone after a block of cited
+  text."
+
+  ;; Remove lines of repeated citation leaders with no other content.
+  (goto-char (point-min))
+  (while (re-search-forward "\\(^>[> ]*\n\\)\\{2,\\}" nil t)
+    (replace-match "\\1"))
+
+  ;; Remove citation leaders standing alone before a block of cited
+  ;; text.
+  (goto-char (point-min))
+  (while (re-search-forward "\\(\n\\|^[^>].*\\)\n\\(^>[> ]*\n\\)" nil t)
+    (replace-match "\\1\n"))
+
+  ;; Remove citation trailers standing alone after a block of cited
+  ;; text.
+  (goto-char (point-min))
+  (while (re-search-forward "\\(^>[> ]*\n\\)\\(^$\\|^[^>].*\\)" nil t)
+    (replace-match "\\2")))
+
+;;
+
+(defun notmuch-wash-wrap-long-lines (msg depth)
+  "Wrap long lines in the message.
+
+If `notmuch-wash-wrap-lines-length' is a number, this will wrap
+the message lines to the minimum of the width of the window or
+its value. Otherwise, this function will wrap long lines in the
+message at the window width. When doing so, citation leaders in
+the wrapped text are maintained."
+
+  (let* ((coolj-wrap-follows-window-size nil)
+        (indent (* depth notmuch-show-indent-messages-width))
+        (limit (if (numberp notmuch-wash-wrap-lines-length)
+                   (min (+ notmuch-wash-wrap-lines-length indent)
+                        (window-width))
+                 (window-width)))
+        (fill-column (- limit
+                        indent
+                        ;; 2 to avoid poor interaction with
+                        ;; `word-wrap'.
+                        2)))
+    (coolj-wrap-region (point-min) (point-max))))
+
+;;
+
+(require 'diff-mode)
+
+(defvar diff-file-header-re) ; From `diff-mode.el'.
+
+(defun notmuch-wash-subject-to-filename (subject &optional maxlen)
+  "Convert a mail SUBJECT into a filename.
+
+The resulting filename is similar to the names generated by \"git
+format-patch\", without the leading patch sequence number
+\"0001-\" and \".patch\" extension. Any leading \"[PREFIX]\"
+style strings are removed prior to conversion.
+
+Optional argument MAXLEN is the maximum length of the resulting
+filename, before trimming any trailing . and - characters."
+  (let* ((s (replace-regexp-in-string "^ *\\(\\[[^]]*\\] *\\)*" "" subject))
+        (s (replace-regexp-in-string "[^A-Za-z0-9._]+" "-" s))
+        (s (replace-regexp-in-string "\\.+" "." s))
+        (s (if maxlen (substring s 0 (min (length s) maxlen)) s))
+        (s (replace-regexp-in-string "[.-]*$" "" s)))
+    s))
+
+(defun notmuch-wash-subject-to-patch-sequence-number (subject)
+  "Convert a patch mail SUBJECT into a patch sequence number.
+
+Return the patch sequence number N from the last \"[PATCH N/M]\"
+style prefix in SUBJECT, or nil if such a prefix can't be found."
+  (when (string-match
+        "^ *\\(\\[[^]]*\\] *\\)*\\[[^]]*?\\([0-9]+\\)/[0-9]+[^]]*\\].*"
+        subject)
+      (string-to-number (substring subject (match-beginning 2) (match-end 2)))))
+
+(defun notmuch-wash-subject-to-patch-filename (subject)
+  "Convert a patch mail SUBJECT into a filename.
+
+The resulting filename is similar to the names generated by \"git
+format-patch\". If the patch mail was generated and sent using
+\"git format-patch/send-email\", this should re-create the
+original filename the sender had."
+  (format "%04d-%s.patch"
+         (or (notmuch-wash-subject-to-patch-sequence-number subject) 1)
+         (notmuch-wash-subject-to-filename subject 52)))
+
+(defun notmuch-wash-convert-inline-patch-to-part (msg depth)
+  "Convert an inline patch into a fake 'text/x-diff' attachment.
+
+Given that this function guesses whether a buffer includes a
+patch and then guesses the extent of the patch, there is scope
+for error."
+
+  (goto-char (point-min))
+  (when (re-search-forward diff-file-header-re nil t)
+    (beginning-of-line -1)
+    (let ((patch-start (point))
+         (patch-end (point-max))
+         part)
+      (goto-char patch-start)
+      (if (or
+          ;; Patch ends with signature.
+          (re-search-forward notmuch-wash-signature-regexp nil t)
+          ;; Patch ends with bugtraq comment.
+          (re-search-forward "^\\*\\*\\* " nil t))
+         (setq patch-end (match-beginning 0)))
+      (save-restriction
+       (narrow-to-region patch-start patch-end)
+       (setq part (plist-put part :content-type "inline patch"))
+       (setq part (plist-put part :content (buffer-string)))
+       (setq part (plist-put part :id -1))
+       (setq part (plist-put part :filename
+                             (notmuch-wash-subject-to-patch-filename
+                              (plist-get
+                               (plist-get msg :headers) :Subject))))
+       (delete-region (point-min) (point-max))
+       (notmuch-show-insert-bodypart nil part depth)))))
+
+;;
+
+(provide 'notmuch-wash)
+
+;;; notmuch-wash.el ends here
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
new file mode 100644 (file)
index 0000000..44402f8
--- /dev/null
@@ -0,0 +1,1151 @@
+;;; notmuch.el --- run notmuch within emacs
+;;
+;; Copyright © Carl Worth
+;;
+;; 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: Carl Worth <cworth@cworth.org>
+;; Homepage: https://notmuchmail.org/
+
+;;; Commentary:
+
+;; This is an emacs-based interface to the notmuch mail system.
+;;
+;; You will first need to have the notmuch program installed and have a
+;; notmuch database built in order to use this. See
+;; https://notmuchmail.org for details.
+;;
+;; To install this software, copy it to a directory that is on the
+;; `load-path' variable within emacs (a good candidate is
+;; /usr/local/share/emacs/site-lisp). If you are viewing this from the
+;; notmuch source distribution then you can simply run:
+;;
+;;     sudo make install-emacs
+;;
+;; to install it.
+;;
+;; Then, to actually run it, add:
+;;
+;;     (autoload 'notmuch "notmuch" "Notmuch mail" t)
+;;
+;; to your ~/.emacs file, and then run "M-x notmuch" from within emacs,
+;; or run:
+;;
+;;     emacs -f notmuch
+;;
+;; Have fun, and let us know if you have any comment, questions, or
+;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
+;; required, but is available from https://notmuchmail.org).
+;;
+;; Note for MELPA users (and others tracking the development version
+;; of notmuch-emacs):
+;;
+;; This emacs package needs a fairly closely matched version of the
+;; notmuch program. If you use the MELPA version of notmuch.el (as
+;; opposed to MELPA stable), you should be prepared to track the
+;; master development branch (i.e. build from git) for the notmuch
+;; program as well. Upgrading notmuch-emacs too far beyond the notmuch
+;; program can CAUSE YOUR EMAIL TO STOP WORKING.
+;;
+;; TL;DR: notmuch-emacs from MELPA and notmuch from distro packages is
+;; NOT SUPPORTED.
+;;
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'mm-view)
+(require 'message)
+
+(require 'notmuch-lib)
+(require 'notmuch-tag)
+(require 'notmuch-show)
+(require 'notmuch-tree)
+(require 'notmuch-mua)
+(require 'notmuch-hello)
+(require 'notmuch-maildir-fcc)
+(require 'notmuch-message)
+(require 'notmuch-parser)
+
+(defcustom notmuch-search-result-format
+  `(("date" . "%12s ")
+    ("count" . "%-7s ")
+    ("authors" . "%-20s ")
+    ("subject" . "%s ")
+    ("tags" . "(%s)"))
+  "Search result formatting. Supported fields are:
+       date, count, authors, subject, tags
+For example:
+       (setq notmuch-search-result-format \(\(\"authors\" . \"%-40s\"\)
+                                            \(\"subject\" . \"%s\"\)\)\)
+Line breaks are permitted in format strings (though this is
+currently experimental).  Note that a line break at the end of an
+\"authors\" field will get elided if the authors list is long;
+place it instead at the beginning of the following field.  To
+enter a line break when setting this variable with setq, use \\n.
+To enter a line break in customize, press \\[quoted-insert] C-j."
+  :type '(alist :key-type (string) :value-type (string))
+  :group 'notmuch-search)
+
+;; The name of this variable `notmuch-init-file' is consistent with the
+;; convention used in e.g. emacs and gnus. The value, `notmuch-config[.el[c]]'
+;; is consistent with notmuch cli configuration file `~/.notmuch-config'.
+(defcustom notmuch-init-file (locate-user-emacs-file "notmuch-config")
+  "Your Notmuch Emacs-Lisp configuration file name.
+If a file with one of the suffixes defined by `get-load-suffixes' exists,
+it will be read instead.
+This file is read once when notmuch is loaded; the notmuch hooks added
+there will be called at other points of notmuch execution."
+  :type 'file
+  :group 'notmuch)
+
+(defvar notmuch-query-history nil
+  "Variable to store minibuffer history for notmuch queries")
+
+(defun notmuch-foreach-mime-part (function mm-handle)
+  (cond ((stringp (car mm-handle))
+         (dolist (part (cdr mm-handle))
+           (notmuch-foreach-mime-part function part)))
+        ((bufferp (car mm-handle))
+         (funcall function mm-handle))
+        (t (dolist (part mm-handle)
+             (notmuch-foreach-mime-part function part)))))
+
+(defun notmuch-count-attachments (mm-handle)
+  (let ((count 0))
+    (notmuch-foreach-mime-part
+     (lambda (p)
+       (let ((disposition (mm-handle-disposition p)))
+         (and (listp disposition)
+              (or (equal (car disposition) "attachment")
+                  (and (equal (car disposition) "inline")
+                       (assq 'filename disposition)))
+              (incf count))))
+     mm-handle)
+    count))
+
+(defun notmuch-save-attachments (mm-handle &optional queryp)
+  (notmuch-foreach-mime-part
+   (lambda (p)
+     (let ((disposition (mm-handle-disposition p)))
+       (and (listp disposition)
+            (or (equal (car disposition) "attachment")
+                (and (equal (car disposition) "inline")
+                     (assq 'filename disposition)))
+            (or (not queryp)
+                (y-or-n-p
+                 (concat "Save '" (cdr (assq 'filename disposition)) "' ")))
+            (mm-save-part p))))
+   mm-handle))
+
+(require 'hl-line)
+
+(defun notmuch-hl-line-mode ()
+  (prog1 (hl-line-mode)
+    (when hl-line-overlay
+      (overlay-put hl-line-overlay 'priority 1))))
+
+(defcustom notmuch-search-hook '(notmuch-hl-line-mode)
+  "List of functions to call when notmuch displays the search results."
+  :type 'hook
+  :options '(notmuch-hl-line-mode)
+  :group 'notmuch-search
+  :group 'notmuch-hooks)
+
+(defvar notmuch-search-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map notmuch-common-keymap)
+    (define-key map "x" 'notmuch-bury-or-kill-this-buffer)
+    (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
+    (define-key map "b" 'notmuch-search-scroll-down)
+    (define-key map " " 'notmuch-search-scroll-up)
+    (define-key map "<" 'notmuch-search-first-thread)
+    (define-key map ">" 'notmuch-search-last-thread)
+    (define-key map "p" 'notmuch-search-previous-thread)
+    (define-key map "n" 'notmuch-search-next-thread)
+    (define-key map "r" 'notmuch-search-reply-to-thread-sender)
+    (define-key map "R" 'notmuch-search-reply-to-thread)
+    (define-key map "o" 'notmuch-search-toggle-order)
+    (define-key map "c" 'notmuch-search-stash-map)
+    (define-key map "t" 'notmuch-search-filter-by-tag)
+    (define-key map "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)
+    (define-key map "+" 'notmuch-search-add-tag)
+    (define-key map (kbd "RET") 'notmuch-search-show-thread)
+    (define-key map "Z" 'notmuch-tree-from-search-current-query)
+    map)
+  "Keymap for \"notmuch search\" buffers.")
+(fset 'notmuch-search-mode-map notmuch-search-mode-map)
+
+(defvar notmuch-search-stash-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "i" 'notmuch-search-stash-thread-id)
+    (define-key map "q" 'notmuch-stash-query)
+    (define-key map "?" 'notmuch-subkeymap-help)
+    map)
+  "Submap for stash commands")
+(fset 'notmuch-search-stash-map notmuch-search-stash-map)
+
+(defun notmuch-search-stash-thread-id ()
+  "Copy thread ID of current thread to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-search-find-thread-id)))
+
+(defun notmuch-stash-query ()
+  "Copy current query to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-search-get-query)))
+
+(defvar notmuch-search-query-string)
+(defvar notmuch-search-target-thread)
+(defvar notmuch-search-target-line)
+
+(defvar notmuch-search-disjunctive-regexp      "\\<[oO][rR]\\>")
+
+(defun notmuch-search-scroll-up ()
+  "Move forward through search results by one window's worth."
+  (interactive)
+  (condition-case nil
+      (scroll-up nil)
+    ((end-of-buffer) (notmuch-search-last-thread))))
+
+(defun notmuch-search-scroll-down ()
+  "Move backward through the search results by one window's worth."
+  (interactive)
+  ;; I don't know why scroll-down doesn't signal beginning-of-buffer
+  ;; the way that scroll-up signals end-of-buffer, but c'est la vie.
+  ;;
+  ;; So instead of trapping a signal we instead check whether the
+  ;; window begins on the first line of the buffer and if so, move
+  ;; directly to that position. (We have to count lines since the
+  ;; window-start position is not the same as point-min due to the
+  ;; invisible thread-ID characters on the first line.
+  (if (equal (count-lines (point-min) (window-start)) 0)
+      (goto-char (point-min))
+    (scroll-down nil)))
+
+(defun notmuch-search-next-thread ()
+  "Select the next thread in the search results."
+  (interactive)
+  (when (notmuch-search-get-result)
+    (goto-char (notmuch-search-result-end))))
+
+(defun notmuch-search-previous-thread ()
+  "Select the previous thread in the search results."
+  (interactive)
+  (if (notmuch-search-get-result)
+      (unless (bobp)
+       (goto-char (notmuch-search-result-beginning (- (point) 1))))
+    ;; We must be past the end; jump to the last result
+    (notmuch-search-last-thread)))
+
+(defun notmuch-search-last-thread ()
+  "Select the last thread in the search results."
+  (interactive)
+  (goto-char (point-max))
+  (forward-line -2)
+  (let ((beg (notmuch-search-result-beginning)))
+    (when beg (goto-char beg))))
+
+(defun notmuch-search-first-thread ()
+  "Select the first thread in the search results."
+  (interactive)
+  (goto-char (point-min)))
+
+(defface notmuch-message-summary-face
+ '((((class color) (background light)) (:background "#f0f0f0"))
+   (((class color) (background dark)) (:background "#303030")))
+ "Face for the single-line message summary in notmuch-show-mode."
+ :group 'notmuch-show
+ :group 'notmuch-faces)
+
+(defface notmuch-search-date
+  '((t :inherit default))
+  "Face used in search mode for dates."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-count
+  '((t :inherit default))
+  "Face used in search mode for the count matching the query."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-subject
+  '((t :inherit default))
+  "Face used in search mode for subjects."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-matching-authors
+  '((t :inherit default))
+  "Face used in search mode for authors matching the query."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-non-matching-authors
+  '((((class color)
+      (background dark))
+     (:foreground "grey30"))
+    (((class color)
+      (background light))
+     (:foreground "grey60"))
+    (t
+     (:italic t)))
+  "Face used in search mode for authors not matching the query."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-tag-face
+  '((((class color)
+      (background dark))
+     (:foreground "OliveDrab1"))
+    (((class color)
+      (background light))
+     (:foreground "navy blue" :bold t))
+    (t
+     (:bold t)))
+  "Face used in search mode face for tags."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-flagged-face
+  '((((class color)
+      (background dark))
+     (:foreground "LightBlue1"))
+    (((class color)
+      (background light))
+     (:foreground "blue")))
+  "Face used in search mode face for flagged threads.
+
+This face is the default value for the \"flagged\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-unread-face
+  '((t
+     (:weight bold)))
+  "Face used in search mode for unread threads.
+
+This face is the default value for the \"unread\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(define-derived-mode notmuch-search-mode fundamental-mode "notmuch-search"
+  "Major mode displaying results of a notmuch search.
+
+This buffer contains the results of a \"notmuch search\" of your
+email archives. Each line in the buffer represents a single
+thread giving a summary of the thread (a relative date, the
+number of matched messages and total messages in the thread,
+participants in the thread, a representative subject line, and
+any tags).
+
+Pressing \\[notmuch-search-show-thread] on any line displays that
+thread. The '\\[notmuch-search-add-tag]' and
+'\\[notmuch-search-remove-tag]' keys can be used to add or remove
+tags from a thread. The '\\[notmuch-search-archive-thread]' key
+is a convenience for archiving a thread (applying changes in
+`notmuch-archive-tags'). The '\\[notmuch-search-tag-all]' key can
+be used to add and/or remove tags from all messages (as opposed
+to threads) that match the current query.  Use with caution, as
+this will also tag matching messages that arrived *after*
+constructing the buffer.
+
+Other useful commands are '\\[notmuch-search-filter]' for
+filtering the current search based on an additional query string,
+'\\[notmuch-search-filter-by-tag]' for filtering to include only
+messages with a given tag, and '\\[notmuch-search]' to execute a
+new, global search.
+
+Complete list of currently available key bindings:
+
+\\{notmuch-search-mode-map}"
+  (make-local-variable 'notmuch-search-query-string)
+  (make-local-variable 'notmuch-search-oldest-first)
+  (make-local-variable 'notmuch-search-target-thread)
+  (make-local-variable 'notmuch-search-target-line)
+  (setq notmuch-buffer-refresh-function #'notmuch-search-refresh-view)
+  (set (make-local-variable 'scroll-preserve-screen-position) t)
+  (add-to-invisibility-spec (cons 'ellipsis t))
+  (setq truncate-lines t)
+  (setq buffer-read-only t)
+  (setq imenu-prev-index-position-function
+        #'notmuch-search-imenu-prev-index-position-function)
+  (setq imenu-extract-index-name-function
+        #'notmuch-search-imenu-extract-index-name-function))
+
+(defun notmuch-search-get-result (&optional pos)
+  "Return the result object for the thread at POS (or point).
+
+If there is no thread at POS (or point), returns nil."
+  (get-text-property (or pos (point)) 'notmuch-search-result))
+
+(defun notmuch-search-result-beginning (&optional pos)
+  "Return the point at the beginning of the thread at POS (or point).
+
+If there is no thread at POS (or point), returns nil."
+  (when (notmuch-search-get-result pos)
+    ;; We pass 1+point because previous-single-property-change starts
+    ;; searching one before the position we give it.
+    (previous-single-property-change (1+ (or pos (point)))
+                                    'notmuch-search-result nil (point-min))))
+
+(defun notmuch-search-result-end (&optional pos)
+  "Return the point at the end of the thread at POS (or point).
+
+The returned point will be just after the newline character that
+ends the result line.  If there is no thread at POS (or point),
+returns nil"
+  (when (notmuch-search-get-result pos)
+    (next-single-property-change (or pos (point)) 'notmuch-search-result
+                                nil (point-max))))
+
+(defun notmuch-search-foreach-result (beg end fn)
+  "Invoke FN for each result between BEG and END.
+
+FN should take one argument.  It will be applied to the
+character position of the beginning of each result that overlaps
+the region between points BEG and END.  As a special case, if (=
+BEG END), FN will be applied to the result containing point
+BEG."
+
+  (lexical-let ((pos (notmuch-search-result-beginning beg))
+               ;; End must be a marker in case fn changes the
+               ;; text.
+               (end (copy-marker end))
+               ;; Make sure we examine at least one result, even if
+               ;; (= beg end).
+               (first t))
+    ;; We have to be careful if the region extends beyond the results.
+    ;; In this case, pos could be null or there could be no result at
+    ;; pos.
+    (while (and pos (or (< pos end) first))
+      (when (notmuch-search-get-result pos)
+       (funcall fn pos))
+      (setq pos (notmuch-search-result-end pos)
+           first nil))))
+;; Unindent the function argument of notmuch-search-foreach-result so
+;; the indentation of callers doesn't get out of hand.
+(put 'notmuch-search-foreach-result 'lisp-indent-function 2)
+
+(defun notmuch-search-properties-in-region (property beg end)
+  (let (output)
+    (notmuch-search-foreach-result beg end
+      (lambda (pos)
+       (push (plist-get (notmuch-search-get-result pos) property) output)))
+    output))
+
+(defun notmuch-search-find-thread-id (&optional bare)
+  "Return the thread for the current thread
+
+If BARE is set then do not prefix with \"thread:\""
+  (let ((thread (plist-get (notmuch-search-get-result) :thread)))
+    (when thread (concat (unless bare "thread:") thread))))
+
+(defun notmuch-search-find-stable-query ()
+  "Return the stable queries for the current thread.
+
+This returns a list (MATCHED-QUERY UNMATCHED-QUERY) for the
+matched and unmatched messages in the current thread."
+  (plist-get (notmuch-search-get-result) :query))
+
+(defun notmuch-search-find-stable-query-region (beg end &optional only-matched)
+  "Return the stable query for the current region.
+
+If ONLY-MATCHED is non-nil, include only matched messages.  If it
+is nil, include both matched and unmatched messages. If there are
+no messages in the region then return nil."
+  (let ((query-list nil) (all (not only-matched)))
+    (dolist (queries (notmuch-search-properties-in-region :query beg end))
+      (when (first queries)
+       (push (first queries) query-list))
+      (when (and all (second queries))
+       (push (second queries) query-list)))
+    (when query-list
+      (concat "(" (mapconcat 'identity query-list ") or (") ")"))))
+
+(defun notmuch-search-find-authors ()
+  "Return the authors for the current thread"
+  (plist-get (notmuch-search-get-result) :authors))
+
+(defun notmuch-search-find-authors-region (beg end)
+  "Return a list of authors for the current region"
+  (notmuch-search-properties-in-region :authors beg end))
+
+(defun notmuch-search-find-subject ()
+  "Return the subject for the current thread"
+  (plist-get (notmuch-search-get-result) :subject))
+
+(defun notmuch-search-find-subject-region (beg end)
+  "Return a list of authors for the current region"
+  (notmuch-search-properties-in-region :subject beg end))
+
+(defun notmuch-search-show-thread (&optional elide-toggle)
+  "Display the currently selected thread.
+
+With a prefix argument, invert the default value of
+`notmuch-show-only-matching-messages' when displaying the
+thread."
+  (interactive "P")
+  (let ((thread-id (notmuch-search-find-thread-id))
+       (subject (notmuch-search-find-subject)))
+    (if (> (length thread-id) 0)
+       (notmuch-show thread-id
+                     elide-toggle
+                     (current-buffer)
+                     notmuch-search-query-string
+                     ;; Name the buffer based on the subject.
+                     (concat "*" (truncate-string-to-width subject 30 nil nil t) "*"))
+      (message "End of search results."))))
+
+(defun notmuch-tree-from-search-current-query ()
+  "Call notmuch tree with the current query"
+  (interactive)
+  (notmuch-tree notmuch-search-query-string))
+
+(defun notmuch-tree-from-search-thread ()
+  "Show the selected thread with notmuch-tree"
+  (interactive)
+  (notmuch-tree (notmuch-search-find-thread-id)
+                notmuch-search-query-string
+               nil
+                (notmuch-prettify-subject (notmuch-search-find-subject))
+               t))
+
+(defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
+  "Begin composing a reply-all to the entire current thread in a new buffer."
+  (interactive "P")
+  (let ((message-id (notmuch-search-find-thread-id)))
+    (notmuch-mua-new-reply message-id prompt-for-sender t)))
+
+(defun notmuch-search-reply-to-thread-sender (&optional prompt-for-sender)
+  "Begin composing a reply to the entire current thread in a new buffer."
+  (interactive "P")
+  (let ((message-id (notmuch-search-find-thread-id)))
+    (notmuch-mua-new-reply message-id prompt-for-sender nil)))
+
+(defun notmuch-search-set-tags (tags &optional pos)
+  (let ((new-result (plist-put (notmuch-search-get-result pos) :tags tags)))
+    (notmuch-search-update-result new-result pos)))
+
+(defun notmuch-search-get-tags (&optional pos)
+  (plist-get (notmuch-search-get-result pos) :tags))
+
+(defun notmuch-search-get-tags-region (beg end)
+  (let (output)
+    (notmuch-search-foreach-result beg end
+      (lambda (pos)
+       (setq output (append output (notmuch-search-get-tags pos)))))
+    output))
+
+(defun notmuch-search-interactive-region ()
+  "Return the bounds of the current interactive region.
+
+This returns (BEG END), where BEG and END are the bounds of the
+region if the region is active, or both `point' otherwise."
+  (if (region-active-p)
+      (list (region-beginning) (region-end))
+    (list (point) (point))))
+
+(defun notmuch-search-interactive-tag-changes (&optional initial-input)
+  "Prompt for tag changes for the current thread or region.
+
+Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
+  (let* ((region (notmuch-search-interactive-region))
+        (beg (first region)) (end (second region))
+        (prompt (if (= beg end) "Tag thread" "Tag region")))
+    (cons (notmuch-read-tag-changes
+          (notmuch-search-get-tags-region beg end) prompt initial-input)
+         region)))
+
+(defun notmuch-search-tag (tag-changes &optional beg end only-matched)
+  "Change tags for the currently selected thread or region.
+
+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, 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 (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)
+    (notmuch-search-foreach-result beg end
+      (lambda (pos)
+       (notmuch-search-set-tags
+        (notmuch-update-tags (notmuch-search-get-tags pos) tag-changes)
+        pos)))))
+
+(defun notmuch-search-add-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to add).
+
+Same as `notmuch-search-tag' but sets initial input to '+'."
+  (interactive (notmuch-search-interactive-tag-changes "+"))
+  (notmuch-search-tag tag-changes beg end))
+
+(defun notmuch-search-remove-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to remove).
+
+Same as `notmuch-search-tag' but sets initial input to '-'."
+  (interactive (notmuch-search-interactive-tag-changes "-"))
+  (notmuch-search-tag tag-changes beg end))
+
+(put 'notmuch-search-archive-thread 'notmuch-prefix-doc
+     "Un-archive the currently selected thread.")
+(defun notmuch-search-archive-thread (&optional unarchive beg end)
+  "Archive the currently selected thread or region.
+
+Archive each message in the currently selected thread by applying
+the tag changes in `notmuch-archive-tags' to each (remove the
+\"inbox\" tag by default). If a prefix argument is given, the
+messages will be \"unarchived\" (i.e. the tag changes in
+`notmuch-archive-tags' will be reversed).
+
+This function advances the next thread when finished."
+  (interactive (cons current-prefix-arg (notmuch-search-interactive-region)))
+  (when notmuch-archive-tags
+    (notmuch-search-tag
+     (notmuch-tag-change-list notmuch-archive-tags unarchive) beg end))
+  (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
+RESULT and redraw it.
+
+This will keep point in a reasonable location.  However, if there
+are enclosing save-excursions and the saved point is in the
+result being updated, the point will be restored to the beginning
+of the result."
+  (let ((start (notmuch-search-result-beginning pos))
+       (end (notmuch-search-result-end pos))
+       (init-point (point))
+       (inhibit-read-only t))
+    ;; Delete the current thread
+    (delete-region start end)
+    ;; Insert the updated thread
+    (notmuch-search-show-result result start)
+    ;; If point was inside the old result, make an educated guess
+    ;; about where to place it now.  Unfortunately, this won't work
+    ;; with save-excursion (or any other markers that would be nice to
+    ;; preserve, such as the window start), but there's nothing we can
+    ;; do about that without a way to retrieve markers in a region.
+    (when (and (>= init-point start) (<= init-point end))
+      (let* ((new-end (notmuch-search-result-end start))
+            (new-point (if (= init-point end)
+                           new-end
+                         (min init-point (- new-end 1)))))
+       (goto-char new-point)))))
+
+(defun notmuch-search-process-sentinel (proc msg)
+  "Add a message to let user know when \"notmuch search\" exits"
+  (let ((buffer (process-buffer proc))
+       (status (process-status proc))
+       (exit-status (process-exit-status proc))
+       (never-found-target-thread nil))
+    (when (memq status '(exit signal))
+      (catch 'return
+       (kill-buffer (process-get proc 'parse-buf))
+       (if (buffer-live-p buffer)
+           (with-current-buffer buffer
+             (save-excursion
+               (let ((inhibit-read-only t)
+                     (atbob (bobp)))
+                 (goto-char (point-max))
+                 (if (eq status 'signal)
+                     (insert "Incomplete search results (search process was killed).\n"))
+                 (when (eq status 'exit)
+                   (insert "End of search results.\n")
+                   ;; For version mismatch, there's no point in
+                   ;; showing the search buffer
+                   (when (or (= exit-status 20) (= exit-status 21))
+                     (kill-buffer)
+                     (throw 'return nil))
+                   (if (and atbob
+                            (not (string= notmuch-search-target-thread "found")))
+                       (set 'never-found-target-thread t)))))
+             (when (and never-found-target-thread
+                      notmuch-search-target-line)
+                 (goto-char (point-min))
+                 (forward-line (1- notmuch-search-target-line)))))))))
+
+(define-widget 'notmuch--custom-face-edit 'lazy
+  "Custom face edit with a tag Edit Face"
+  ;; I could not persuage custom-face-edit to respect the :tag
+  ;; property so create a widget specially
+  :tag "Manually specify face"
+  :type 'custom-face-edit)
+
+(defcustom notmuch-search-line-faces
+  '(("unread" . notmuch-search-unread-face)
+    ("flagged" . notmuch-search-flagged-face))
+  "Alist of tags to faces for line highlighting in notmuch-search.
+Each element looks like (TAG . FACE).
+A thread with TAG will have FACE applied.
+
+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 '((\"unread\" . (:foreground \"green\"))
+                                   (\"deleted\" . (:foreground \"red\"
+                                                 :background \"blue\"))))
+
+The FACE must be a face name (a symbol or string), a property
+list of face attributes, or a list of these.  The faces 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 (radio (face :tag "Face name")
+                                   (notmuch--custom-face-edit)))
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defun notmuch-search-color-line (start end line-tag-list)
+  "Colorize lines in `notmuch-show' based on tags."
+  ;; Reverse the list so earlier entries take precedence
+  (dolist (elem (reverse notmuch-search-line-faces))
+    (let ((tag (car elem))
+         (face (cdr elem)))
+      (when (member tag line-tag-list)
+       (notmuch-apply-face nil face nil start end)))))
+
+(defun notmuch-search-author-propertize (authors)
+  "Split `authors' into matching and non-matching authors and
+propertize appropriately. If no boundary between authors and
+non-authors is found, assume that all of the authors match."
+  (if (string-match "\\(.*\\)|\\(.*\\)" authors)
+      (concat (propertize (concat (match-string 1 authors) ",")
+                         'face 'notmuch-search-matching-authors)
+             (propertize (match-string 2 authors)
+                         'face 'notmuch-search-non-matching-authors))
+    (propertize authors 'face 'notmuch-search-matching-authors)))
+
+(defun notmuch-search-insert-authors (format-string authors)
+  ;; Save the match data to avoid interfering with
+  ;; `notmuch-search-process-filter'.
+  (save-match-data
+    (let* ((formatted-authors (format format-string authors))
+          (formatted-sample (format format-string ""))
+          (visible-string formatted-authors)
+          (invisible-string "")
+          (padding ""))
+
+      ;; Truncate the author string to fit the specification.
+      (if (> (length formatted-authors)
+            (length formatted-sample))
+         (let ((visible-length (- (length formatted-sample)
+                                  (length "... "))))
+           ;; Truncate the visible string according to the width of
+           ;; the display string.
+           (setq visible-string (substring formatted-authors 0 visible-length)
+                 invisible-string (substring formatted-authors visible-length))
+           ;; If possible, truncate the visible string at a natural
+           ;; break (comma or pipe), as incremental search doesn't
+           ;; match across the visible/invisible border.
+           (when (string-match "\\(.*\\)\\([,|] \\)\\([^,|]*\\)" visible-string)
+             ;; Second clause is destructive on `visible-string', so
+             ;; order is important.
+             (setq invisible-string (concat (match-string 3 visible-string)
+                                            invisible-string)
+                   visible-string (concat (match-string 1 visible-string)
+                                          (match-string 2 visible-string))))
+           ;; `visible-string' may be shorter than the space allowed
+           ;; by `format-string'. If so we must insert some padding
+           ;; after `invisible-string'.
+           (setq padding (make-string (- (length formatted-sample)
+                                         (length visible-string)
+                                         (length "..."))
+                                      ? ))))
+
+      ;; Use different faces to show matching and non-matching authors.
+      (if (string-match "\\(.*\\)|\\(.*\\)" visible-string)
+         ;; The visible string contains both matching and
+         ;; non-matching authors.
+         (setq visible-string (notmuch-search-author-propertize visible-string)
+               ;; The invisible string must contain only non-matching
+               ;; authors, as the visible-string contains both.
+               invisible-string (propertize invisible-string
+                                            'face 'notmuch-search-non-matching-authors))
+       ;; The visible string contains only matching authors.
+       (setq visible-string (propertize visible-string
+                                        'face 'notmuch-search-matching-authors)
+             ;; The invisible string may contain both matching and
+             ;; non-matching authors.
+             invisible-string (notmuch-search-author-propertize invisible-string)))
+
+      ;; If there is any invisible text, add it as a tooltip to the
+      ;; visible text.
+      (when (not (string= invisible-string ""))
+       (setq visible-string (propertize visible-string 'help-echo (concat "..." invisible-string))))
+
+      ;; Insert the visible and, if present, invisible author strings.
+      (insert visible-string)
+      (when (not (string= invisible-string ""))
+       (let ((start (point))
+             overlay)
+         (insert invisible-string)
+         (setq overlay (make-overlay start (point)))
+         (overlay-put overlay 'invisible 'ellipsis)
+         (overlay-put overlay 'isearch-open-invisible #'delete-overlay)))
+      (insert padding))))
+
+(defun notmuch-search-insert-field (field format-string result)
+  (cond
+   ((string-equal field "date")
+    (insert (propertize (format format-string (plist-get result :date_relative))
+                       'face 'notmuch-search-date)))
+   ((string-equal field "count")
+    (insert (propertize (format format-string
+                               (format "[%s/%s]" (plist-get result :matched)
+                                       (plist-get result :total)))
+                       'face 'notmuch-search-count)))
+   ((string-equal field "subject")
+    (insert (propertize (format format-string
+                               (notmuch-sanitize (plist-get result :subject)))
+                       'face 'notmuch-search-subject)))
+
+   ((string-equal field "authors")
+    (notmuch-search-insert-authors
+     format-string (notmuch-sanitize (plist-get result :authors))))
+
+   ((string-equal field "tags")
+    (let ((tags (plist-get result :tags))
+         (orig-tags (plist-get result :orig-tags)))
+      (insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
+
+(defun notmuch-search-show-result (result pos)
+  "Insert RESULT at POS."
+  ;; Ignore excluded matches
+  (unless (= (plist-get result :matched) 0)
+    (save-excursion
+      (goto-char pos)
+      (dolist (spec notmuch-search-result-format)
+       (notmuch-search-insert-field (car spec) (cdr spec) result))
+      (insert "\n")
+      (notmuch-search-color-line pos (point) (plist-get result :tags))
+      (put-text-property pos (point) 'notmuch-search-result result))))
+
+(defun notmuch-search-append-result (result)
+  "Insert RESULT at the end of the buffer.
+
+This is only called when a result is first inserted so it also
+sets the :orig-tag property."
+  (let ((new-result (plist-put result :orig-tags (plist-get result :tags)))
+       (pos (point-max)))
+    (notmuch-search-show-result new-result pos)
+    (when (string= (plist-get result :thread) notmuch-search-target-thread)
+      (setq notmuch-search-target-thread "found")
+      (goto-char pos))))
+
+(defun notmuch-search-process-filter (proc string)
+  "Process and filter the output of \"notmuch search\""
+  (let ((results-buf (process-buffer proc))
+       (parse-buf (process-get proc 'parse-buf))
+       (inhibit-read-only t)
+       done)
+    (when (buffer-live-p results-buf)
+      (with-current-buffer parse-buf
+       ;; Insert new data
+       (save-excursion
+         (goto-char (point-max))
+         (insert string))
+       (notmuch-sexp-parse-partial-list 'notmuch-search-append-result
+                                        results-buf)))))
+
+(defun notmuch-search-tag-all (tag-changes)
+  "Add/remove tags from all messages in current search buffer.
+
+See `notmuch-tag' for information on the format of TAG-CHANGES."
+  (interactive
+   (list (notmuch-read-tag-changes
+         (notmuch-search-get-tags-region (point-min) (point-max)) "Tag all")))
+  (notmuch-search-tag tag-changes (point-min) (point-max) t))
+
+(defun notmuch-search-buffer-title (query)
+  "Returns the title for a buffer with notmuch search results."
+  (let* ((saved-search
+         (let (longest
+               (longest-length 0))
+           (loop for tuple in notmuch-saved-searches
+                 if (let ((quoted-query (regexp-quote (notmuch-saved-search-get tuple :query))))
+                      (and (string-match (concat "^" quoted-query) query)
+                           (> (length (match-string 0 query))
+                              longest-length)))
+                 do (setq longest tuple))
+           longest))
+        (saved-search-name (notmuch-saved-search-get saved-search :name))
+        (saved-search-query (notmuch-saved-search-get saved-search :query)))
+    (cond ((and saved-search (equal saved-search-query query))
+          ;; Query is the same as saved search (ignoring case)
+          (concat "*notmuch-saved-search-" saved-search-name "*"))
+         (saved-search
+          (concat "*notmuch-search-"
+                  (replace-regexp-in-string (concat "^" (regexp-quote saved-search-query))
+                                            (concat "[ " saved-search-name " ]")
+                                            query)
+                  "*"))
+         (t
+          (concat "*notmuch-search-" query "*"))
+         )))
+
+(defun notmuch-read-query (prompt)
+  "Read a notmuch-query from the minibuffer with completion.
+
+PROMPT is the string to prompt with."
+  (lexical-let*
+      ((all-tags
+        (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
+                (process-lines notmuch-command "search" "--output=tags" "*")))
+       (completions
+        (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
+                      "subject:" "attachment:")
+                (mapcar (lambda (tag) (concat "tag:" 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))
+                          (notmuch-show-mode (notmuch-show-get-query))
+                          (notmuch-tree-mode (notmuch-tree-get-query))))
+         (minibuffer-completion-table
+          (completion-table-dynamic
+           (lambda (string)
+             ;; generate a list of possible completions for the current input
+             (cond
+              ;; this ugly regexp is used to get the last word of the input
+              ;; possibly preceded by a '('
+              ((string-match "\\(^\\|.* (?\\)\\([^ ]*\\)$" string)
+               (mapcar (lambda (compl)
+                         (concat (match-string-no-properties 1 string) compl))
+                       (all-completions (match-string-no-properties 2 string)
+                                        completions)))
+              (t (list string)))))))
+      ;; this was simpler than convincing completing-read to accept spaces:
+      (define-key keymap (kbd "TAB") 'minibuffer-complete)
+      (let ((history-delete-duplicates t))
+       (read-from-minibuffer prompt nil keymap nil
+                             'notmuch-search-history current-query nil)))))
+
+(defun notmuch-search-get-query ()
+  "Return the current query in this search buffer"
+  notmuch-search-query-string)
+
+(put 'notmuch-search 'notmuch-doc "Search for messages.")
+;;;###autoload
+(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.
+Other optional parameters are used as follows:
+
+  OLDEST-FIRST: A Boolean controlling the sort order of returned threads
+  TARGET-THREAD: A thread ID (without the thread: prefix) that will be made
+                 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."
+  (interactive
+   (list
+    ;; Prompt for a query
+    nil
+    ;; Use the default search order (if we're doing a search from a
+    ;; search buffer, ignore any buffer-local overrides)
+    (default-value 'notmuch-search-oldest-first)))
+
+  (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
+        (buffer (get-buffer-create (notmuch-search-buffer-title query))))
+    (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)
+    (set 'notmuch-search-query-string query)
+    (set 'notmuch-search-oldest-first oldest-first)
+    (set 'notmuch-search-target-thread target-thread)
+    (set 'notmuch-search-target-line target-line)
+    (notmuch-tag-clear-cache)
+    (let ((proc (get-buffer-process (current-buffer)))
+         (inhibit-read-only t))
+      (if proc
+         (error "notmuch search process already running for query `%s'" query)
+       )
+      (erase-buffer)
+      (goto-char (point-min))
+      (save-excursion
+       (let ((proc (notmuch-start-notmuch
+                    "notmuch-search" buffer #'notmuch-search-process-sentinel
+                    "search" "--format=sexp" "--format-version=4"
+                    (if oldest-first
+                        "--sort=oldest-first"
+                      "--sort=newest-first")
+                    query))
+             ;; Use a scratch buffer to accumulate partial output.
+             ;; This buffer will be killed by the sentinel, which
+             ;; should be called no matter how the process dies.
+             (parse-buf (generate-new-buffer " *notmuch search parse*")))
+         (process-put proc 'parse-buf parse-buf)
+         (set-process-filter proc 'notmuch-search-process-filter)
+         (set-process-query-on-exit-flag proc nil))))
+    (run-hooks 'notmuch-search-hook)))
+
+(defun notmuch-search-refresh-view ()
+  "Refresh the current view.
+
+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-search erases the current buffer.
+    (notmuch-search query oldest-first target-thread target-line t)
+    (goto-char (point-min))))
+
+(defun notmuch-search-toggle-order ()
+  "Toggle the current search order.
+
+This command toggles the sort order for the current search. The
+default sort order is defined by `notmuch-search-oldest-first'."
+  (interactive)
+  (set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
+  (notmuch-search-refresh-view))
+
+(defun notmuch-group-disjunctive-query-string (query-string)
+  "Group query if it contains a complex expression.
+
+Enclose QUERY-STRING in parentheses if it matches
+`notmuch-search-disjunctive-regexp'."
+  (if (string-match-p notmuch-search-disjunctive-regexp query-string)
+      (concat "( " query-string " )")
+    query-string))
+
+(defun notmuch-search-filter (query)
+  "Filter or LIMIT the current search results based on an additional query string.
+
+Runs a new search matching only messages that match both the
+current search results AND the additional query string provided."
+  (interactive (list (notmuch-read-query "Filter search: ")))
+  (let ((grouped-query (notmuch-group-disjunctive-query-string query))
+       (grouped-original-query (notmuch-group-disjunctive-query-string
+                                notmuch-search-query-string)))
+    (notmuch-search (if (string= grouped-original-query "*")
+                       grouped-query
+                     (concat grouped-original-query " and " grouped-query))
+                   notmuch-search-oldest-first)))
+
+(defun notmuch-search-filter-by-tag (tag)
+  "Filter the current search results based on a single tag.
+
+Runs a new search matching only messages that match both the
+current search results AND that are tagged with the given tag."
+  (interactive
+   (list (notmuch-select-tag-with-completion "Filter by tag: ")))
+  (notmuch-search (concat notmuch-search-query-string " and tag:" tag) notmuch-search-oldest-first))
+
+;;;###autoload
+(defun notmuch ()
+  "Run notmuch and display saved searches, known tags, etc."
+  (interactive)
+  (notmuch-hello))
+
+(defun notmuch-interesting-buffer (b)
+  "Is the current buffer of interest to a notmuch user?"
+  (with-current-buffer b
+    (memq major-mode '(notmuch-show-mode
+                      notmuch-search-mode
+                      notmuch-tree-mode
+                      notmuch-hello-mode
+                      notmuch-message-mode))))
+
+;;;###autoload
+(defun notmuch-cycle-notmuch-buffers ()
+  "Cycle through any existing notmuch buffers (search, show or hello).
+
+If the current buffer is the only notmuch buffer, bury it. If no
+notmuch buffers exist, run `notmuch'."
+  (interactive)
+
+  (let (start first)
+    ;; If the current buffer is a notmuch buffer, remember it and then
+    ;; bury it.
+    (when (notmuch-interesting-buffer (current-buffer))
+      (setq start (current-buffer))
+      (bury-buffer))
+
+    ;; Find the first notmuch buffer.
+    (setq first (loop for buffer in (buffer-list)
+                     if (notmuch-interesting-buffer buffer)
+                     return buffer))
+
+    (if first
+       ;; If the first one we found is any other than the starting
+       ;; buffer, switch to it.
+       (unless (eq first start)
+         (switch-to-buffer first))
+      (notmuch))))
+
+;;;; Imenu Support
+
+(defun notmuch-search-imenu-prev-index-position-function ()
+  "Move point to previous message in notmuch-search buffer.
+This function is used as a value for
+`imenu-prev-index-position-function'."
+  (notmuch-search-previous-thread))
+
+(defun notmuch-search-imenu-extract-index-name-function ()
+  "Return imenu name for line at point.
+This function is used as a value for
+`imenu-extract-index-name-function'.  Point should be at the
+beginning of the line."
+  (let ((subject (notmuch-search-find-subject))
+       (author (notmuch-search-find-authors)))
+    (format "%s (%s)" subject author)))
+
+(setq mail-user-agent 'notmuch-user-agent)
+
+(provide 'notmuch)
+
+;; After provide to avoid loops if notmuch was require'd via notmuch-init-file.
+(if init-file-user ; don't load init file if the -q option was used.
+    (let ((init-file (locate-file notmuch-init-file '("/")
+                                 (get-load-suffixes))))
+      (if init-file (load init-file nil t t))))
+
+;;; notmuch.el ends here
diff --git a/gbp.conf b/gbp.conf
deleted file mode 100644 (file)
index a1c8735..0000000
--- a/gbp.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-# Configuration file for git-buildpackage
-
-[DEFAULT]
-# The default branch for upstream sources
-upstream-branch = master
-
-# The default branch for the debian patch (no patch in our case)
-debian-branch = master
-
-# Format for upstream tags
-upstream-tag = %(version)s
-
-# Format for the debian tag
-debian-tag = debian/%(version)s
diff --git a/gmime-filter-reply.c b/gmime-filter-reply.c
new file mode 100644 (file)
index 0000000..f673c0a
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <stdbool.h>
+
+#include "gmime-filter-reply.h"
+
+/**
+ * SECTION: gmime-filter-reply
+ * @title: GMimeFilterReply
+ * @short_description: Add/remove reply markers
+ *
+ * A #GMimeFilter for adding or removing reply markers
+ **/
+
+
+static void g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass);
+static void g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass);
+static void g_mime_filter_reply_finalize (GObject *object);
+
+static GMimeFilter *filter_copy (GMimeFilter *filter);
+static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                          char **out, size_t *outlen, size_t *outprespace);
+static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                            char **out, size_t *outlen, size_t *outprespace);
+static void filter_reset (GMimeFilter *filter);
+
+
+static GMimeFilterClass *parent_class = NULL;
+
+GType
+g_mime_filter_reply_get_type (void)
+{
+       static GType type = 0;
+
+       if (!type) {
+               static const GTypeInfo info = {
+                       sizeof (GMimeFilterReplyClass),
+                       NULL, /* base_class_init */
+                       NULL, /* base_class_finalize */
+                       (GClassInitFunc) g_mime_filter_reply_class_init,
+                       NULL, /* class_finalize */
+                       NULL, /* class_data */
+                       sizeof (GMimeFilterReply),
+                       0,    /* n_preallocs */
+                       (GInstanceInitFunc) g_mime_filter_reply_init,
+                       NULL    /* value_table */
+               };
+
+               type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0);
+       }
+
+       return type;
+}
+
+
+static void
+g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
+
+       parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
+
+       object_class->finalize = g_mime_filter_reply_finalize;
+
+       filter_class->copy = filter_copy;
+       filter_class->filter = filter_filter;
+       filter_class->complete = filter_complete;
+       filter_class->reset = filter_reset;
+}
+
+static void
+g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass)
+{
+       (void) klass;
+       filter->saw_nl = true;
+       filter->saw_angle = false;
+}
+
+static void
+g_mime_filter_reply_finalize (GObject *object)
+{
+       G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GMimeFilter *
+filter_copy (GMimeFilter *filter)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+
+       return g_mime_filter_reply_new (reply->encode);
+}
+
+static void
+filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
+              char **outbuf, size_t *outlen, size_t *outprespace)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+       const char *inptr = inbuf;
+       const char *inend = inbuf + inlen;
+       char *outptr;
+
+       (void) prespace;
+       if (reply->encode) {
+               g_mime_filter_set_size (filter, 3 * inlen, false);
+
+               outptr = filter->outbuf;
+               while (inptr < inend) {
+                       if (reply->saw_nl) {
+                               *outptr++ = '>';
+                               *outptr++ = ' ';
+                               reply->saw_nl = false;
+                       }
+                       if (*inptr == '\n')
+                               reply->saw_nl = true;
+                       else
+                               reply->saw_nl = false;
+                       if (*inptr != '\r')
+                               *outptr++ = *inptr;
+                       inptr++;
+               }
+       } else {
+               g_mime_filter_set_size (filter, inlen + 1, false);
+
+               outptr = filter->outbuf;
+               while (inptr < inend) {
+                       if (reply->saw_nl) {
+                               if (*inptr == '>')
+                                       reply->saw_angle = true;
+                               else
+                                       *outptr++ = *inptr;
+                               reply->saw_nl = false;
+                       } else if (reply->saw_angle) {
+                               if (*inptr == ' ')
+                                       ;
+                               else
+                                       *outptr++ = *inptr;
+                               reply->saw_angle = false;
+                       } else if (*inptr != '\r') {
+                               if (*inptr == '\n')
+                                       reply->saw_nl = true;
+                               *outptr++ = *inptr;
+                       }
+
+                       inptr++;
+               }
+       }
+
+       *outlen = outptr - filter->outbuf;
+       *outprespace = filter->outpre;
+       *outbuf = filter->outbuf;
+}
+
+static void
+filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
+                char **outbuf, size_t *outlen, size_t *outprespace)
+{
+       if (inbuf && inlen)
+               filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
+}
+
+static void
+filter_reset (GMimeFilter *filter)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+
+       reply->saw_nl = true;
+       reply->saw_angle = false;
+}
+
+
+/**
+ * g_mime_filter_reply_new:
+ * @encode: %true if the filter should encode or %false otherwise
+ * @dots: encode/decode dots (as for SMTP)
+ *
+ * Creates a new #GMimeFilterReply filter.
+ *
+ * If @encode is %true, then all lines will be prefixed by "> ",
+ * otherwise any lines starting with "> " will have that removed
+ *
+ * Returns: a new #GMimeFilterReply filter.
+ **/
+GMimeFilter *
+g_mime_filter_reply_new (gboolean encode)
+{
+       GMimeFilterReply *new_reply;
+
+       new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL);
+       new_reply->encode = encode;
+
+       return (GMimeFilter *) new_reply;
+}
+
diff --git a/gmime-filter-reply.h b/gmime-filter-reply.h
new file mode 100644 (file)
index 0000000..b7cbc6b
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _GMIME_FILTER_REPLY_H_
+#define _GMIME_FILTER_REPLY_H_
+
+#include <gmime/gmime-filter.h>
+
+G_BEGIN_DECLS
+
+#define GMIME_TYPE_FILTER_REPLY            (g_mime_filter_reply_get_type ())
+#define GMIME_FILTER_REPLY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReply))
+#define GMIME_FILTER_REPLY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
+#define GMIME_IS_FILTER_REPLY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GMIME_TYPE_FILTER_REPLY))
+#define GMIME_IS_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GMIME_TYPE_FILTER_REPLY))
+#define GMIME_FILTER_REPLY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
+
+typedef struct _GMimeFilterReply GMimeFilterReply;
+typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass;
+
+/**
+ * GMimeFilterReply:
+ * @parent_object: parent #GMimeFilter
+ * @encode: encoding vs decoding reply markers
+ * @saw_nl: previous char was a \n
+ * @saw_angle: previous char was a >
+ *
+ * A filter to insert/remove reply markers (lines beginning with >)
+ **/
+struct _GMimeFilterReply {
+       GMimeFilter parent_object;
+
+       gboolean encode;
+       gboolean saw_nl;
+       gboolean saw_angle;
+};
+
+struct _GMimeFilterReplyClass {
+       GMimeFilterClass parent_class;
+
+};
+
+
+GType g_mime_filter_reply_get_type (void);
+
+GMimeFilter *g_mime_filter_reply_new (gboolean encode);
+
+G_END_DECLS
+
+
+#endif /* _GMIME_FILTER_REPLY_H_ */
diff --git a/hooks.c b/hooks.c
new file mode 100644 (file)
index 0000000..7348d32
--- /dev/null
+++ b/hooks.c
@@ -0,0 +1,96 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2011 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/ .
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#include "notmuch-client.h"
+#include <sys/wait.h>
+
+int
+notmuch_run_hook (const char *db_path, const char *hook)
+{
+    char *hook_path;
+    int status = 0;
+    pid_t pid;
+
+    hook_path = talloc_asprintf (NULL, "%s/%s/%s/%s", db_path, ".notmuch",
+                                "hooks", hook);
+    if (hook_path == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return 1;
+    }
+
+    /* Check access before fork() for speed and simplicity of error handling. */
+    if (access (hook_path, X_OK) == -1) {
+       /* Ignore ENOENT. It's okay not to have a hook, hook dir, or even
+        * notmuch dir. Dangling symbolic links also result in ENOENT, but
+        * we'll ignore that too for simplicity. */
+       if (errno != ENOENT) {
+           fprintf (stderr, "Error: %s hook access failed: %s\n", hook,
+                    strerror (errno));
+           status = 1;
+       }
+       goto DONE;
+    }
+
+    /* Flush any buffered output before forking. */
+    fflush (stdout);
+
+    pid = fork();
+    if (pid == -1) {
+       fprintf (stderr, "Error: %s hook fork failed: %s\n", hook,
+                strerror (errno));
+       status = 1;
+       goto DONE;
+    } else if (pid == 0) {
+       execl (hook_path, hook_path, NULL);
+       /* Same as above for ENOENT, but unlikely now. Indicate all other errors
+        * to parent through non-zero exit status. */
+       if (errno != ENOENT) {
+           fprintf (stderr, "Error: %s hook execution failed: %s\n", hook,
+                    strerror (errno));
+           status = 1;
+       }
+       exit (status);
+    }
+
+    if (waitpid (pid, &status, 0) == -1) {
+       fprintf (stderr, "Error: %s hook wait failed: %s\n", hook,
+                strerror (errno));
+       status = 1;
+       goto DONE;
+    }
+
+    if (!WIFEXITED (status) || WEXITSTATUS (status)) {
+       if (WIFEXITED (status)) {
+           fprintf (stderr, "Error: %s hook failed with status %d\n",
+                    hook, WEXITSTATUS (status));
+       } else if (WIFSIGNALED (status)) {
+           fprintf (stderr, "Error: %s hook terminated with signal %d\n",
+                    hook, WTERMSIG (status));
+       }
+       status = 1;
+    }
+
+  DONE:
+    talloc_free (hook_path);
+
+    return status;
+}
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/lib/Makefile.local b/lib/Makefile.local
new file mode 100644 (file)
index 0000000..5dc057c
--- /dev/null
@@ -0,0 +1,92 @@
+# -*- makefile -*-
+
+dir := lib
+
+# The (often-reused) $dir works fine within targets/prerequisites,
+# but cannot be used reliably within commands, so copy its value to a
+# variable that is not reused.
+lib := $(dir)
+
+ifeq ($(PLATFORM),MACOSX)
+LIBRARY_SUFFIX = dylib
+# On OS X, library version numbers go before suffix.
+LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
+SONAME = libnotmuch.$(LIBNOTMUCH_VERSION_MAJOR).$(LIBRARY_SUFFIX)
+LIBNAME = libnotmuch.$(LIBNOTMUCH_VERSION_MAJOR).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE).$(LIBRARY_SUFFIX)
+LIBRARY_LINK_FLAG = -dynamiclib -install_name $(libdir)/$(SONAME) -compatibility_version $(LIBNOTMUCH_VERSION_MAJOR).$(LIBNOTMUCH_VERSION_MINOR) -current_version $(LIBNOTMUCH_VERSION_MAJOR).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
+else
+LIBRARY_SUFFIX = so
+LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
+SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR)
+LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
+LIBRARY_LINK_FLAG = -shared -Wl,--version-script=$(srcdir)/$(lib)/notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
+ifeq ($(PLATFORM),OPENBSD)
+LIBRARY_LINK_FLAG += -lc
+endif
+ifeq ($(LIBDIR_IN_LDCONFIG),1)
+ifeq ($(DESTDIR),)
+LIBRARY_INSTALL_POST_COMMAND=ldconfig
+endif
+endif
+endif
+
+extra_cflags += -I$(srcdir)/$(dir) -fPIC -fvisibility=hidden
+extra_cxxflags += -fvisibility-inlines-hidden
+
+libnotmuch_c_srcs =            \
+       $(notmuch_compat_srcs)  \
+       $(dir)/filenames.c      \
+       $(dir)/string-list.c    \
+       $(dir)/message-file.c   \
+       $(dir)/message-id.c     \
+       $(dir)/messages.c       \
+       $(dir)/sha1.c           \
+       $(dir)/built-with.c     \
+       $(dir)/string-map.c     \
+       $(dir)/indexopts.c      \
+       $(dir)/tags.c
+
+libnotmuch_cxx_srcs =          \
+       $(dir)/database.cc      \
+       $(dir)/parse-time-vrp.cc        \
+       $(dir)/directory.cc     \
+       $(dir)/index.cc         \
+       $(dir)/message.cc       \
+       $(dir)/add-message.cc   \
+       $(dir)/message-property.cc \
+       $(dir)/query.cc         \
+       $(dir)/query-fp.cc      \
+       $(dir)/config.cc        \
+       $(dir)/regexp-fields.cc \
+       $(dir)/thread.cc \
+       $(dir)/thread-fp.cc
+
+libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
+
+$(dir)/libnotmuch.a: $(libnotmuch_modules)
+       $(call quiet,AR) rcs $@ $^
+
+$(dir)/$(LIBNAME): $(libnotmuch_modules) util/libnotmuch_util.a parse-time-string/libparse-time-string.a
+       $(call quiet,CXX $(CXXFLAGS)) $(libnotmuch_modules) $(FINAL_LIBNOTMUCH_LDFLAGS) $(LIBRARY_LINK_FLAG) -o $@ util/libnotmuch_util.a parse-time-string/libparse-time-string.a
+
+$(dir)/$(SONAME): $(dir)/$(LIBNAME)
+       ln -sf $(LIBNAME) $@
+
+$(dir)/$(LINKER_NAME): $(dir)/$(SONAME)
+       ln -sf $(LIBNAME) $@
+
+install: install-$(dir)
+
+install-$(dir): $(dir)/$(LIBNAME)
+       mkdir -p "$(DESTDIR)$(libdir)/"
+       install -m0644 "$(lib)/$(LIBNAME)" "$(DESTDIR)$(libdir)/"
+       ln -sf $(LIBNAME) "$(DESTDIR)$(libdir)/$(SONAME)"
+       ln -sf $(LIBNAME) "$(DESTDIR)$(libdir)/$(LINKER_NAME)"
+       mkdir -p "$(DESTDIR)$(includedir)"
+       install -m0644 "$(srcdir)/$(lib)/notmuch.h" "$(DESTDIR)$(includedir)/"
+       $(LIBRARY_INSTALL_POST_COMMAND)
+
+SRCS  := $(SRCS) $(libnotmuch_c_srcs) $(libnotmuch_cxx_srcs)
+CLEAN += $(libnotmuch_modules) $(dir)/$(SONAME) $(dir)/$(LINKER_NAME)
+CLEAN += $(dir)/$(LIBNAME) $(dir)/libnotmuch.a
+CLEAN += $(dir)/notmuch.h.gch
diff --git a/lib/add-message.cc b/lib/add-message.cc
new file mode 100644 (file)
index 0000000..da37032
--- /dev/null
@@ -0,0 +1,608 @@
+#include "database-private.h"
+
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'.
+ *
+ * We explicitly avoid including any reference identical to
+ * 'message_id' in the result (to avoid mass confusion when a single
+ * message references itself cyclically---and yes, mail messages are
+ * not infrequent in the wild that do this---don't ask me why).
+ *
+ * Return the last reference parsed, if it is not equal to message_id.
+ */
+static char *
+parse_references (void *ctx,
+                 const char *message_id,
+                 GHashTable *hash,
+                 const char *refs)
+{
+    char *ref, *last_ref = NULL;
+
+    if (refs == NULL || *refs == '\0')
+       return NULL;
+
+    while (*refs) {
+       ref = _notmuch_message_id_parse (ctx, refs, &refs);
+
+       if (ref && strcmp (ref, message_id)) {
+           g_hash_table_add (hash, ref);
+           last_ref = ref;
+       }
+    }
+
+    /* The return value of this function is used to add a parent
+     * reference to the database.  We should avoid making a message
+     * its own parent, thus the above check.
+     */
+    return talloc_strdup(ctx, last_ref);
+}
+
+static const char *
+_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
+{
+    /* 16 bytes (+ terminator) for hexadecimal representation of
+     * a 64-bit integer. */
+    static char thread_id[17];
+    Xapian::WritableDatabase *db;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+    notmuch->last_thread_id++;
+
+    sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
+
+    db->set_metadata ("last_thread_id", thread_id);
+
+    return thread_id;
+}
+
+static char *
+_get_metadata_thread_id_key (void *ctx, const char *message_id)
+{
+    if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
+       message_id = _notmuch_message_id_compressed (ctx, message_id);
+
+    return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
+                           message_id);
+}
+
+
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+                                     void *ctx,
+                                     const char *message_id,
+                                     const char **thread_id_ret);
+
+
+/* Find the thread ID to which the message with 'message_id' belongs.
+ *
+ * Note: 'thread_id_ret' must not be NULL!
+ * On success '*thread_id_ret' is set to a newly talloced string belonging to
+ * 'ctx'.
+ *
+ * Note: If there is no message in the database with the given
+ * 'message_id' then a new thread_id will be allocated for this
+ * message ID and stored in the database metadata so that the
+ * thread ID can be looked up if the message is added to the database
+ * later.
+ */
+static notmuch_status_t
+_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
+                                 void *ctx,
+                                 const char *message_id,
+                                 const char **thread_id_ret)
+{
+    notmuch_private_status_t status;
+    notmuch_message_t *message;
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
+       return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
+                                                    thread_id_ret);
+
+    /* Look for this message (regular or ghost) */
+    message = _notmuch_message_create_for_message_id (
+       notmuch, message_id, &status);
+    if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+       /* Message exists */
+       *thread_id_ret = talloc_steal (
+           ctx, notmuch_message_get_thread_id (message));
+    } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+       /* Message did not exist.  Give it a fresh thread ID and
+        * populate this message as a ghost message. */
+       *thread_id_ret = talloc_strdup (
+           ctx, _notmuch_database_generate_thread_id (notmuch));
+       if (! *thread_id_ret) {
+           status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       } else {
+           status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
+           if (status == 0)
+               /* Commit the new ghost message */
+               _notmuch_message_sync (message);
+       }
+    } else {
+       /* Create failed. Fall through. */
+    }
+
+    notmuch_message_destroy (message);
+
+    return COERCE_STATUS (status, "Error creating ghost message");
+}
+
+/* Pre-ghost messages _resolve_message_id_to_thread_id */
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+                                     void *ctx,
+                                     const char *message_id,
+                                     const char **thread_id_ret)
+{
+    notmuch_status_t status;
+    notmuch_message_t *message;
+    std::string thread_id_string;
+    char *metadata_key;
+    Xapian::WritableDatabase *db;
+
+    status = notmuch_database_find_message (notmuch, message_id, &message);
+
+    if (status)
+       return status;
+
+    if (message) {
+       *thread_id_ret = talloc_steal (ctx,
+                                      notmuch_message_get_thread_id (message));
+
+       notmuch_message_destroy (message);
+
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* Message has not been seen yet.
+     *
+     * We may have seen a reference to it already, in which case, we
+     * can return the thread ID stored in the metadata. Otherwise, we
+     * generate a new thread ID and store it there.
+     */
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+    thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
+
+    if (thread_id_string.empty()) {
+       *thread_id_ret = talloc_strdup (ctx,
+                                       _notmuch_database_generate_thread_id (notmuch));
+       db->set_metadata (metadata_key, *thread_id_ret);
+    } else {
+       *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
+    }
+
+    talloc_free (metadata_key);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_merge_threads (notmuch_database_t *notmuch,
+               const char *winner_thread_id,
+               const char *loser_thread_id)
+{
+    Xapian::PostingIterator loser, loser_end;
+    notmuch_message_t *message = NULL;
+    notmuch_private_status_t private_status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    _notmuch_database_find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
+
+    for ( ; loser != loser_end; loser++) {
+       message = _notmuch_message_create (notmuch, notmuch,
+                                          *loser, &private_status);
+       if (message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       _notmuch_message_remove_term (message, "thread", loser_thread_id);
+       _notmuch_message_add_term (message, "thread", winner_thread_id);
+       _notmuch_message_sync (message);
+
+       notmuch_message_destroy (message);
+       message = NULL;
+    }
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id)
+{
+    GHashTable *parents = NULL;
+    const char *refs, *in_reply_to, *in_reply_to_message_id, *strict_message_id = NULL;
+    const char *last_ref_message_id, *this_message_id;
+    GList *l, *keys = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    parents = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    _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");
+    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");
+    if (in_reply_to)
+       strict_message_id = _notmuch_message_id_parse_strict (message,
+                                                             in_reply_to);
+
+    in_reply_to_message_id = parse_references (message,
+                                              this_message_id,
+                                              parents, in_reply_to);
+
+    /* For the parent of this message, use
+     * 1) the In-Reply-To header, if it looks sane, otherwise
+     * 2) the last message ID of the References header, if available.
+     * 3) Otherwise, fall back to the first message ID in
+     * the In-Reply-To header.
+     */
+
+    if (strict_message_id) {
+       _notmuch_message_add_term (message, "replyto", strict_message_id);
+    } else if (last_ref_message_id) {
+       _notmuch_message_add_term (message, "replyto",
+                                  last_ref_message_id);
+    } else if (in_reply_to_message_id) {
+       _notmuch_message_add_term (message, "replyto",
+                            in_reply_to_message_id);
+    }
+
+    keys = g_hash_table_get_keys (parents);
+    for (l = keys; l; l = l->next) {
+       char *parent_message_id;
+       const char *parent_thread_id = NULL;
+
+       parent_message_id = (char *) l->data;
+
+       _notmuch_message_add_term (message, "reference",
+                                  parent_message_id);
+
+       ret = _resolve_message_id_to_thread_id (notmuch,
+                                               message,
+                                               parent_message_id,
+                                               &parent_thread_id);
+       if (ret)
+           goto DONE;
+
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, parent_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, parent_thread_id)) {
+           ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
+           if (ret)
+               goto DONE;
+       }
+    }
+
+  DONE:
+    if (keys)
+       g_list_free (keys);
+    if (parents)
+       g_hash_table_unref (parents);
+
+    return ret;
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
+                                           notmuch_message_t *message,
+                                           const char **thread_id)
+{
+    const char *message_id = notmuch_message_get_message_id (message);
+    Xapian::PostingIterator child, children_end;
+    notmuch_message_t *child_message = NULL;
+    const char *child_thread_id;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+
+    _notmuch_database_find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
+
+    for ( ; child != children_end; child++) {
+
+       child_message = _notmuch_message_create (message, notmuch,
+                                                *child, &private_status);
+       if (child_message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       child_thread_id = notmuch_message_get_thread_id (child_message);
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, child_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, child_thread_id)) {
+           _notmuch_message_remove_term (child_message, "reference",
+                                         message_id);
+           _notmuch_message_sync (child_message);
+           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
+           if (ret)
+               goto DONE;
+       }
+
+       notmuch_message_destroy (child_message);
+       child_message = NULL;
+    }
+
+  DONE:
+    if (child_message)
+       notmuch_message_destroy (child_message);
+
+    return ret;
+}
+
+/* Fetch and clear the stored thread_id for message, or NULL if none. */
+static char *
+_consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
+                            notmuch_message_t *message)
+{
+    const char *message_id;
+    std::string stored_id;
+    char *metadata_key;
+
+    message_id = notmuch_message_get_message_id (message);
+    metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+
+    /* Check if we have already seen related messages to this one.
+     * If we have then use the thread_id that we stored at that time.
+     */
+    stored_id = notmuch->xapian_db->get_metadata (metadata_key);
+    if (stored_id.empty ()) {
+       return NULL;
+    } else {
+       Xapian::WritableDatabase *db;
+
+       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+       /* Clear the metadata for this message ID. We don't need it
+        * anymore. */
+       db->set_metadata (metadata_key, "");
+
+       return talloc_strdup (ctx, stored_id.c_str ());
+    }
+}
+
+/* Given a blank or ghost 'message' and its corresponding
+ * 'message_file' link it to existing threads in the database.
+ *
+ * First, if is_ghost, this retrieves the thread ID already stored in
+ * the message (which will be the case if a message was previously
+ * added that referenced this one).  If the message is blank
+ * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
+ * later in this function).  If the database does not support ghost
+ * messages, this checks for a thread ID stored in database metadata
+ * for this message ID.
+ *
+ * Second, we look at 'message_file' and its link-relevant headers
+ * (References and In-Reply-To) for message IDs.
+ *
+ * Finally, we look in the database for existing message that
+ * reference 'message'.
+ *
+ * In all cases, we assign to the current message the first thread ID
+ * found. We will also merge any existing, distinct threads where this
+ * message belongs to both, (which is not uncommon when messages are
+ * processed out of order).
+ *
+ * Finally, if no thread ID has been found through referenced messages, we
+ * call _notmuch_message_generate_thread_id to generate a new thread
+ * ID. This should only happen for new, top-level messages, (no
+ * References or In-Reply-To header in this message, and no previously
+ * added message refers to this message).
+ */
+static notmuch_status_t
+_notmuch_database_link_message (notmuch_database_t *notmuch,
+                               notmuch_message_t *message,
+                               notmuch_message_file_t *message_file,
+                               bool is_ghost)
+{
+    void *local = talloc_new (NULL);
+    notmuch_status_t status;
+    const char *thread_id = NULL;
+
+    /* Check if the message already had a thread ID */
+    if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
+       if (is_ghost)
+           thread_id = notmuch_message_get_thread_id (message);
+    } else {
+       thread_id = _consume_metadata_thread_id (local, notmuch, message);
+       if (thread_id)
+           _notmuch_message_add_term (message, "thread", thread_id);
+    }
+
+    status = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                       message_file,
+                                                       &thread_id);
+    if (status)
+       goto DONE;
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
+       /* In general, it shouldn't be necessary to link children,
+        * since the earlier indexing of those children will have
+        * stored a thread ID for the missing parent.  However, prior
+        * to ghost messages, these stored thread IDs were NOT
+        * rewritten during thread merging (and there was no
+        * performant way to do so), so if indexed children were
+        * pulled into a different thread ID by a merge, it was
+        * necessary to pull them *back* into the stored thread ID of
+        * the parent.  With ghost messages, we just rewrite the
+        * stored thread IDs during merging, so this workaround isn't
+        * necessary. */
+       status = _notmuch_database_link_message_to_children (notmuch, message,
+                                                            &thread_id);
+       if (status)
+           goto DONE;
+    }
+
+    /* If not part of any existing thread, generate a new thread ID. */
+    if (thread_id == NULL) {
+       thread_id = _notmuch_database_generate_thread_id (notmuch);
+
+       _notmuch_message_add_term (message, "thread", thread_id);
+    }
+
+ DONE:
+    talloc_free (local);
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *notmuch,
+                            const char *filename,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_t **message_ret)
+{
+    notmuch_message_file_t *message_file;
+    notmuch_message_t *message = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
+    notmuch_private_status_t private_status;
+    bool is_ghost = false, is_new = false;
+    notmuch_indexopts_t *def_indexopts = NULL;
+
+    const char *date;
+    const char *from, *to, *subject;
+    char *message_id = NULL;
+
+    if (message_ret)
+       *message_ret = NULL;
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    message_file = _notmuch_message_file_open (notmuch, filename);
+    if (message_file == NULL)
+       return NOTMUCH_STATUS_FILE_ERROR;
+
+    /* Adding a message may change many documents.  Do this all
+     * atomically. */
+    ret = notmuch_database_begin_atomic (notmuch);
+    if (ret)
+       goto DONE;
+
+    ret = _notmuch_message_file_get_headers (message_file,
+                                            &from, &subject, &to, &date,
+                                            &message_id);
+    if (ret)
+       goto DONE;
+
+    try {
+       /* Now that we have a message ID, we get a message object,
+        * (which may or may not reference an existing document in the
+        * database). */
+
+       message = _notmuch_message_create_for_message_id (notmuch,
+                                                         message_id,
+                                                         &private_status);
+
+       talloc_free (message_id);
+
+       /* We cannot call notmuch_message_get_flag for a new message */
+       switch (private_status) {
+       case NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND:
+           is_ghost = false;
+           is_new = true;
+           break;
+       case NOTMUCH_PRIVATE_STATUS_SUCCESS:
+           is_ghost = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST);
+           is_new = false;
+           break;
+       default:
+           ret = COERCE_STATUS (private_status,
+                                "Unexpected status value from _notmuch_message_create_for_message_id");
+           goto DONE;
+       }
+
+       _notmuch_message_add_filename (message, filename);
+
+       if (is_new || is_ghost) {
+           _notmuch_message_add_term (message, "type", "mail");
+           if (is_ghost)
+               /* Convert ghost message to a regular message */
+               _notmuch_message_remove_term (message, "type", "ghost");
+       }
+
+       ret = _notmuch_database_link_message (notmuch, message,
+                                                 message_file, is_ghost);
+       if (ret)
+           goto DONE;
+
+       if (is_new || is_ghost)
+           _notmuch_message_set_header_values (message, date, from, subject);
+
+       if (!indexopts) {
+           def_indexopts = notmuch_database_get_default_indexopts (notmuch);
+           indexopts = def_indexopts;
+       }
+
+       ret = _notmuch_message_index_file (message, indexopts, message_file);
+       if (ret)
+           goto DONE;
+
+       if (! is_new && !is_ghost)
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+
+       _notmuch_message_sync (message);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       goto DONE;
+    }
+
+  DONE:
+    if (def_indexopts)
+       notmuch_indexopts_destroy (def_indexopts);
+
+    if (message) {
+       if ((ret == NOTMUCH_STATUS_SUCCESS ||
+            ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
+           *message_ret = message;
+       else
+           notmuch_message_destroy (message);
+    }
+
+    if (message_file)
+       _notmuch_message_file_close (message_file);
+
+    ret2 = notmuch_database_end_atomic (notmuch);
+    if ((ret == NOTMUCH_STATUS_SUCCESS ||
+        ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
+       ret2 != NOTMUCH_STATUS_SUCCESS)
+       ret = ret2;
+
+    return ret;
+}
+
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *notmuch,
+                             const char *filename,
+                             notmuch_message_t **message_ret)
+{
+    return notmuch_database_index_file (notmuch, filename,
+                                       NULL,
+                                       message_ret);
+
+}
diff --git a/lib/built-with.c b/lib/built-with.c
new file mode 100644 (file)
index 0000000..9cffd9f
--- /dev/null
@@ -0,0 +1,38 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch.h"
+#include "notmuch-private.h"
+
+notmuch_bool_t
+notmuch_built_with (const char *name)
+{
+    if (STRNCMP_LITERAL (name, "compact") == 0) {
+       return HAVE_XAPIAN_COMPACT;
+    } else if (STRNCMP_LITERAL (name, "field_processor") == 0) {
+       return HAVE_XAPIAN_FIELD_PROCESSOR;
+    } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
+       return HAVE_XAPIAN_DB_RETRY_LOCK;
+    } else if (STRNCMP_LITERAL (name, "session_key") == 0) {
+       return HAVE_GMIME_SESSION_KEYS;
+    } else {
+       return false;
+    }
+}
diff --git a/lib/config.cc b/lib/config.cc
new file mode 100644 (file)
index 0000000..da71c16
--- /dev/null
@@ -0,0 +1,193 @@
+/* config.cc - API for database metadata
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch.h"
+#include "notmuch-private.h"
+#include "database-private.h"
+
+static const std::string CONFIG_PREFIX = "C";
+
+struct _notmuch_config_list {
+    notmuch_database_t *notmuch;
+    Xapian::TermIterator iterator;
+    char *current_key;
+    char *current_val;
+};
+
+static int
+_notmuch_config_list_destroy (notmuch_config_list_t *list)
+{
+    /* invoke destructor w/o deallocating memory */
+    list->iterator.~TermIterator();
+    return 0;
+}
+
+notmuch_status_t
+notmuch_database_set_config (notmuch_database_t *notmuch,
+                            const char *key,
+                            const char *value)
+{
+    notmuch_status_t status;
+    Xapian::WritableDatabase *db;
+
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+       return status;
+
+    try {
+       db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+       db->set_metadata (CONFIG_PREFIX + key, value);
+    } catch (const Xapian::Error &error) {
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       notmuch->exception_reported = true;
+       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
+                              error.get_msg().c_str());
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_metadata_value (notmuch_database_t *notmuch,
+                const char *key,
+                std::string &value)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    try {
+       value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
+    } catch (const Xapian::Error &error) {
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       notmuch->exception_reported = true;
+       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
+                              error.get_msg().c_str());
+    }
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_get_config (notmuch_database_t *notmuch,
+                            const char *key,
+                            char **value)
+{
+    std::string strval;
+    notmuch_status_t status;
+
+    if (! value)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    status = _metadata_value (notmuch, key, strval);
+    if (status)
+       return status;
+
+    *value = strdup (strval.c_str ());
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_get_config_list (notmuch_database_t *notmuch,
+                                 const char *prefix,
+                                 notmuch_config_list_t **out)
+{
+    notmuch_config_list_t *list = NULL;
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    list = talloc (notmuch, notmuch_config_list_t);
+    if (! list) {
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    talloc_set_destructor (list, _notmuch_config_list_destroy);
+    list->notmuch = notmuch;
+    list->current_key = NULL;
+    list->current_val = NULL;
+
+    try {
+
+       new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
+                                                    (CONFIG_PREFIX + (prefix ? prefix : "")));
+
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
+                              error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    *out = list;
+
+  DONE:
+    if (status && list)
+       talloc_free (list);
+
+    return status;
+}
+
+notmuch_bool_t
+notmuch_config_list_valid (notmuch_config_list_t *metadata)
+{
+    if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
+       return false;
+
+    return true;
+}
+
+const char *
+notmuch_config_list_key (notmuch_config_list_t *list)
+{
+    if (list->current_key)
+       talloc_free (list->current_key);
+
+    list->current_key = talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
+
+    return list->current_key;
+}
+
+const char *
+notmuch_config_list_value (notmuch_config_list_t *list)
+{
+    std::string strval;
+    notmuch_status_t status;
+    const char *key = notmuch_config_list_key (list);
+
+    /* TODO: better error reporting?? */
+    status = _metadata_value (list->notmuch, key, strval);
+    if (status)
+       return NULL;
+
+    if (list->current_val)
+       talloc_free (list->current_val);
+
+    list->current_val = talloc_strdup (list, strval.c_str ());
+    return list->current_val;
+}
+
+void
+notmuch_config_list_move_to_next (notmuch_config_list_t *list)
+{
+    list->iterator++;
+}
+
+void
+notmuch_config_list_destroy (notmuch_config_list_t *list)
+{
+    talloc_free (list);
+}
diff --git a/lib/database-private.h b/lib/database-private.h
new file mode 100644 (file)
index 0000000..a499b25
--- /dev/null
@@ -0,0 +1,255 @@
+/* database-private.h - For peeking into the internals of notmuch_database_t
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_DATABASE_PRIVATE_H
+#define NOTMUCH_DATABASE_PRIVATE_H
+
+/* According to WG14/N1124, a C++ implementation won't provide us a
+ * macro like PRIx64 (which gives a printf format string for
+ * formatting a uint64_t as hexadecimal) unless we define
+ * __STDC_FORMAT_MACROS before including inttypes.h. That's annoying,
+ * but there it is.
+ */
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include "notmuch-private.h"
+
+#ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
+#define XAPIAN_DEPRECATED(D) D
+#endif
+
+#include <xapian.h>
+
+/* Bit masks for _notmuch_database::features.  Features are named,
+ * independent aspects of the database schema.
+ *
+ * A database stores the set of features that it "uses" (implicitly
+ * before database version 3 and explicitly as of version 3).
+ *
+ * A given library version will "recognize" a particular set of
+ * features; if a database uses a feature that the library does not
+ * recognize, the library will refuse to open it.  It is assumed the
+ * set of recognized features grows monotonically over time.  A
+ * library version will "implement" some subset of the recognized
+ * features: some operations may require that the database use (or not
+ * use) some feature, while other operations may support both
+ * databases that use and that don't use some feature.
+ *
+ * On disk, the database stores string names for these features (see
+ * the feature_names array).  These enum bit values are never
+ * persisted to disk and may change freely.
+ */
+enum _notmuch_features {
+    /* If set, file names are stored in "file-direntry" terms.  If
+     * unset, file names are stored in document data.
+     *
+     * Introduced: version 1. */
+    NOTMUCH_FEATURE_FILE_TERMS = 1 << 0,
+
+    /* If set, directory timestamps are stored in documents with
+     * XDIRECTORY terms and relative paths.  If unset, directory
+     * timestamps are stored in documents with XTIMESTAMP terms and
+     * absolute paths.
+     *
+     * Introduced: version 1. */
+    NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1,
+
+    /* If set, the from, subject, and message-id headers are stored in
+     * message document values.  If unset, message documents *may*
+     * have these values, but if the value is empty, it must be
+     * retrieved from the message file.
+     *
+     * Introduced: optional in version 1, required as of version 3.
+     */
+    NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2,
+
+    /* If set, folder terms are boolean and path terms exist.  If
+     * unset, folder terms are probabilistic and stemmed and path
+     * terms do not exist.
+     *
+     * Introduced: version 2. */
+    NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3,
+
+    /* If set, missing messages are stored in ghost mail documents.
+     * If unset, thread IDs of ghost messages are stored as database
+     * metadata instead of in ghost documents.
+     *
+     * Introduced: version 3. */
+    NOTMUCH_FEATURE_GHOSTS = 1 << 4,
+
+
+    /* If set, then the database was created after the introduction of
+     * indexed mime types. If unset, then the database may contain a
+     * mixture of messages with indexed and non-indexed mime types.
+     *
+     * Introduced: version 3. */
+    NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5,
+
+    /* If set, messages store the revision number of the last
+     * modification in NOTMUCH_VALUE_LAST_MOD.
+     *
+     * Introduced: version 3. */
+    NOTMUCH_FEATURE_LAST_MOD = 1 << 6,
+};
+
+/* In C++, a named enum is its own type, so define bitwise operators
+ * on _notmuch_features. */
+inline _notmuch_features
+operator|(_notmuch_features a, _notmuch_features b)
+{
+    return static_cast<_notmuch_features>(
+       static_cast<unsigned>(a) | static_cast<unsigned>(b));
+}
+
+inline _notmuch_features
+operator&(_notmuch_features a, _notmuch_features b)
+{
+    return static_cast<_notmuch_features>(
+       static_cast<unsigned>(a) & static_cast<unsigned>(b));
+}
+
+inline _notmuch_features
+operator~(_notmuch_features a)
+{
+    return static_cast<_notmuch_features>(~static_cast<unsigned>(a));
+}
+
+inline _notmuch_features&
+operator|=(_notmuch_features &a, _notmuch_features b)
+{
+    a = a | b;
+    return a;
+}
+
+inline _notmuch_features&
+operator&=(_notmuch_features &a, _notmuch_features b)
+{
+    a = a & 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_PROCESSOR = 1 << 2,
+} 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 | \
+                                   Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE | \
+                                   Xapian::QueryParser::FLAG_WILDCARD | \
+                                   Xapian::QueryParser::FLAG_PURE_NOT)
+
+struct _notmuch_database {
+    bool exception_reported;
+
+    char *path;
+
+    notmuch_database_mode_t mode;
+    int atomic_nesting;
+    /* true if changes have been made in this atomic section */
+    bool atomic_dirty;
+    Xapian::Database *xapian_db;
+
+    /* Bit mask of features used by this database.  This is a
+     * bitwise-OR of NOTMUCH_FEATURE_* values (above). */
+    enum _notmuch_features features;
+
+    unsigned int last_doc_id;
+    uint64_t last_thread_id;
+
+    /* error reporting; this value persists only until the
+     * next library call. May be NULL */
+    char *status_string;
+
+    /* Highest committed revision number.  Modifications are recorded
+     * under a higher revision number, which can be generated with
+     * notmuch_database_new_revision. */
+    unsigned long revision;
+    const char *uuid;
+
+    /* 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;
+    Xapian::ValueRangeProcessor *last_mod_range_processor;
+};
+
+/* Prior to database version 3, features were implied by the database
+ * version number, so hard-code them for earlier versions. */
+#define NOTMUCH_FEATURES_V0 ((enum _notmuch_features)0)
+#define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \
+                            NOTMUCH_FEATURE_DIRECTORY_DOCS)
+#define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER)
+
+/* Current database features.  If any of these are missing from a
+ * database, request an upgrade.
+ * NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES and
+ * NOTMUCH_FEATURE_INDEXED_MIMETYPES are not included because upgrade
+ * doesn't currently introduce the features (though brand new databases
+ * will have it). */
+#define NOTMUCH_FEATURES_CURRENT \
+    (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \
+     NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS | \
+     NOTMUCH_FEATURE_LAST_MOD)
+
+/* Return the list of terms from the given iterator matching a prefix.
+ * The prefix will be stripped from the strings in the returned list.
+ * The list will be allocated using ctx as the talloc context.
+ *
+ * The function returns NULL on failure.
+ */
+notmuch_string_list_t *
+_notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
+                                        Xapian::TermIterator &end,
+                                        const char *prefix);
+
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+                               const char *prefix_name,
+                               const char *value,
+                               Xapian::PostingIterator *begin,
+                               Xapian::PostingIterator *end);
+#endif
diff --git a/lib/database.cc b/lib/database.cc
new file mode 100644 (file)
index 0000000..9cf8062
--- /dev/null
@@ -0,0 +1,2043 @@
+/* database.cc - The database interfaces of the notmuch mail library
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "database-private.h"
+#include "parse-time-vrp.h"
+#include "query-fp.h"
+#include "thread-fp.h"
+#include "regexp-fields.h"
+#include "string-util.h"
+
+#include <iostream>
+
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <ftw.h>
+
+#include <glib.h> /* g_free, GPtrArray, GHashTable */
+#include <glib-object.h> /* g_type_init */
+
+#include <gmime/gmime.h> /* g_mime_init */
+
+using namespace std;
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+typedef struct {
+    const char *name;
+    const char *prefix;
+    notmuch_field_flag_t flags;
+} prefix_t;
+
+#define NOTMUCH_DATABASE_VERSION 3
+
+#define STRINGIFY(s) _SUB_STRINGIFY(s)
+#define _SUB_STRINGIFY(s) #s
+
+#if HAVE_XAPIAN_DB_RETRY_LOCK
+#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
+#else
+#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
+#endif
+
+/* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
+ *
+ * We currently have three different types of documents (mail, ghost,
+ * and directory) and also some metadata.
+ *
+ * Mail document
+ * -------------
+ * 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:
+ *
+ *     type:   mail
+ *
+ *     id:     Unique ID of mail. This is from the Message-ID header
+ *             if present and not too long (see NOTMUCH_MESSAGE_ID_MAX).
+ *             If it's present and too long, then we use
+ *             "notmuch-sha1-<sha1_sum_of_message_id>".
+ *              If this header is not present, we use
+ *             "notmuch-sha1-<sha1_sum_of_entire_file>".
+ *
+ *     thread: The ID of the thread to which the mail belongs
+ *
+ *     replyto: The ID from the In-Reply-To header of the mail (if any).
+ *
+ *    Multiple terms of given prefix:
+ *
+ *     reference: All message IDs from In-Reply-To and References
+ *                headers in the message.
+ *
+ *     tag:       Any tags associated with this message by the user.
+ *
+ *     file-direntry:  A colon-separated pair of values
+ *                     (INTEGER:STRING), where INTEGER is the
+ *                     document ID of a directory document, and
+ *                     STRING is the name of a file within that
+ *                     directory for this mail message.
+ *
+ *      property:       Has a property with key=value
+ *                 FIXME: if no = is present, should match on any value
+ *
+ *    A mail document also has four values:
+ *
+ *     TIMESTAMP:      The time_t value corresponding to the message's
+ *                     Date header.
+ *
+ *     MESSAGE_ID:     The unique ID of the mail mess (see "id" above)
+ *
+ *     FROM:           The value of the "From" header
+ *
+ *     SUBJECT:        The value of the "Subject" header
+ *
+ *     LAST_MOD:       The revision number as of the last tag or
+ *                     filename change.
+ *
+ * In addition, terms from the content of the message are added with
+ * "from", "to", "attachment", and "subject" prefixes for use by the
+ * user in searching. Similarly, terms from the path of the mail
+ * message are added with "folder" and "path" prefixes. But the
+ * database doesn't really care itself about any of these.
+ *
+ * The data portion of a mail document is empty.
+ *
+ * Ghost mail document [if NOTMUCH_FEATURE_GHOSTS]
+ * -----------------------------------------------
+ * A ghost mail document is like a mail document, but where we don't
+ * have the message content.  These are used to track thread reference
+ * information for messages we haven't received.
+ *
+ * A ghost mail document has type: ghost; id and thread fields that
+ * are identical to the mail document fields; and a MESSAGE_ID value.
+ *
+ * Directory document
+ * ------------------
+ * A directory document is used by a client of the notmuch library to
+ * maintain data necessary to allow for efficient polling of mail
+ * directories.
+ *
+ * All directory documents contain one term:
+ *
+ *     directory:      The directory path (relative to the database path)
+ *                     Or the SHA1 sum of the directory path (if the
+ *                     path itself is too long to fit in a Xapian
+ *                     term).
+ *
+ * And all directory documents for directories other than top-level
+ * directories also contain the following term:
+ *
+ *     directory-direntry: A colon-separated pair of values
+ *                         (INTEGER:STRING), where INTEGER is the
+ *                         document ID of the parent directory
+ *                         document, and STRING is the name of this
+ *                         directory within that parent.
+ *
+ * All directory documents have a single value:
+ *
+ *     TIMESTAMP:      The mtime of the directory (at last scan)
+ *
+ * The data portion of a directory document contains the path of the
+ * directory (relative to the database path).
+ *
+ * Database metadata
+ * -----------------
+ * Xapian allows us to store arbitrary name-value pairs as
+ * "metadata". We currently use the following metadata names with the
+ * given meanings:
+ *
+ *     version         The database schema version, (which is distinct
+ *                     from both the notmuch package version (see
+ *                     notmuch --version) and the libnotmuch library
+ *                     version. The version is stored as an base-10
+ *                     ASCII integer. The initial database version
+ *                     was 1, (though a schema existed before that
+ *                     were no "version" database value existed at
+ *                     all). Successive versions are allocated as
+ *                     changes are made to the database (such as by
+ *                     indexing new fields).
+ *
+ *     features        The set of features supported by this
+ *                     database. This consists of a set of
+ *                     '\n'-separated lines, where each is a feature
+ *                     name, a '\t', and compatibility flags.  If the
+ *                     compatibility flags contain 'w', then the
+ *                     opener must support this feature to safely
+ *                     write this database.  If the compatibility
+ *                     flags contain 'r', then the opener must
+ *                     support this feature to read this database.
+ *                     Introduced in database version 3.
+ *
+ *     last_thread_id  The last thread ID generated. This is stored
+ *                     as a 16-byte hexadecimal ASCII representation
+ *                     of a 64-bit unsigned integer. The first ID
+ *                     generated is 1 and the value will be
+ *                     incremented for each thread ID.
+ *
+ *     C*              metadata keys starting with C indicate
+ *                     configuration data. It can be managed with the
+ *                     n_database_*config* API.  There is a convention
+ *                     of hierarchical keys separated by '.' (e.g.
+ *                     query.notmuch stores the value for the named
+ *                     query 'notmuch'), but it is not enforced by the
+ *                     API.
+ *
+ * Obsolete metadata
+ * -----------------
+ *
+ * If ! NOTMUCH_FEATURE_GHOSTS, there are no ghost mail documents.
+ * Instead, the database has the following additional database
+ * metadata:
+ *
+ *     thread_id_*     A pre-allocated thread ID for a particular
+ *                     message. This is actually an arbitrarily large
+ *                     family of metadata name. Any particular name is
+ *                     formed by concatenating "thread_id_" with a message
+ *                     ID (or the SHA1 sum of a message ID if it is very
+ *                     long---see description of 'id' in the mail
+ *                     document). The value stored is a thread ID.
+ *
+ *                     These thread ID metadata values are stored
+ *                     whenever a message references a parent message
+ *                     that does not yet exist in the database. A
+ *                     thread ID will be allocated and stored, and if
+ *                     the message is later added, the stored thread
+ *                     ID will be used (and the metadata value will
+ *                     be cleared).
+ *
+ *                     Even before a message is added, it's
+ *                     pre-allocated thread ID is useful so that all
+ *                     descendant messages that reference this common
+ *                     parent can be recognized as belonging to the
+ *                     same thread.
+ */
+
+/* With these prefix values we follow the conventions published here:
+ *
+ * https://xapian.org/docs/omega/termprefixes.html
+ *
+ * as much as makes sense. Note that I took some liberty in matching
+ * the reserved prefix values to notmuch concepts, (for example, 'G'
+ * is documented as "newsGroup (or similar entity - e.g. a web forum
+ * name)", for which I think the thread is the closest analogue in
+ * notmuch. This in spite of the fact that we will eventually be
+ * storing mailing-list messages where 'G' for "mailing list name"
+ * might be even a closer analogue. I'm treating the single-character
+ * prefixes preferentially for core notmuch concepts (which will be
+ * nearly universal to all mail messages).
+ */
+
+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 |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "tag",                   "K",            NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "is",                    "K",            NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "id",                    "Q",            NOTMUCH_FIELD_EXTERNAL },
+    { "mid",                   "Q",            NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "path",                  "P",            NOTMUCH_FIELD_EXTERNAL|
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "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:",     NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+    { "date",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "query",                 NULL,           NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROCESSOR },
+#endif
+    { "from",                  "XFROM",        NOTMUCH_FIELD_EXTERNAL |
+                                               NOTMUCH_FIELD_PROBABILISTIC |
+                                               NOTMUCH_FIELD_PROCESSOR },
+    { "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 |
+                                               NOTMUCH_FIELD_PROCESSOR},
+};
+
+static void
+_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    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);
+}
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+static void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
+       Xapian::FieldProcessor *fp;
+
+       if (STRNCMP_LITERAL (prefix->name, "date") == 0)
+           fp = (new DateFieldProcessor())->release ();
+       else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
+           fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+       else if (STRNCMP_LITERAL(prefix->name, "thread") == 0)
+           fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+       else
+           fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
+                                           *notmuch->query_parser, notmuch))->release ();
+
+       /* we treat all field-processor fields as boolean in order to get the raw input */
+       notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
+    } else {
+       _setup_query_field_default (prefix, notmuch);
+    }
+}
+#else
+static inline void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    _setup_query_field_default (prefix, notmuch);
+}
+#endif
+
+const char *
+_find_prefix (const char *name)
+{
+    unsigned int i;
+
+    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);
+
+    return "";
+}
+
+static const struct {
+    /* NOTMUCH_FEATURE_* value. */
+    _notmuch_features value;
+    /* Feature name as it appears in the database.  This name should
+     * be appropriate for displaying to the user if an older version
+     * of notmuch doesn't support this feature. */
+    const char *name;
+    /* Compatibility flags when this feature is declared. */
+    const char *flags;
+} feature_names[] = {
+    { NOTMUCH_FEATURE_FILE_TERMS,
+      "multiple paths per message", "rw" },
+    { NOTMUCH_FEATURE_DIRECTORY_DOCS,
+      "relative directory paths", "rw" },
+    /* Header values are not required for reading a database because a
+     * reader can just refer to the message file. */
+    { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
+      "from/subject/message-ID in database", "w" },
+    { NOTMUCH_FEATURE_BOOL_FOLDER,
+      "exact folder:/path: search", "rw" },
+    { NOTMUCH_FEATURE_GHOSTS,
+      "mail documents for missing messages", "w"},
+    /* Knowledge of the index mime-types are not required for reading
+     * a database because a reader will just be unable to query
+     * them. */
+    { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
+      "indexed MIME types", "w"},
+    { NOTMUCH_FEATURE_LAST_MOD,
+      "modification tracking", "w"},
+};
+
+const char *
+notmuch_status_to_string (notmuch_status_t status)
+{
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+       return "No error occurred";
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+       return "Out of memory";
+    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
+       return "Attempt to write to a read-only database";
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+       return "A Xapian exception occurred";
+    case NOTMUCH_STATUS_FILE_ERROR:
+       return "Something went wrong trying to read or write a file";
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+       return "File is not an email";
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       return "Message ID is identical to a message in database";
+    case NOTMUCH_STATUS_NULL_POINTER:
+       return "Erroneous NULL pointer";
+    case NOTMUCH_STATUS_TAG_TOO_LONG:
+       return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+       return "Unbalanced number of calls to notmuch_message_freeze/thaw";
+    case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
+       return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic";
+    case NOTMUCH_STATUS_UNSUPPORTED_OPERATION:
+       return "Unsupported operation";
+    case NOTMUCH_STATUS_UPGRADE_REQUIRED:
+       return "Operation requires a database upgrade";
+    case NOTMUCH_STATUS_PATH_ERROR:
+       return "Path supplied is illegal for this function";
+    case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
+       return "Crypto protocol missing, malformed, or unintelligible";
+    case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
+       return "Crypto engine initialization failure";
+    case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
+       return "Unknown crypto protocol";
+    default:
+    case NOTMUCH_STATUS_LAST_STATUS:
+       return "Unknown error status value";
+    }
+}
+
+void
+_notmuch_database_log (notmuch_database_t *notmuch,
+                     const char *format,
+                     ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    if (notmuch->status_string)
+       talloc_free (notmuch->status_string);
+
+    notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
+    va_end (va_args);
+}
+
+void
+_notmuch_database_log_append (notmuch_database_t *notmuch,
+                     const char *format,
+                     ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    if (notmuch->status_string)
+       notmuch->status_string = talloc_vasprintf_append (notmuch->status_string, format, va_args);
+    else
+       notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
+
+    va_end (va_args);
+}
+
+static void
+find_doc_ids_for_term (notmuch_database_t *notmuch,
+                      const char *term,
+                      Xapian::PostingIterator *begin,
+                      Xapian::PostingIterator *end)
+{
+    *begin = notmuch->xapian_db->postlist_begin (term);
+
+    *end = notmuch->xapian_db->postlist_end (term);
+}
+
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+                               const char *prefix_name,
+                               const char *value,
+                               Xapian::PostingIterator *begin,
+                               Xapian::PostingIterator *end)
+{
+    char *term;
+
+    term = talloc_asprintf (notmuch, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    find_doc_ids_for_term (notmuch, term, begin, end);
+
+    talloc_free (term);
+}
+
+notmuch_private_status_t
+_notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
+                                     const char *prefix_name,
+                                     const char *value,
+                                     unsigned int *doc_id)
+{
+    Xapian::PostingIterator i, end;
+
+    _notmuch_database_find_doc_ids (notmuch, prefix_name, value, &i, &end);
+
+    if (i == end) {
+       *doc_id = 0;
+       return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+    }
+
+    *doc_id = *i;
+
+#if DEBUG_DATABASE_SANITY
+    i++;
+
+    if (i != end)
+       INTERNAL_ERROR ("Term %s:%s is not unique as expected.\n",
+                       prefix_name, value);
+#endif
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+static Xapian::Document
+find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
+{
+    return notmuch->xapian_db->get_document (doc_id);
+}
+
+/* Generate a compressed version of 'message_id' of the form:
+ *
+ *     notmuch-sha1-<sha1_sum_of_message_id>
+ */
+char *
+_notmuch_message_id_compressed (void *ctx, const char *message_id)
+{
+    char *sha1, *compressed;
+
+    sha1 = _notmuch_sha1_of_string (message_id);
+
+    compressed = talloc_asprintf (ctx, "notmuch-sha1-%s", sha1);
+    free (sha1);
+
+    return compressed;
+}
+
+notmuch_status_t
+notmuch_database_find_message (notmuch_database_t *notmuch,
+                              const char *message_id,
+                              notmuch_message_t **message_ret)
+{
+    notmuch_private_status_t status;
+    unsigned int doc_id;
+
+    if (message_ret == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
+       message_id = _notmuch_message_id_compressed (notmuch, message_id);
+
+    try {
+       status = _notmuch_database_find_unique_doc_id (notmuch, "id",
+                                                      message_id, &doc_id);
+
+       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+           *message_ret = NULL;
+       else {
+           *message_ret = _notmuch_message_create (notmuch, notmuch, doc_id,
+                                                   NULL);
+           if (*message_ret == NULL)
+               return NOTMUCH_STATUS_OUT_OF_MEMORY;
+       }
+
+       return NOTMUCH_STATUS_SUCCESS;
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       *message_ret = NULL;
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+}
+
+notmuch_status_t
+notmuch_database_create (const char *path, notmuch_database_t **database)
+{
+    char *status_string = NULL;
+    notmuch_status_t status;
+
+    status = notmuch_database_create_verbose (path, database,
+                                             &status_string);
+
+    if (status_string) {
+       fputs (status_string, stderr);
+       free (status_string);
+    }
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_create_verbose (const char *path,
+                                notmuch_database_t **database,
+                                char **status_string)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    notmuch_database_t *notmuch = NULL;
+    char *notmuch_path = NULL;
+    char *message = NULL;
+    struct stat st;
+    int err;
+
+    if (path == NULL) {
+       message = strdup ("Error: Cannot create a database for a NULL path.\n");
+       status = NOTMUCH_STATUS_NULL_POINTER;
+       goto DONE;
+    }
+
+    if (path[0] != '/') {
+       message = strdup ("Error: Database path must be absolute.\n");
+       status = NOTMUCH_STATUS_PATH_ERROR;
+       goto DONE;
+    }
+
+    err = stat (path, &st);
+    if (err) {
+       IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
+                               path, strerror (errno)));
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (! S_ISDIR (st.st_mode)) {
+       IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
+                                "Not a directory.\n",
+                                path));
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
+
+    err = mkdir (notmuch_path, 0755);
+
+    if (err) {
+       IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
+                                notmuch_path, strerror (errno)));
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    status = notmuch_database_open_verbose (path,
+                                           NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                           &notmuch, &message);
+    if (status)
+       goto DONE;
+
+    /* Upgrade doesn't add these feature to existing databases, but
+     * new databases have them. */
+    notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
+    notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
+
+    status = notmuch_database_upgrade (notmuch, NULL, NULL);
+    if (status) {
+       notmuch_database_close(notmuch);
+       notmuch = NULL;
+    }
+
+  DONE:
+    if (notmuch_path)
+       talloc_free (notmuch_path);
+
+    if (message) {
+       if (status_string)
+           *status_string = message;
+       else
+           free (message);
+    }
+    if (database)
+       *database = notmuch;
+    else
+       talloc_free (notmuch);
+    return status;
+}
+
+notmuch_status_t
+_notmuch_database_ensure_writable (notmuch_database_t *notmuch)
+{
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+       _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
+       return NOTMUCH_STATUS_READ_ONLY_DATABASE;
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Allocate a revision number for the next change. */
+unsigned long
+_notmuch_database_new_revision (notmuch_database_t *notmuch)
+{
+    unsigned long new_revision = notmuch->revision + 1;
+
+    /* If we're in an atomic section, hold off on updating the
+     * committed revision number until we commit the atomic section.
+     */
+    if (notmuch->atomic_nesting)
+       notmuch->atomic_dirty = true;
+    else
+       notmuch->revision = new_revision;
+
+    return new_revision;
+}
+
+/* Parse a database features string from the given database version.
+ * Returns the feature bit set.
+ *
+ * For version < 3, this ignores the features string and returns a
+ * hard-coded set of features.
+ *
+ * If there are unrecognized features that are required to open the
+ * database in mode (which should be 'r' or 'w'), return a
+ * comma-separated list of unrecognized but required features in
+ * *incompat_out suitable for presenting to the user.  *incompat_out
+ * will be allocated from ctx.
+ */
+static _notmuch_features
+_parse_features (const void *ctx, const char *features, unsigned int version,
+                char mode, char **incompat_out)
+{
+    _notmuch_features res = static_cast<_notmuch_features>(0);
+    unsigned int namelen, i;
+    size_t llen = 0;
+    const char *flags;
+
+    /* Prior to database version 3, features were implied by the
+     * version number. */
+    if (version == 0)
+       return NOTMUCH_FEATURES_V0;
+    else if (version == 1)
+       return NOTMUCH_FEATURES_V1;
+    else if (version == 2)
+       return NOTMUCH_FEATURES_V2;
+
+    /* Parse the features string */
+    while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
+       flags = strchr (features, '\t');
+       if (! flags || flags > features + llen)
+           continue;
+       namelen = flags - features;
+
+       for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
+           if (strlen (feature_names[i].name) == namelen &&
+               strncmp (feature_names[i].name, features, namelen) == 0) {
+               res |= feature_names[i].value;
+               break;
+           }
+       }
+
+       if (i == ARRAY_SIZE (feature_names) && incompat_out) {
+           /* Unrecognized feature */
+           const char *have = strchr (flags, mode);
+           if (have && have < features + llen) {
+               /* This feature is required to access this database in
+                * 'mode', but we don't understand it. */
+               if (! *incompat_out)
+                   *incompat_out = talloc_strdup (ctx, "");
+               *incompat_out = talloc_asprintf_append_buffer (
+                   *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
+                   namelen, features);
+           }
+       }
+    }
+
+    return res;
+}
+
+static char *
+_print_features (const void *ctx, unsigned int features)
+{
+    unsigned int i;
+    char *res = talloc_strdup (ctx, "");
+
+    for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
+       if (features & feature_names[i].value)
+           res = talloc_asprintf_append_buffer (
+               res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
+
+    return res;
+}
+
+notmuch_status_t
+notmuch_database_open (const char *path,
+                      notmuch_database_mode_t mode,
+                      notmuch_database_t **database)
+{
+    char *status_string = NULL;
+    notmuch_status_t status;
+
+    status = notmuch_database_open_verbose (path, mode, database,
+                                          &status_string);
+
+    if (status_string) {
+       fputs (status_string, stderr);
+       free (status_string);
+    }
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_open_verbose (const char *path,
+                              notmuch_database_mode_t mode,
+                              notmuch_database_t **database,
+                              char **status_string)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    void *local = talloc_new (NULL);
+    notmuch_database_t *notmuch = NULL;
+    char *notmuch_path, *xapian_path, *incompat_features;
+    char *message = NULL;
+    struct stat st;
+    int err;
+    unsigned int i, version;
+    static int initialized = 0;
+
+    if (path == NULL) {
+       message = strdup ("Error: Cannot open a database for a NULL path.\n");
+       status = NOTMUCH_STATUS_NULL_POINTER;
+       goto DONE;
+    }
+
+    if (path[0] != '/') {
+       message = strdup ("Error: Database path must be absolute.\n");
+       status = NOTMUCH_STATUS_PATH_ERROR;
+       goto DONE;
+    }
+
+    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
+       message = strdup ("Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    err = stat (notmuch_path, &st);
+    if (err) {
+       IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
+                                notmuch_path, strerror (errno)));
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
+       message = strdup ("Out of memory\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    /* Initialize the GLib type system and threads */
+#if !GLIB_CHECK_VERSION(2, 35, 1)
+    g_type_init ();
+#endif
+
+    /* Initialize gmime */
+    if (! initialized) {
+       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+       initialized = 1;
+    }
+
+    notmuch = talloc_zero (NULL, notmuch_database_t);
+    notmuch->exception_reported = false;
+    notmuch->status_string = NULL;
+    notmuch->path = talloc_strdup (notmuch, path);
+
+    strip_trailing(notmuch->path, '/');
+
+    notmuch->mode = mode;
+    notmuch->atomic_nesting = 0;
+    notmuch->view = 1;
+    try {
+       string last_thread_id;
+       string last_mod;
+
+       if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
+           notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
+                                                              DB_ACTION);
+       } else {
+           notmuch->xapian_db = new Xapian::Database (xapian_path);
+       }
+
+       /* Check version.  As of database version 3, we represent
+        * changes in terms of features, so assume a version bump
+        * means a dramatically incompatible change. */
+       version = notmuch_database_get_version (notmuch);
+       if (version > NOTMUCH_DATABASE_VERSION) {
+           IGNORE_RESULT (asprintf (&message,
+                     "Error: Notmuch database at %s\n"
+                     "       has a newer database format version (%u) than supported by this\n"
+                     "       version of notmuch (%u).\n",
+                                    notmuch_path, version, NOTMUCH_DATABASE_VERSION));
+           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+           notmuch_database_destroy (notmuch);
+           notmuch = NULL;
+           status = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       /* Check features. */
+       incompat_features = NULL;
+       notmuch->features = _parse_features (
+           local, notmuch->xapian_db->get_metadata ("features").c_str (),
+           version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
+           &incompat_features);
+       if (incompat_features) {
+           IGNORE_RESULT (asprintf (&message,
+               "Error: Notmuch database at %s\n"
+               "       requires features (%s)\n"
+               "       not supported by this version of notmuch.\n",
+                                    notmuch_path, incompat_features));
+           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+           notmuch_database_destroy (notmuch);
+           notmuch = NULL;
+           status = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
+       last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
+       if (last_thread_id.empty ()) {
+           notmuch->last_thread_id = 0;
+       } else {
+           const char *str;
+           char *end;
+
+           str = last_thread_id.c_str ();
+           notmuch->last_thread_id = strtoull (str, &end, 16);
+           if (*end != '\0')
+               INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
+       }
+
+       /* Get current highest revision number. */
+       last_mod = notmuch->xapian_db->get_value_upper_bound (
+           NOTMUCH_VALUE_LAST_MOD);
+       if (last_mod.empty ())
+           notmuch->revision = 0;
+       else
+           notmuch->revision = Xapian::sortable_unserialise (last_mod);
+       notmuch->uuid = talloc_strdup (
+           notmuch, notmuch->xapian_db->get_uuid ().c_str ());
+
+       notmuch->query_parser = new Xapian::QueryParser;
+       notmuch->term_gen = new Xapian::TermGenerator;
+       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+       notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+       notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+       notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
+
+       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
+       notmuch->query_parser->set_database (*notmuch->xapian_db);
+       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+       notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
+       notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
+       notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
+
+       for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+           const prefix_t *prefix = &prefix_table[i];
+           if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
+               _setup_query_field (prefix, notmuch);
+           }
+       }
+    } catch (const Xapian::Error &error) {
+       IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
+                                error.get_msg().c_str()));
+       notmuch_database_destroy (notmuch);
+       notmuch = NULL;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+  DONE:
+    talloc_free (local);
+
+    if (message) {
+       if (status_string)
+           *status_string = message;
+       else
+           free (message);
+    }
+
+    if (database)
+       *database = notmuch;
+    else
+       talloc_free (notmuch);
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_close (notmuch_database_t *notmuch)
+{
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    /* Many Xapian objects (and thus notmuch objects) hold references to
+     * the database, so merely deleting the database may not suffice to
+     * close it.  Thus, we explicitly close it here. */
+    if (notmuch->xapian_db != NULL) {
+       try {
+           /* If there's an outstanding transaction, it's unclear if
+            * closing the Xapian database commits everything up to
+            * that transaction, or may discard committed (but
+            * unflushed) transactions.  To be certain, explicitly
+            * cancel any outstanding transaction before closing. */
+           if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
+               notmuch->atomic_nesting)
+               (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))
+                   ->cancel_transaction ();
+
+           /* Close the database.  This implicitly flushes
+            * outstanding changes. */
+           notmuch->xapian_db->close();
+       } catch (const Xapian::Error &error) {
+           status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+           if (! notmuch->exception_reported) {
+               _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
+                        error.get_msg().c_str());
+           }
+       }
+    }
+
+    delete notmuch->term_gen;
+    notmuch->term_gen = NULL;
+    delete notmuch->query_parser;
+    notmuch->query_parser = NULL;
+    delete notmuch->xapian_db;
+    notmuch->xapian_db = NULL;
+    delete notmuch->value_range_processor;
+    notmuch->value_range_processor = NULL;
+    delete notmuch->date_range_processor;
+    notmuch->date_range_processor = NULL;
+    delete notmuch->last_mod_range_processor;
+    notmuch->last_mod_range_processor = NULL;
+
+    return status;
+}
+
+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),
+          unused (int type),
+          unused (struct FTW *ftw))
+{
+    return remove (path);
+}
+
+static int
+rmtree (const char *path)
+{
+    return nftw (path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
+}
+
+class NotmuchCompactor : public Xapian::Compactor
+{
+    notmuch_compact_status_cb_t status_cb;
+    void *status_closure;
+
+public:
+    NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
+       status_cb (cb), status_closure (closure) { }
+
+    virtual void
+    set_status (const std::string &table, const std::string &status)
+    {
+       char *msg;
+
+       if (status_cb == NULL)
+           return;
+
+       if (status.length () == 0)
+           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str());
+       else
+           msg = talloc_asprintf (NULL, "     %s", status.c_str());
+
+       if (msg == NULL) {
+           return;
+       }
+
+       status_cb (msg, status_closure);
+       talloc_free (msg);
+    }
+};
+
+/* Compacts the given database, optionally saving the original database
+ * in backup_path. Additionally, a callback function can be provided to
+ * give the user feedback on the progress of the (likely long-lived)
+ * compaction process.
+ *
+ * The backup path must point to a directory on the same volume as the
+ * original database. Passing a NULL backup_path will result in the
+ * uncompacted database being deleted after compaction has finished.
+ * Note that the database write lock will be held during the
+ * compaction process to protect data integrity.
+ */
+notmuch_status_t
+notmuch_database_compact (const char *path,
+                         const char *backup_path,
+                         notmuch_compact_status_cb_t status_cb,
+                         void *closure)
+{
+    void *local;
+    char *notmuch_path, *xapian_path, *compact_xapian_path;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_database_t *notmuch = NULL;
+    struct stat statbuf;
+    bool keep_backup;
+    char *message = NULL;
+
+    local = talloc_new (NULL);
+    if (! local)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    ret = notmuch_database_open_verbose (path,
+                                        NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                        &notmuch,
+                                        &message);
+    if (ret) {
+       if (status_cb) status_cb (message, closure);
+       goto DONE;
+    }
+
+    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
+       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    if (backup_path == NULL) {
+       if (! (backup_path = talloc_asprintf (local, "%s.old", xapian_path))) {
+           ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+           goto DONE;
+       }
+       keep_backup = false;
+    }
+    else {
+       keep_backup = true;
+    }
+
+    if (stat (backup_path, &statbuf) != -1) {
+       _notmuch_database_log (notmuch, "Path already exists: %s\n", backup_path);
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+    if (errno != ENOENT) {
+       _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n",
+                strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    /* Unconditionally attempt to remove old work-in-progress database (if
+     * any). This is "protected" by database lock. If this fails due to write
+     * errors (etc), the following code will fail and provide error message.
+     */
+    (void) rmtree (compact_xapian_path);
+
+    try {
+       NotmuchCompactor compactor (status_cb, closure);
+
+       compactor.set_renumber (false);
+       compactor.add_source (xapian_path);
+       compactor.set_destdir (compact_xapian_path);
+       compactor.compact ();
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg().c_str());
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       goto DONE;
+    }
+
+    if (rename (xapian_path, backup_path)) {
+       _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
+                xapian_path, backup_path, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (rename (compact_xapian_path, xapian_path)) {
+       _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
+                compact_xapian_path, xapian_path, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (! keep_backup) {
+       if (rmtree (backup_path)) {
+           _notmuch_database_log (notmuch, "Error removing old database %s: %s\n",
+                    backup_path, strerror (errno));
+           ret = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+    }
+
+  DONE:
+    if (notmuch) {
+       notmuch_status_t ret2;
+
+       const char *str = notmuch_database_status_string (notmuch);
+       if (status_cb && str)
+           status_cb (str, closure);
+
+       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);
+
+    return ret;
+}
+
+notmuch_status_t
+notmuch_database_destroy (notmuch_database_t *notmuch)
+{
+    notmuch_status_t status;
+
+    status = notmuch_database_close (notmuch);
+    talloc_free (notmuch);
+
+    return status;
+}
+
+const char *
+notmuch_database_get_path (notmuch_database_t *notmuch)
+{
+    return notmuch->path;
+}
+
+unsigned int
+notmuch_database_get_version (notmuch_database_t *notmuch)
+{
+    unsigned int version;
+    string version_string;
+    const char *str;
+    char *end;
+
+    version_string = notmuch->xapian_db->get_metadata ("version");
+    if (version_string.empty ())
+       return 0;
+
+    str = version_string.c_str ();
+    if (str == NULL || *str == '\0')
+       return 0;
+
+    version = strtoul (str, &end, 10);
+    if (*end != '\0')
+       INTERNAL_ERROR ("Malformed database version: %s", str);
+
+    return version;
+}
+
+notmuch_bool_t
+notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
+{
+    return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
+       ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
+        (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
+}
+
+static volatile sig_atomic_t do_progress_notify = 0;
+
+static void
+handle_sigalrm (unused (int signal))
+{
+    do_progress_notify = 1;
+}
+
+/* Upgrade the current database.
+ *
+ * After opening a database in read-write mode, the client should
+ * check if an upgrade is needed (notmuch_database_needs_upgrade) and
+ * if so, upgrade with this function before making any modifications.
+ *
+ * The optional progress_notify callback can be used by the caller to
+ * provide progress indication to the user. If non-NULL it will be
+ * called periodically with 'count' as the number of messages upgraded
+ * so far and 'total' the overall number of messages that will be
+ * converted.
+ */
+notmuch_status_t
+notmuch_database_upgrade (notmuch_database_t *notmuch,
+                         void (*progress_notify) (void *closure,
+                                                  double progress),
+                         void *closure)
+{
+    void *local = talloc_new (NULL);
+    Xapian::TermIterator t, t_end;
+    Xapian::WritableDatabase *db;
+    struct sigaction action;
+    struct itimerval timerval;
+    bool timer_is_active = false;
+    enum _notmuch_features target_features, new_features;
+    notmuch_status_t status;
+    notmuch_private_status_t private_status;
+    notmuch_query_t *query = NULL;
+    unsigned int count = 0, total = 0;
+
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+       return status;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+    target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
+    new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
+
+    if (! notmuch_database_needs_upgrade (notmuch))
+       return NOTMUCH_STATUS_SUCCESS;
+
+    if (progress_notify) {
+       /* Set up our handler for SIGALRM */
+       memset (&action, 0, sizeof (struct sigaction));
+       action.sa_handler = handle_sigalrm;
+       sigemptyset (&action.sa_mask);
+       action.sa_flags = SA_RESTART;
+       sigaction (SIGALRM, &action, NULL);
+
+       /* Then start a timer to send SIGALRM once per second. */
+       timerval.it_interval.tv_sec = 1;
+       timerval.it_interval.tv_usec = 0;
+       timerval.it_value.tv_sec = 1;
+       timerval.it_value.tv_usec = 0;
+       setitimer (ITIMER_REAL, &timerval, NULL);
+
+       timer_is_active = true;
+    }
+
+    /* Figure out how much total work we need to do. */
+    if (new_features &
+       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+        NOTMUCH_FEATURE_LAST_MOD)) {
+       query = notmuch_query_create (notmuch, "");
+       unsigned msg_count;
+
+       status = notmuch_query_count_messages (query, &msg_count);
+       if (status)
+           goto DONE;
+
+       total += msg_count;
+       notmuch_query_destroy (query);
+       query = NULL;
+    }
+    if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
+       t_end = db->allterms_end ("XTIMESTAMP");
+       for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++)
+           ++total;
+    }
+    if (new_features & NOTMUCH_FEATURE_GHOSTS) {
+       /* The ghost message upgrade converts all thread_id_*
+        * metadata values into ghost message documents. */
+       t_end = db->metadata_keys_end ("thread_id_");
+       for (t = db->metadata_keys_begin ("thread_id_"); t != t_end; ++t)
+           ++total;
+    }
+
+    /* Perform the upgrade in a transaction. */
+    db->begin_transaction (true);
+
+    /* Set the target features so we write out changes in the desired
+     * format. */
+    notmuch->features = target_features;
+
+    /* Perform per-message upgrades. */
+    if (new_features &
+       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |
+        NOTMUCH_FEATURE_LAST_MOD)) {
+       notmuch_messages_t *messages;
+       notmuch_message_t *message;
+       char *filename;
+
+       query = notmuch_query_create (notmuch, "");
+
+       status = notmuch_query_search_messages (query, &messages);
+       if (status)
+           goto DONE;
+       for (;
+            notmuch_messages_valid (messages);
+            notmuch_messages_move_to_next (messages))
+       {
+           if (do_progress_notify) {
+               progress_notify (closure, (double) count / total);
+               do_progress_notify = 0;
+           }
+
+           message = notmuch_messages_get (messages);
+
+           /* Before version 1, each message document had its
+            * filename in the data field. Copy that into the new
+            * format by calling notmuch_message_add_filename.
+            */
+           if (new_features & NOTMUCH_FEATURE_FILE_TERMS) {
+               filename = _notmuch_message_talloc_copy_data (message);
+               if (filename && *filename != '\0') {
+                   _notmuch_message_add_filename (message, filename);
+                   _notmuch_message_clear_data (message);
+               }
+               talloc_free (filename);
+           }
+
+           /* Prior to version 2, the "folder:" prefix was
+            * probabilistic and stemmed. Change it to the current
+            * boolean prefix. Add "path:" prefixes while at it.
+            */
+           if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)
+               _notmuch_message_upgrade_folder (message);
+
+           /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not
+            * track modification revisions.  Give all messages the
+            * next available revision; since we just started tracking
+            * revisions for this database, that will be 1.
+            */
+           if (new_features & NOTMUCH_FEATURE_LAST_MOD)
+               _notmuch_message_upgrade_last_mod (message);
+
+           _notmuch_message_sync (message);
+
+           notmuch_message_destroy (message);
+
+           count++;
+       }
+
+       notmuch_query_destroy (query);
+       query = NULL;
+    }
+
+    /* Perform per-directory upgrades. */
+
+    /* Before version 1 we stored directory timestamps in
+     * XTIMESTAMP documents instead of the current XDIRECTORY
+     * documents. So copy those as well. */
+    if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) {
+       t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP");
+
+       for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
+            t != t_end;
+            t++)
+       {
+           Xapian::PostingIterator p, p_end;
+           std::string term = *t;
+
+           p_end = notmuch->xapian_db->postlist_end (term);
+
+           for (p = notmuch->xapian_db->postlist_begin (term);
+                p != p_end;
+                p++)
+           {
+               Xapian::Document document;
+               time_t mtime;
+               notmuch_directory_t *directory;
+
+               if (do_progress_notify) {
+                   progress_notify (closure, (double) count / total);
+                   do_progress_notify = 0;
+               }
+
+               document = find_document_for_doc_id (notmuch, *p);
+               mtime = Xapian::sortable_unserialise (
+                   document.get_value (NOTMUCH_VALUE_TIMESTAMP));
+
+               directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
+                                                      NOTMUCH_FIND_CREATE, &status);
+               notmuch_directory_set_mtime (directory, mtime);
+               notmuch_directory_destroy (directory);
+
+               db->delete_document (*p);
+           }
+
+           ++count;
+       }
+    }
+
+    /* Perform metadata upgrades. */
+
+    /* Prior to NOTMUCH_FEATURE_GHOSTS, thread IDs for missing
+     * messages were stored as database metadata. Change these to
+     * ghost messages.
+     */
+    if (new_features & NOTMUCH_FEATURE_GHOSTS) {
+       notmuch_message_t *message;
+       std::string message_id, thread_id;
+
+       t_end = db->metadata_keys_end (NOTMUCH_METADATA_THREAD_ID_PREFIX);
+       for (t = db->metadata_keys_begin (NOTMUCH_METADATA_THREAD_ID_PREFIX);
+            t != t_end; ++t) {
+           if (do_progress_notify) {
+               progress_notify (closure, (double) count / total);
+               do_progress_notify = 0;
+           }
+
+           message_id = (*t).substr (
+               strlen (NOTMUCH_METADATA_THREAD_ID_PREFIX));
+           thread_id = db->get_metadata (*t);
+
+           /* Create ghost message */
+           message = _notmuch_message_create_for_message_id (
+               notmuch, message_id.c_str (), &private_status);
+           if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+               /* Document already exists; ignore the stored thread ID */
+           } else if (private_status ==
+                      NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+               private_status = _notmuch_message_initialize_ghost (
+                   message, thread_id.c_str ());
+               if (! private_status)
+                   _notmuch_message_sync (message);
+           }
+
+           if (private_status) {
+               _notmuch_database_log (notmuch,
+                        "Upgrade failed while creating ghost messages.\n");
+               status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
+               goto DONE;
+           }
+
+           /* Clear saved metadata thread ID */
+           db->set_metadata (*t, "");
+
+           ++count;
+       }
+    }
+
+    status = NOTMUCH_STATUS_SUCCESS;
+    db->set_metadata ("features", _print_features (local, notmuch->features));
+    db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
+
+ DONE:
+    if (status == NOTMUCH_STATUS_SUCCESS)
+       db->commit_transaction ();
+    else
+       db->cancel_transaction ();
+
+    if (timer_is_active) {
+       /* Now stop the timer. */
+       timerval.it_interval.tv_sec = 0;
+       timerval.it_interval.tv_usec = 0;
+       timerval.it_value.tv_sec = 0;
+       timerval.it_value.tv_usec = 0;
+       setitimer (ITIMER_REAL, &timerval, NULL);
+
+       /* And disable the signal handler. */
+       action.sa_handler = SIG_IGN;
+       sigaction (SIGALRM, &action, NULL);
+    }
+
+    if (query)
+       notmuch_query_destroy (query);
+
+    talloc_free (local);
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_begin_atomic (notmuch_database_t *notmuch)
+{
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+       notmuch->atomic_nesting > 0)
+       goto DONE;
+
+    if (notmuch_database_needs_upgrade (notmuch))
+       return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+    try {
+       (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+DONE:
+    notmuch->atomic_nesting++;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_end_atomic (notmuch_database_t *notmuch)
+{
+    Xapian::WritableDatabase *db;
+
+    if (notmuch->atomic_nesting == 0)
+       return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
+
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+       notmuch->atomic_nesting > 1)
+       goto DONE;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    try {
+       db->commit_transaction ();
+
+       /* This is a hack for testing.  Xapian never flushes on a
+        * non-flushed commit, even if the flush threshold is 1.
+        * However, we rely on flushing to test atomicity. */
+       const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
+       if (thresh && atoi (thresh) == 1)
+           db->commit ();
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    if (notmuch->atomic_dirty) {
+       ++notmuch->revision;
+       notmuch->atomic_dirty = false;
+    }
+
+DONE:
+    notmuch->atomic_nesting--;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+unsigned long
+notmuch_database_get_revision (notmuch_database_t *notmuch,
+                               const char **uuid)
+{
+    if (uuid)
+       *uuid = notmuch->uuid;
+    return notmuch->revision;
+}
+
+/* We allow the user to use arbitrarily long paths for directories. But
+ * we have a term-length limit. So if we exceed that, we'll use the
+ * SHA-1 of the path for the database term.
+ *
+ * Note: This function may return the original value of 'path'. If it
+ * does not, then the caller is responsible to free() the returned
+ * value.
+ */
+const char *
+_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);
+    else
+       return path;
+}
+
+/* Given a path, split it into two parts: the directory part is all
+ * components except for the last, and the basename is that last
+ * component. Getting the return-value for either part is optional
+ * (the caller can pass NULL).
+ *
+ * The original 'path' can represent either a regular file or a
+ * directory---the splitting will be carried out in the same way in
+ * either case. Trailing slashes on 'path' will be ignored, and any
+ * cases of multiple '/' characters appearing in series will be
+ * treated as a single '/'.
+ *
+ * Allocation (if any) will have 'ctx' as the talloc owner. But
+ * pointers will be returned within the original path string whenever
+ * possible.
+ *
+ * Note: If 'path' is non-empty and contains no non-trailing slash,
+ * (that is, consists of a filename with no parent directory), then
+ * the directory returned will be an empty string. However, if 'path'
+ * is an empty string, then both directory and basename will be
+ * returned as NULL.
+ */
+notmuch_status_t
+_notmuch_database_split_path (void *ctx,
+                             const char *path,
+                             const char **directory,
+                             const char **basename)
+{
+    const char *slash;
+
+    if (path == NULL || *path == '\0') {
+       if (directory)
+           *directory = NULL;
+       if (basename)
+           *basename = NULL;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* Find the last slash (not counting a trailing slash), if any. */
+
+    slash = path + strlen (path) - 1;
+
+    /* First, skip trailing slashes. */
+    while (slash != path && *slash == '/')
+       --slash;
+
+    /* Then, find a slash. */
+    while (slash != path && *slash != '/') {
+       if (basename)
+           *basename = slash;
+
+       --slash;
+    }
+
+    /* Finally, skip multiple slashes. */
+    while (slash != path && *(slash - 1) == '/')
+       --slash;
+
+    if (slash == path) {
+       if (directory)
+           *directory = talloc_strdup (ctx, "");
+       if (basename)
+           *basename = path;
+    } else {
+       if (directory)
+           *directory = talloc_strndup (ctx, path, slash - path);
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Find the document ID of the specified directory.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), a new directory document will be
+ * created if one does not exist for 'path'.  Otherwise, if the
+ * directory document does not exist, this sets *directory_id to
+ * ((unsigned int)-1) and returns NOTMUCH_STATUS_SUCCESS.
+ */
+notmuch_status_t
+_notmuch_database_find_directory_id (notmuch_database_t *notmuch,
+                                    const char *path,
+                                    notmuch_find_flags_t flags,
+                                    unsigned int *directory_id)
+{
+    notmuch_directory_t *directory;
+    notmuch_status_t status;
+
+    if (path == NULL) {
+       *directory_id = 0;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    directory = _notmuch_directory_create (notmuch, path, flags, &status);
+    if (status || !directory) {
+       *directory_id = -1;
+       return status;
+    }
+
+    *directory_id = _notmuch_directory_get_document_id (directory);
+
+    notmuch_directory_destroy (directory);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+const char *
+_notmuch_database_get_directory_path (void *ctx,
+                                     notmuch_database_t *notmuch,
+                                     unsigned int doc_id)
+{
+    Xapian::Document document;
+
+    document = find_document_for_doc_id (notmuch, doc_id);
+
+    return talloc_strdup (ctx, document.get_data ().c_str ());
+}
+
+/* Given a legal 'filename' for the database, (either relative to
+ * database path or absolute with initial components identical to
+ * database path), return a new string (with 'ctx' as the talloc
+ * owner) suitable for use as a direntry term value.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), the necessary directory documents
+ * will be created in the database as needed.  Otherwise, if the
+ * necessary directory documents do not exist, this sets
+ * *direntry to NULL and returns NOTMUCH_STATUS_SUCCESS.
+ */
+notmuch_status_t
+_notmuch_database_filename_to_direntry (void *ctx,
+                                       notmuch_database_t *notmuch,
+                                       const char *filename,
+                                       notmuch_find_flags_t flags,
+                                       char **direntry)
+{
+    const char *relative, *directory, *basename;
+    Xapian::docid directory_id;
+    notmuch_status_t status;
+
+    relative = _notmuch_database_relative_path (notmuch, filename);
+
+    status = _notmuch_database_split_path (ctx, relative,
+                                          &directory, &basename);
+    if (status)
+       return status;
+
+    status = _notmuch_database_find_directory_id (notmuch, directory, flags,
+                                                 &directory_id);
+    if (status || directory_id == (unsigned int)-1) {
+       *direntry = NULL;
+       return status;
+    }
+
+    *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Given a legal 'path' for the database, return the relative path.
+ *
+ * The return value will be a pointer to the original path contents,
+ * and will be either the original string (if 'path' was relative) or
+ * a portion of the string (if path was absolute and begins with the
+ * database path).
+ */
+const char *
+_notmuch_database_relative_path (notmuch_database_t *notmuch,
+                                const char *path)
+{
+    const char *db_path, *relative;
+    unsigned int db_path_len;
+
+    db_path = notmuch_database_get_path (notmuch);
+    db_path_len = strlen (db_path);
+
+    relative = path;
+
+    if (*relative == '/') {
+       while (*relative == '/' && *(relative+1) == '/')
+           relative++;
+
+       if (strncmp (relative, db_path, db_path_len) == 0)
+       {
+           relative += db_path_len;
+           while (*relative == '/')
+               relative++;
+       }
+    }
+
+    return relative;
+}
+
+notmuch_status_t
+notmuch_database_get_directory (notmuch_database_t *notmuch,
+                               const char *path,
+                               notmuch_directory_t **directory)
+{
+    notmuch_status_t status;
+
+    if (directory == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+    *directory = NULL;
+
+    try {
+       *directory = _notmuch_directory_create (notmuch, path,
+                                               NOTMUCH_FIND_LOOKUP, &status);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+    return status;
+}
+
+/* Allocate a document ID that satisfies the following criteria:
+ *
+ * 1. The ID does not exist for any document in the Xapian database
+ *
+ * 2. The ID was not previously returned from this function
+ *
+ * 3. The ID is the smallest integer satisfying (1) and (2)
+ *
+ * This function will trigger an internal error if these constraints
+ * cannot all be satisfied, (that is, the pool of available document
+ * IDs has been exhausted).
+ */
+unsigned int
+_notmuch_database_generate_doc_id (notmuch_database_t *notmuch)
+{
+    assert (notmuch->last_doc_id >= notmuch->xapian_db->get_lastdocid ());
+
+    notmuch->last_doc_id++;
+
+    if (notmuch->last_doc_id == 0)
+       INTERNAL_ERROR ("Xapian document IDs are exhausted.\n");
+
+    return notmuch->last_doc_id;
+}
+
+notmuch_status_t
+notmuch_database_remove_message (notmuch_database_t *notmuch,
+                                const char *filename)
+{
+    notmuch_status_t status;
+    notmuch_message_t *message;
+
+    status = notmuch_database_find_message_by_filename (notmuch, filename,
+                                                       &message);
+
+    if (status == NOTMUCH_STATUS_SUCCESS && message) {
+           status = _notmuch_message_remove_filename (message, filename);
+           if (status == NOTMUCH_STATUS_SUCCESS)
+               _notmuch_message_delete (message);
+           else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
+               _notmuch_message_sync (message);
+
+           notmuch_message_destroy (message);
+    }
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
+                                          const char *filename,
+                                          notmuch_message_t **message_ret)
+{
+    void *local;
+    const char *prefix = _find_prefix ("file-direntry");
+    char *direntry, *term;
+    Xapian::PostingIterator i, end;
+    notmuch_status_t status;
+
+    if (message_ret == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_FILE_TERMS))
+       return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+    /* return NULL on any failure */
+    *message_ret = NULL;
+
+    local = talloc_new (notmuch);
+
+    try {
+       status = _notmuch_database_filename_to_direntry (
+           local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+       if (status || !direntry)
+           goto DONE;
+
+       term = talloc_asprintf (local, "%s%s", prefix, direntry);
+
+       find_doc_ids_for_term (notmuch, term, &i, &end);
+
+       if (i != end) {
+           notmuch_private_status_t private_status;
+
+           *message_ret = _notmuch_message_create (notmuch, notmuch, *i,
+                                                   &private_status);
+           if (*message_ret == NULL)
+               status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       }
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+  DONE:
+    talloc_free (local);
+
+    if (status && *message_ret) {
+       notmuch_message_destroy (*message_ret);
+       *message_ret = NULL;
+    }
+    return status;
+}
+
+notmuch_string_list_t *
+_notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
+                                        Xapian::TermIterator &end,
+                                        const char *prefix)
+{
+    int prefix_len = strlen (prefix);
+    notmuch_string_list_t *list;
+
+    list = _notmuch_string_list_create (ctx);
+    if (unlikely (list == NULL))
+       return NULL;
+
+    for (i.skip_to (prefix); i != end; i++) {
+       /* Terminate loop at first term without desired prefix. */
+       if (strncmp ((*i).c_str (), prefix, prefix_len))
+           break;
+
+       _notmuch_string_list_append (list, (*i).c_str () + prefix_len);
+    }
+
+    return list;
+}
+
+notmuch_tags_t *
+notmuch_database_get_all_tags (notmuch_database_t *db)
+{
+    Xapian::TermIterator i, end;
+    notmuch_string_list_t *tags;
+
+    try {
+       i = db->xapian_db->allterms_begin();
+       end = db->xapian_db->allterms_end();
+       tags = _notmuch_database_get_terms_with_prefix (db, i, end,
+                                                       _find_prefix ("tag"));
+       _notmuch_string_list_sort (tags);
+       return _notmuch_tags_create (db, tags);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
+                error.get_msg().c_str());
+       db->exception_reported = true;
+       return NULL;
+    }
+}
+
+const char *
+notmuch_database_status_string (const notmuch_database_t *notmuch)
+{
+    return notmuch->status_string;
+}
diff --git a/lib/directory.cc b/lib/directory.cc
new file mode 100644 (file)
index 0000000..4fcb017
--- /dev/null
@@ -0,0 +1,316 @@
+/* directory.cc - Results of directory-based searches from a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+/* Create an iterator to iterate over the basenames of files (or
+ * directories) that all share a common parent directory.
+ */
+static notmuch_filenames_t *
+_create_filenames_for_terms_with_prefix (void *ctx,
+                                        notmuch_database_t *notmuch,
+                                        const char *prefix)
+{
+    notmuch_string_list_t *filename_list;
+    Xapian::TermIterator i, end;
+
+    i = notmuch->xapian_db->allterms_begin();
+    end = notmuch->xapian_db->allterms_end();
+    filename_list = _notmuch_database_get_terms_with_prefix (ctx, i, end,
+                                                            prefix);
+    if (unlikely (filename_list == NULL))
+       return NULL;
+
+    return _notmuch_filenames_create (ctx, filename_list);
+}
+
+struct _notmuch_directory {
+    notmuch_database_t *notmuch;
+    Xapian::docid document_id;
+    Xapian::Document doc;
+    time_t mtime;
+};
+
+/* We end up having to call the destructor explicitly because we had
+ * to use "placement new" in order to initialize C++ objects within a
+ * block that we allocated with talloc. So C++ is making talloc
+ * slightly less simple to use, (we wouldn't need
+ * talloc_set_destructor at all otherwise).
+ */
+static int
+_notmuch_directory_destructor (notmuch_directory_t *directory)
+{
+    directory->doc.~Document ();
+
+    return 0;
+}
+
+static notmuch_private_status_t
+find_directory_document (notmuch_database_t *notmuch,
+                        const char *db_path,
+                        Xapian::Document *document)
+{
+    notmuch_private_status_t status;
+    Xapian::docid doc_id;
+
+    status = _notmuch_database_find_unique_doc_id (notmuch, "directory",
+                                                  db_path, &doc_id);
+    if (status) {
+       *document = Xapian::Document ();
+       return status;
+    }
+
+    *document = notmuch->xapian_db->get_document (doc_id);
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Find or create a directory document.
+ *
+ * 'path' should be a path relative to the path of 'database', or else
+ * should be an absolute path with initial components that match the
+ * path of 'database'.
+ *
+ * If (flags & NOTMUCH_FIND_CREATE), then the directory document will
+ * be created if it does not exist.  Otherwise, if the directory
+ * document does not exist, *status_ret is set to
+ * NOTMUCH_STATUS_SUCCESS and this returns NULL.
+ */
+notmuch_directory_t *
+_notmuch_directory_create (notmuch_database_t *notmuch,
+                          const char *path,
+                          notmuch_find_flags_t flags,
+                          notmuch_status_t *status_ret)
+{
+    Xapian::WritableDatabase *db;
+    notmuch_directory_t *directory;
+    notmuch_private_status_t private_status;
+    const char *db_path;
+    bool create = (flags & NOTMUCH_FIND_CREATE);
+
+    if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) {
+       *status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED;
+       return NULL;
+    }
+
+    *status_ret = NOTMUCH_STATUS_SUCCESS;
+
+    path = _notmuch_database_relative_path (notmuch, path);
+
+    if (create && notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+       INTERNAL_ERROR ("Failure to ensure database is writable");
+
+    directory = talloc (notmuch, notmuch_directory_t);
+    if (unlikely (directory == NULL)) {
+       *status_ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    directory->notmuch = notmuch;
+
+    /* "placement new"---not actually allocating memory */
+    new (&directory->doc) Xapian::Document;
+
+    talloc_set_destructor (directory, _notmuch_directory_destructor);
+
+    db_path = _notmuch_database_get_directory_db_path (path);
+
+    try {
+       Xapian::TermIterator i, end;
+
+       private_status = find_directory_document (notmuch, db_path,
+                                                 &directory->doc);
+       directory->document_id = directory->doc.get_docid ();
+
+       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           if (!create) {
+               notmuch_directory_destroy (directory);
+               directory = NULL;
+               *status_ret = NOTMUCH_STATUS_SUCCESS;
+               goto DONE;
+           }
+
+           void *local = talloc_new (directory);
+           const char *parent, *basename;
+           Xapian::docid parent_id;
+           char *term = talloc_asprintf (local, "%s%s",
+                                         _find_prefix ("directory"), db_path);
+           directory->doc.add_term (term, 0);
+
+           directory->doc.set_data (path);
+
+           _notmuch_database_split_path (local, path, &parent, &basename);
+
+           *status_ret = _notmuch_database_find_directory_id (
+               notmuch, parent, NOTMUCH_FIND_CREATE, &parent_id);
+           if (*status_ret) {
+               notmuch_directory_destroy (directory);
+               directory = NULL;
+               goto DONE;
+           }
+
+           if (basename) {
+               term = talloc_asprintf (local, "%s%u:%s",
+                                       _find_prefix ("directory-direntry"),
+                                       parent_id, basename);
+               directory->doc.add_term (term, 0);
+           }
+
+           directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
+                                     Xapian::sortable_serialise (0));
+
+           db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+           directory->document_id = _notmuch_database_generate_doc_id (notmuch);
+           db->replace_document (directory->document_id, directory->doc);
+           talloc_free (local);
+       }
+
+       directory->mtime = Xapian::sortable_unserialise (
+           directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch,
+                "A Xapian exception occurred creating a directory: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       notmuch_directory_destroy (directory);
+       directory = NULL;
+       *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+  DONE:
+    if (db_path != path)
+       free ((char *) db_path);
+
+    return directory;
+}
+
+unsigned int
+_notmuch_directory_get_document_id (notmuch_directory_t *directory)
+{
+    return directory->document_id;
+}
+
+notmuch_status_t
+notmuch_directory_set_mtime (notmuch_directory_t *directory,
+                            time_t mtime)
+{
+    notmuch_database_t *notmuch = directory->notmuch;
+    Xapian::WritableDatabase *db;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (notmuch);
+    if (status)
+       return status;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+    try {
+       directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
+                                  Xapian::sortable_serialise (mtime));
+
+       db->replace_document (directory->document_id, directory->doc);
+
+       directory->mtime = mtime;
+
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch,
+                "A Xapian exception occurred setting directory mtime: %s.\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+time_t
+notmuch_directory_get_mtime (notmuch_directory_t *directory)
+{
+    return directory->mtime;
+}
+
+notmuch_filenames_t *
+notmuch_directory_get_child_files (notmuch_directory_t *directory)
+{
+    char *term;
+    notmuch_filenames_t *child_files;
+
+    term = talloc_asprintf (directory, "%s%u:",
+                           _find_prefix ("file-direntry"),
+                           directory->document_id);
+
+    child_files = _create_filenames_for_terms_with_prefix (directory,
+                                                          directory->notmuch,
+                                                          term);
+
+    talloc_free (term);
+
+    return child_files;
+}
+
+notmuch_filenames_t *
+notmuch_directory_get_child_directories (notmuch_directory_t *directory)
+{
+    char *term;
+    notmuch_filenames_t *child_directories;
+
+    term = talloc_asprintf (directory, "%s%u:",
+                           _find_prefix ("directory-direntry"),
+                           directory->document_id);
+
+    child_directories = _create_filenames_for_terms_with_prefix (directory,
+                                                directory->notmuch, term);
+
+    talloc_free (term);
+
+    return child_directories;
+}
+
+notmuch_status_t
+notmuch_directory_delete (notmuch_directory_t *directory)
+{
+    notmuch_status_t status;
+    Xapian::WritableDatabase *db;
+
+    status = _notmuch_database_ensure_writable (directory->notmuch);
+    if (status)
+       return status;
+
+    try {
+       db = static_cast <Xapian::WritableDatabase *> (directory->notmuch->xapian_db);
+       db->delete_document (directory->document_id);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (directory->notmuch,
+                              "A Xapian exception occurred deleting directory entry: %s.\n",
+                              error.get_msg().c_str());
+       directory->notmuch->exception_reported = true;
+       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+    notmuch_directory_destroy (directory);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+void
+notmuch_directory_destroy (notmuch_directory_t *directory)
+{
+    talloc_free (directory);
+}
diff --git a/lib/filenames.c b/lib/filenames.c
new file mode 100644 (file)
index 0000000..37d631d
--- /dev/null
@@ -0,0 +1,76 @@
+/* filenames.c - Iterator for a list of filenames
+ *
+ * Copyright © 2010 Intel Corporation
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+struct _notmuch_filenames {
+    notmuch_string_node_t *iterator;
+};
+
+/* The notmuch_filenames_t iterates over a notmuch_string_list_t of
+ * file names */
+notmuch_filenames_t *
+_notmuch_filenames_create (const void *ctx,
+                          notmuch_string_list_t *list)
+{
+    notmuch_filenames_t *filenames;
+
+    filenames = talloc (ctx, notmuch_filenames_t);
+    if (unlikely (filenames == NULL))
+       return NULL;
+
+    filenames->iterator = list->head;
+    (void) talloc_reference (filenames, list);
+
+    return filenames;
+}
+
+notmuch_bool_t
+notmuch_filenames_valid (notmuch_filenames_t *filenames)
+{
+    if (filenames == NULL)
+       return false;
+
+    return (filenames->iterator != NULL);
+}
+
+const char *
+notmuch_filenames_get (notmuch_filenames_t *filenames)
+{
+    if ((filenames == NULL) || (filenames->iterator == NULL))
+       return NULL;
+
+    return filenames->iterator->string;
+}
+
+void
+notmuch_filenames_move_to_next (notmuch_filenames_t *filenames)
+{
+    if ((filenames == NULL) || (filenames->iterator == NULL))
+       return;
+
+    filenames->iterator = filenames->iterator->next;
+}
+
+void
+notmuch_filenames_destroy (notmuch_filenames_t *filenames)
+{
+    talloc_free (filenames);
+}
diff --git a/lib/index.cc b/lib/index.cc
new file mode 100644 (file)
index 0000000..3f69438
--- /dev/null
@@ -0,0 +1,630 @@
+/*
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include <gmime/gmime.h>
+#include <gmime/gmime-filter.h>
+
+#include <xapian.h>
+
+
+typedef struct {
+    int state;
+    int a;
+    int b;
+    int next_if_match;
+    int next_if_not_match;
+} scanner_state_t;
+
+/* Simple, linear state-transition diagram for the uuencode filter.
+ *
+ * If the character being processed is within the range of [a, b]
+ * for the current state then we transition next_if_match
+ * state. If not, we transition to the next_if_not_match state.
+ *
+ * The final two states are special in that they are the states in
+ * which we discard data. */
+static const int first_uuencode_skipping_state = 11;
+static const scanner_state_t uuencode_states[] = {
+    {0,  'b',  'b',  1,  0},
+    {1,  'e',  'e',  2,  0},
+    {2,  'g',  'g',  3,  0},
+    {3,  'i',  'i',  4,  0},
+    {4,  'n',  'n',  5,  0},
+    {5,  ' ',  ' ',  6,  0},
+    {6,  '0',  '7',  7,  0},
+    {7,  '0',  '7',  8,  0},
+    {8,  '0',  '7',  9,  0},
+    {9,  ' ',  ' ',  10, 0},
+    {10, '\n', '\n', 11, 10},
+    {11, 'M',  'M',  12, 0},
+    {12, ' ',  '`',  12, 11}
+};
+
+/* The following table is intended to implement this DFA (in 'dot'
+   format). Note that 2 and 3 are "hidden" states used to step through
+   the possible out edges of state 1.
+
+digraph html_filter {
+       0 -> 1  [label="<"];
+       0 -> 0;
+       1 -> 4 [label="'"];
+       1 -> 5 [label="\""];
+       1 -> 0 [label=">"];
+       1 -> 1;
+       4 -> 1 [label="'"];
+       4 -> 4;
+       5 -> 1 [label="\""];
+       5 -> 5;
+}
+*/
+static const int first_html_skipping_state = 1;
+static const scanner_state_t html_states[] = {
+    {0,  '<',  '<',  1,  0},
+    {1,  '\'', '\'', 4,  2},  /* scanning for quote or > */
+    {1,  '"',  '"',  5,  3},
+    {1,  '>',  '>',  0,  1},
+    {4,  '\'', '\'', 1,  4},  /* inside single quotes */
+    {5,  '"', '"',   1,  5},  /* inside double quotes */
+};
+
+/* Oh, how I wish that gobject didn't require so much noisy boilerplate!
+ * (Though I have at least eliminated some of the stock set...) */
+typedef struct _NotmuchFilterDiscardNonTerm NotmuchFilterDiscardNonTerm;
+typedef struct _NotmuchFilterDiscardNonTermClass NotmuchFilterDiscardNonTermClass;
+
+/**
+ * NotmuchFilterDiscardNonTerm:
+ *
+ * @parent_object: parent #GMimeFilter
+ * @encode: encoding vs decoding
+ * @state: State of the parser
+ *
+ * A filter to discard uuencoded portions of an email.
+ *
+ * A uuencoded portion is identified as beginning with a line
+ * matching:
+ *
+ *     begin [0-7][0-7][0-7] .*
+ *
+ * After that detection, and beginning with the following line,
+ * characters will be discarded as long as the first character of each
+ * line begins with M and subsequent characters on the line are within
+ * the range of ASCII characters from ' ' to '`'.
+ *
+ * This is not a perfect UUencode filter. It's possible to have a
+ * message that will legitimately match that pattern, (so that some
+ * legitimate content is discarded). And for most UUencoded files, the
+ * final line of encoded data (the line not starting with M) will be
+ * indexed.
+ **/
+struct _NotmuchFilterDiscardNonTerm {
+    GMimeFilter parent_object;
+    GMimeContentType *content_type;
+    int state;
+    int first_skipping_state;
+    const scanner_state_t *states;
+};
+
+struct _NotmuchFilterDiscardNonTermClass {
+    GMimeFilterClass parent_class;
+};
+
+static GMimeFilter *notmuch_filter_discard_non_term_new (GMimeContentType *content);
+
+static void notmuch_filter_discard_non_term_finalize (GObject *object);
+
+static GMimeFilter *filter_copy (GMimeFilter *filter);
+static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                          char **out, size_t *outlen, size_t *outprespace);
+static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                            char **out, size_t *outlen, size_t *outprespace);
+static void filter_reset (GMimeFilter *filter);
+
+
+static GMimeFilterClass *parent_class = NULL;
+
+static void
+notmuch_filter_discard_non_term_class_init (NotmuchFilterDiscardNonTermClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
+
+    parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
+
+    object_class->finalize = notmuch_filter_discard_non_term_finalize;
+
+    filter_class->copy = filter_copy;
+    filter_class->filter = filter_filter;
+    filter_class->complete = filter_complete;
+    filter_class->reset = filter_reset;
+}
+
+static void
+notmuch_filter_discard_non_term_finalize (GObject *object)
+{
+    G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static GMimeFilter *
+filter_copy (GMimeFilter *gmime_filter)
+{
+    NotmuchFilterDiscardNonTerm *filter = (NotmuchFilterDiscardNonTerm *) gmime_filter;
+    return notmuch_filter_discard_non_term_new (filter->content_type);
+}
+
+static void
+filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t prespace,
+              char **outbuf, size_t *outlen, size_t *outprespace)
+{
+    NotmuchFilterDiscardNonTerm *filter = (NotmuchFilterDiscardNonTerm *) gmime_filter;
+    const scanner_state_t *states = filter->states;
+    const char *inptr = inbuf;
+    const char *inend = inbuf + inlen;
+    char *outptr;
+
+    (void) prespace;
+
+    int next;
+
+    g_mime_filter_set_size (gmime_filter, inlen, false);
+    outptr = gmime_filter->outbuf;
+
+    next = filter->state;
+    while (inptr < inend) {
+        /* Each state is defined by a contiguous set of rows of the
+        * state table marked by a common value for '.state'. The
+        * state numbers must be equal to the index of the first row
+        * in a given state; thus the loop condition here looks for a
+        * jump to a first row of a state, which is a real transition
+        * in the underlying DFA.
+        */
+       do {
+           if (*inptr >= states[next].a && *inptr <= states[next].b)  {
+               next = states[next].next_if_match;
+           } else  {
+               next = states[next].next_if_not_match;
+           }
+
+       } while (next != states[next].state);
+
+       if (filter->state < filter->first_skipping_state)
+           *outptr++ = *inptr;
+
+       filter->state = next;
+       inptr++;
+    }
+
+    *outlen = outptr - gmime_filter->outbuf;
+    *outprespace = gmime_filter->outpre;
+    *outbuf = gmime_filter->outbuf;
+}
+
+static void
+filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
+                char **outbuf, size_t *outlen, size_t *outprespace)
+{
+    if (inbuf && inlen)
+       filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
+}
+
+static void
+filter_reset (GMimeFilter *gmime_filter)
+{
+    NotmuchFilterDiscardNonTerm *filter = (NotmuchFilterDiscardNonTerm *) gmime_filter;
+
+    filter->state = 0;
+}
+
+/**
+ * notmuch_filter_discard_non_term_new:
+ *
+ * Returns: a new #NotmuchFilterDiscardNonTerm filter.
+ **/
+static GMimeFilter *
+notmuch_filter_discard_non_term_new (GMimeContentType *content_type)
+{
+    static GType type = 0;
+    NotmuchFilterDiscardNonTerm *filter;
+
+    if (!type) {
+       static const GTypeInfo info = {
+           sizeof (NotmuchFilterDiscardNonTermClass),
+           NULL, /* base_class_init */
+           NULL, /* base_class_finalize */
+           (GClassInitFunc) notmuch_filter_discard_non_term_class_init,
+           NULL, /* class_finalize */
+           NULL, /* class_data */
+           sizeof (NotmuchFilterDiscardNonTerm),
+           0,    /* n_preallocs */
+           NULL, /* instance_init */
+           NULL  /* value_table */
+       };
+
+       type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info, (GTypeFlags) 0);
+    }
+
+    filter = (NotmuchFilterDiscardNonTerm *) g_object_new (type, NULL);
+    filter->content_type = content_type;
+    filter->state = 0;
+    if (g_mime_content_type_is_type (content_type, "text", "html")) {
+      filter->states = html_states;
+      filter->first_skipping_state = first_html_skipping_state;
+    } else {
+      filter->states = uuencode_states;
+      filter->first_skipping_state = first_uuencode_skipping_state;
+    }
+
+    return (GMimeFilter *) filter;
+}
+
+/* We're finally down to a single (NAME + address) email "mailbox". */
+static void
+_index_address_mailbox (notmuch_message_t *message,
+                       const char *prefix_name,
+                       InternetAddress *address)
+{
+    InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
+    const char *name, *addr, *combined;
+    void *local = talloc_new (message);
+
+    name = internet_address_get_name (address);
+    addr = internet_address_mailbox_get_addr (mailbox);
+
+    /* Combine the name and address and index them as a phrase. */
+    if (name && addr)
+       combined = talloc_asprintf (local, "%s %s", name, addr);
+    else if (name)
+       combined = name;
+    else
+       combined = addr;
+
+    if (combined)
+       _notmuch_message_gen_terms (message, prefix_name, combined);
+
+    talloc_free (local);
+}
+
+static void
+_index_address_list (notmuch_message_t *message,
+                    const char *prefix_name,
+                    InternetAddressList *addresses);
+
+/* The outer loop over the InternetAddressList wasn't quite enough.
+ * There can actually be a tree here where a single member of the list
+ * is a "group" containing another list. Recurse please.
+ */
+static void
+_index_address_group (notmuch_message_t *message,
+                     const char *prefix_name,
+                     InternetAddress *address)
+{
+    InternetAddressGroup *group;
+    InternetAddressList *list;
+
+    group = INTERNET_ADDRESS_GROUP (address);
+    list = internet_address_group_get_members (group);
+
+    if (! list)
+       return;
+
+    _index_address_list (message, prefix_name, list);
+}
+
+static void
+_index_address_list (notmuch_message_t *message,
+                    const char *prefix_name,
+                    InternetAddressList *addresses)
+{
+    int i;
+    InternetAddress *address;
+
+    if (addresses == NULL)
+       return;
+
+    for (i = 0; i < internet_address_list_length (addresses); i++) {
+       address = internet_address_list_get_address (addresses, i);
+       if (INTERNET_ADDRESS_IS_MAILBOX (address)) {
+           _index_address_mailbox (message, prefix_name, address);
+       } else if (INTERNET_ADDRESS_IS_GROUP (address)) {
+           _index_address_group (message, prefix_name, address);
+       } else {
+           INTERNAL_ERROR ("GMime InternetAddress is neither a mailbox nor a group.\n");
+       }
+    }
+}
+
+static void
+_index_content_type (notmuch_message_t *message, GMimeObject *part)
+{
+    GMimeContentType *content_type = g_mime_object_get_content_type (part);
+    if (content_type) {
+       char *mime_string = g_mime_content_type_to_string (content_type);
+       if (mime_string) {
+           _notmuch_message_gen_terms (message, "mimetype", mime_string);
+           g_free (mime_string);
+       }
+    }
+}
+
+static void
+_index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
+                           GMimeContentType *content_type,
+                           GMimeMultipartEncrypted *part);
+
+/* Callback to generate terms for each mime part of a message. */
+static void
+_index_mime_part (notmuch_message_t *message,
+                 notmuch_indexopts_t *indexopts,
+                 GMimeObject *part)
+{
+    GMimeStream *stream, *filter;
+    GMimeFilter *discard_non_term_filter;
+    GMimeDataWrapper *wrapper;
+    GByteArray *byte_array;
+    GMimeContentDisposition *disposition;
+    GMimeContentType *content_type;
+    char *body;
+    const char *charset;
+
+    if (! part) {
+       _notmuch_database_log (notmuch_message_get_database (message),
+                             "Warning: Not indexing empty mime part.\n");
+       return;
+    }
+
+    _index_content_type (message, part);
+    content_type = g_mime_object_get_content_type (part);
+
+    if (GMIME_IS_MULTIPART (part)) {
+       GMimeMultipart *multipart = GMIME_MULTIPART (part);
+       int i;
+
+       if (GMIME_IS_MULTIPART_SIGNED (multipart))
+         _notmuch_message_add_term (message, "tag", "signed");
+
+       if (GMIME_IS_MULTIPART_ENCRYPTED (multipart))
+         _notmuch_message_add_term (message, "tag", "encrypted");
+
+       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
+           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
+               /* Don't index the signature, but index its content type. */
+               if (i == GMIME_MULTIPART_SIGNED_SIGNATURE) {
+                   _index_content_type (message,
+                                        g_mime_multipart_get_part (multipart, i));
+                   continue;
+               } else if (i != GMIME_MULTIPART_SIGNED_CONTENT) {
+                   _notmuch_database_log (notmuch_message_get_database (message),
+                                          "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+               }
+           }
+           if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) {
+               _index_content_type (message,
+                                    g_mime_multipart_get_part (multipart, i));
+               if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
+                   _index_encrypted_mime_part(message, indexopts,
+                                              content_type,
+                                              GMIME_MULTIPART_ENCRYPTED (part));
+               } else {
+                   if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
+                       _notmuch_database_log (notmuch_message_get_database (message),
+                                              "Warning: Unexpected extra parts of multipart/encrypted.\n");
+                   }
+               }
+               continue;
+           }
+           _index_mime_part (message, indexopts,
+                             g_mime_multipart_get_part (multipart, i));
+       }
+       return;
+    }
+
+    if (GMIME_IS_MESSAGE_PART (part)) {
+       GMimeMessage *mime_message;
+
+       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+
+       _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
+
+       return;
+    }
+
+    if (! (GMIME_IS_PART (part))) {
+       _notmuch_database_log (notmuch_message_get_database (message),
+                             "Warning: Not indexing unknown mime part: %s.\n",
+                             g_type_name (G_OBJECT_TYPE (part)));
+       return;
+    }
+
+    disposition = g_mime_object_get_content_disposition (part);
+    if (disposition &&
+       strcasecmp (g_mime_content_disposition_get_disposition (disposition),
+                   GMIME_DISPOSITION_ATTACHMENT) == 0)
+    {
+       const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+
+       _notmuch_message_add_term (message, "tag", "attachment");
+       _notmuch_message_gen_terms (message, "attachment", filename);
+
+       /* XXX: Would be nice to call out to something here to parse
+        * the attachment into text and then index that. */
+       return;
+    }
+
+    byte_array = g_byte_array_new ();
+
+    stream = g_mime_stream_mem_new_with_byte_array (byte_array);
+    g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), false);
+
+    filter = g_mime_stream_filter_new (stream);
+
+    discard_non_term_filter = notmuch_filter_discard_non_term_new (content_type);
+
+    g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter),
+                             discard_non_term_filter);
+
+    charset = g_mime_object_get_content_type_parameter (part, "charset");
+    if (charset) {
+       GMimeFilter *charset_filter;
+       charset_filter = g_mime_filter_charset_new (charset, "UTF-8");
+       /* This result can be NULL for things like "unknown-8bit".
+        * Don't set a NULL filter as that makes GMime print
+        * annoying assertion-failure messages on stderr. */
+       if (charset_filter) {
+           g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter),
+                                     charset_filter);
+           g_object_unref (charset_filter);
+       }
+    }
+
+    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+    if (wrapper)
+       g_mime_data_wrapper_write_to_stream (wrapper, filter);
+
+    g_object_unref (stream);
+    g_object_unref (filter);
+    g_object_unref (discard_non_term_filter);
+
+    g_byte_array_append (byte_array, (guint8 *) "\0", 1);
+    body = (char *) g_byte_array_free (byte_array, false);
+
+    if (body) {
+       _notmuch_message_gen_terms (message, NULL, body);
+
+       free (body);
+    }
+}
+
+/* descend (if desired) into the cleartext part of an encrypted MIME
+ * part while indexing. */
+static void
+_index_encrypted_mime_part (notmuch_message_t *message,
+                           notmuch_indexopts_t *indexopts,
+                           g_mime_3_unused(GMimeContentType *content_type),
+                           GMimeMultipartEncrypted *encrypted_data)
+{
+    notmuch_status_t status;
+    GError *err = NULL;
+    notmuch_database_t * notmuch = NULL;
+    GMimeObject *clear = NULL;
+
+    if (!indexopts || (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE))
+       return;
+
+    notmuch = notmuch_message_get_database (message);
+
+    GMimeCryptoContext* crypto_ctx = NULL;
+#if (GMIME_MAJOR_VERSION < 3)
+    {
+       const char *protocol = NULL;
+       protocol = g_mime_content_type_get_parameter (content_type, "protocol");
+       status = _notmuch_crypto_get_gmime_ctx_for_protocol (&(indexopts->crypto),
+                                                        protocol, &crypto_ctx);
+       if (status) {
+           _notmuch_database_log (notmuch, "Warning: setup failed for decrypting "
+                                  "during indexing. (%d)\n", status);
+           status = notmuch_message_add_property (message, "index.decryption", "failure");
+           if (status)
+               _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+                                             "property (%d)\n", status);
+           return;
+       }
+    }
+#endif
+    bool attempted = false;
+    GMimeDecryptResult *decrypt_result = NULL;
+    bool get_sk = (HAVE_GMIME_SESSION_KEYS && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
+    clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts),
+                                    message, crypto_ctx, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
+    if (!attempted)
+       return;
+    if (err || !clear) {
+       if (decrypt_result)
+           g_object_unref (decrypt_result);
+       if (err) {
+           _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n",
+                                  err->domain, err->code, err->message);
+           g_error_free(err);
+       } else {
+           _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (unknown error)\n");
+       }
+       /* Indicate that we failed to decrypt during indexing */
+       status = notmuch_message_add_property (message, "index.decryption", "failure");
+       if (status)
+           _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+                                         "property (%d)\n", status);
+       return;
+    }
+    if (decrypt_result) {
+#if HAVE_GMIME_SESSION_KEYS
+       if (get_sk) {
+           status = notmuch_message_add_property (message, "session-key",
+                                                  g_mime_decrypt_result_get_session_key (decrypt_result));
+           if (status)
+               _notmuch_database_log (notmuch, "failed to add session-key "
+                                      "property (%d)\n", status);
+       }
+#endif
+       g_object_unref (decrypt_result);
+    }
+    _index_mime_part (message, indexopts, clear);
+    g_object_unref (clear);
+
+    status = notmuch_message_add_property (message, "index.decryption", "success");
+    if (status)
+       _notmuch_database_log (notmuch, "failed to add index.decryption "
+                              "property (%d)\n", status);
+
+}
+
+notmuch_status_t
+_notmuch_message_index_file (notmuch_message_t *message,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_file_t *message_file)
+{
+    GMimeMessage *mime_message;
+    InternetAddressList *addresses;
+    const char *subject;
+    notmuch_status_t status;
+
+    status = _notmuch_message_file_get_mime_message (message_file,
+                                                    &mime_message);
+    if (status)
+       return status;
+
+    addresses = g_mime_message_get_from (mime_message);
+    if (addresses) {
+       _index_address_list (message, "from", addresses);
+       g_mime_2_6_unref (addresses);
+    }
+
+    addresses = g_mime_message_get_all_recipients (mime_message);
+    if (addresses) {
+       _index_address_list (message, "to", addresses);
+       g_object_unref (addresses);
+    }
+
+    subject = g_mime_message_get_subject (mime_message);
+    _notmuch_message_gen_terms (message, "subject", subject);
+
+    _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
diff --git a/lib/indexopts.c b/lib/indexopts.c
new file mode 100644 (file)
index 0000000..b78a57b
--- /dev/null
@@ -0,0 +1,75 @@
+/* indexopts.c - options for indexing messages (currently a stub)
+ *
+ * Copyright © 2017 Daniel Kahn Gillmor
+ *
+ * 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/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-private.h"
+
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db)
+{
+    notmuch_indexopts_t *ret = talloc_zero (db, notmuch_indexopts_t);
+    if (!ret)
+       return ret;
+    ret->crypto.decrypt = NOTMUCH_DECRYPT_AUTO;
+
+    char * decrypt_policy;
+    notmuch_status_t err = notmuch_database_get_config (db, "index.decrypt", &decrypt_policy);
+    if (err)
+       return ret;
+
+    if (decrypt_policy) {
+       if ((!(strcasecmp(decrypt_policy, "true"))) ||
+           (!(strcasecmp(decrypt_policy, "yes"))) ||
+           (!(strcasecmp(decrypt_policy, "1"))))
+           notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_TRUE);
+       else if ((!(strcasecmp(decrypt_policy, "false"))) ||
+                (!(strcasecmp(decrypt_policy, "no"))) ||
+                (!(strcasecmp(decrypt_policy, "0"))))
+           notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_FALSE);
+       else if (!strcasecmp(decrypt_policy, "nostash"))
+           notmuch_indexopts_set_decrypt_policy (ret, NOTMUCH_DECRYPT_NOSTASH);
+    }
+
+    free (decrypt_policy);
+    return ret;
+}
+
+notmuch_status_t
+notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts,
+                                     notmuch_decryption_policy_t decrypt_policy)
+{
+    if (!indexopts)
+       return NOTMUCH_STATUS_NULL_POINTER;
+    indexopts->crypto.decrypt = decrypt_policy;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_decryption_policy_t
+notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts)
+{
+    if (!indexopts)
+       return false;
+    return indexopts->crypto.decrypt;
+}
+
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *indexopts)
+{
+    talloc_free (indexopts);
+}
diff --git a/lib/message-file.c b/lib/message-file.c
new file mode 100644 (file)
index 0000000..8f0dbbd
--- /dev/null
@@ -0,0 +1,433 @@
+/* message.c - Utility functions for parsing an email message for notmuch.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdarg.h>
+
+#include "notmuch-private.h"
+
+#include <gmime/gmime.h>
+
+#include <glib.h> /* GHashTable */
+
+struct _notmuch_message_file {
+    /* File object */
+    FILE *file;
+    char *filename;
+
+    /* Cache for decoded headers */
+    GHashTable *headers;
+
+    GMimeMessage *message;
+};
+
+static int
+_notmuch_message_file_destructor (notmuch_message_file_t *message)
+{
+    if (message->headers)
+       g_hash_table_destroy (message->headers);
+
+    if (message->message)
+       g_object_unref (message->message);
+
+    if (message->file)
+       fclose (message->file);
+
+    return 0;
+}
+
+/* Create a new notmuch_message_file_t for 'filename' with 'ctx' as
+ * the talloc owner. */
+notmuch_message_file_t *
+_notmuch_message_file_open_ctx (notmuch_database_t *notmuch,
+                               void *ctx, const char *filename)
+{
+    notmuch_message_file_t *message;
+
+    message = talloc_zero (ctx, notmuch_message_file_t);
+    if (unlikely (message == NULL))
+       return NULL;
+
+    /* Only needed for error messages during parsing. */
+    message->filename = talloc_strdup (message, filename);
+    if (message->filename == NULL)
+       goto FAIL;
+
+    talloc_set_destructor (message, _notmuch_message_file_destructor);
+
+    message->file = fopen (filename, "r");
+    if (message->file == NULL)
+       goto FAIL;
+
+    return message;
+
+  FAIL:
+    _notmuch_database_log (notmuch, "Error opening %s: %s\n",
+                         filename, strerror (errno));
+    _notmuch_message_file_close (message);
+
+    return NULL;
+}
+
+notmuch_message_file_t *
+_notmuch_message_file_open (notmuch_database_t *notmuch,
+                           const char *filename)
+{
+    return _notmuch_message_file_open_ctx (notmuch, NULL, filename);
+}
+
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message_file)
+{
+    return message_file->filename;
+}
+
+void
+_notmuch_message_file_close (notmuch_message_file_t *message)
+{
+    talloc_free (message);
+}
+
+static bool
+_is_mbox (FILE *file)
+{
+    char from_buf[5];
+    bool ret = false;
+
+    /* Is this mbox? */
+    if (fread (from_buf, sizeof (from_buf), 1, file) == 1 &&
+       strncmp (from_buf, "From ", 5) == 0)
+       ret = true;
+
+    rewind (file);
+
+    return ret;
+}
+
+notmuch_status_t
+_notmuch_message_file_parse (notmuch_message_file_t *message)
+{
+    GMimeStream *stream;
+    GMimeParser *parser;
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    static int initialized = 0;
+    bool is_mbox;
+
+    if (message->message)
+       return NOTMUCH_STATUS_SUCCESS;
+
+    is_mbox = _is_mbox (message->file);
+
+    if (! initialized) {
+       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+       initialized = 1;
+    }
+
+    message->headers = g_hash_table_new_full (strcase_hash, strcase_equal,
+                                             free, g_free);
+    if (! message->headers)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    stream = g_mime_stream_file_new (message->file);
+
+    /* We'll own and fclose the FILE* ourselves. */
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), false);
+
+    parser = g_mime_parser_new_with_stream (stream);
+    g_mime_parser_set_scan_from (parser, is_mbox);
+
+    message->message = g_mime_parser_construct_message (parser);
+    if (! message->message) {
+       status = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+       goto DONE;
+    }
+
+    if (is_mbox && ! g_mime_parser_eos (parser)) {
+       /*
+        * This is a multi-message mbox. (For historical reasons, we
+        * do support single-message mboxes.)
+        */
+       status = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+    }
+
+  DONE:
+    g_object_unref (stream);
+    g_object_unref (parser);
+
+    if (status) {
+       g_hash_table_destroy (message->headers);
+       message->headers = NULL;
+
+       if (message->message) {
+           g_object_unref (message->message);
+           message->message = NULL;
+       }
+
+       rewind (message->file);
+    }
+
+    return status;
+}
+
+notmuch_status_t
+_notmuch_message_file_get_mime_message (notmuch_message_file_t *message,
+                                       GMimeMessage **mime_message)
+{
+    notmuch_status_t status;
+
+    status = _notmuch_message_file_parse (message);
+    if (status)
+       return status;
+
+    *mime_message = message->message;
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/*
+ * Get all instances of a header decoded and concatenated.
+ *
+ * The result must be freed using g_free().
+ *
+ * Return NULL on errors, empty string for non-existing headers.
+ */
+
+static char *
+_extend_header (char *combined, const char *value) {
+    char *decoded;
+
+    decoded = g_mime_utils_header_decode_text (value);
+    if (! decoded) {
+       if (combined) {
+           g_free (combined);
+           combined = NULL;
+       }
+       goto DONE;
+    }
+
+    if (combined) {
+       char *tmp = g_strdup_printf ("%s %s", combined, decoded);
+       g_free (decoded);
+       g_free (combined);
+       if (! tmp) {
+           combined = NULL;
+           goto DONE;
+       }
+
+       combined = tmp;
+    } else {
+       combined = decoded;
+    }
+ DONE:
+    return combined;
+}
+
+#if (GMIME_MAJOR_VERSION < 3)
+static char *
+_notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
+                                          const char *header)
+{
+    GMimeHeaderList *headers;
+    GMimeHeaderIter *iter;
+    char *combined = NULL;
+
+    headers = g_mime_object_get_header_list (GMIME_OBJECT (message->message));
+    if (! headers)
+       return NULL;
+
+    iter = g_mime_header_iter_new ();
+    if (! iter)
+       return NULL;
+
+    if (! g_mime_header_list_get_iter (headers, iter))
+       goto DONE;
+
+    do {
+       const char *value;
+       if (strcasecmp (g_mime_header_iter_get_name (iter), header) != 0)
+           continue;
+
+       /* Note that GMime retains ownership of value... */
+       value = g_mime_header_iter_get_value (iter);
+
+       combined = _extend_header (combined, value);
+    } while (g_mime_header_iter_next (iter));
+
+    /* Return empty string for non-existing headers. */
+    if (! combined)
+       combined = g_strdup ("");
+
+  DONE:
+    g_mime_header_iter_free (iter);
+
+    return combined;
+}
+#else
+static char *
+_notmuch_message_file_get_combined_header (notmuch_message_file_t *message,
+                                          const char *header)
+{
+    char *combined = NULL;
+    GMimeHeaderList *headers;
+
+    headers = g_mime_object_get_header_list (GMIME_OBJECT (message->message));
+    if (! headers)
+       return NULL;
+
+
+    for (int i=0; i < g_mime_header_list_get_count (headers); i++) {
+       const char *value;
+       GMimeHeader *g_header = g_mime_header_list_get_header_at (headers, i);
+
+       if (strcasecmp (g_mime_header_get_name (g_header), header) != 0)
+           continue;
+
+       /* GMime retains ownership of value, we hope */
+       value = g_mime_header_get_value (g_header);
+
+       combined = _extend_header (combined, value);
+    }
+
+    /* Return empty string for non-existing headers. */
+    if (! combined)
+       combined = g_strdup ("");
+
+    return combined;
+}
+#endif
+
+const char *
+_notmuch_message_file_get_header (notmuch_message_file_t *message,
+                                const char *header)
+{
+    const char *value;
+    char *decoded;
+
+    if (_notmuch_message_file_parse (message))
+       return NULL;
+
+    /* If we have a cached decoded value, use it. */
+    value = g_hash_table_lookup (message->headers, header);
+    if (value)
+       return value;
+
+    if (strcasecmp (header, "received") == 0) {
+       /*
+        * The Received: header is special. We concatenate all
+        * instances of the header as we use this when analyzing the
+        * path the mail has taken from sender to recipient.
+        */
+       decoded = _notmuch_message_file_get_combined_header (message, header);
+    } else {
+       value = g_mime_object_get_header (GMIME_OBJECT (message->message),
+                                         header);
+       if (value)
+           decoded = g_mime_utils_header_decode_text (value);
+       else
+           decoded = g_strdup ("");
+    }
+
+    if (! decoded)
+       return NULL;
+
+    /* Cache the decoded value. We also own the strings. */
+    g_hash_table_insert (message->headers, xstrdup (header), decoded);
+
+    return decoded;
+}
+
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+                                  const char **from_out,
+                                  const char **subject_out,
+                                  const char **to_out,
+                                  const char **date_out,
+                                  char **message_id_out)
+{
+    notmuch_status_t ret;
+    const char *header;
+    const char *from, *to, *subject, *date;
+    char *message_id = NULL;
+
+    /* Parse message up front to get better error status. */
+    ret = _notmuch_message_file_parse (message_file);
+    if (ret)
+       goto DONE;
+
+    /* Before we do any real work, (especially before doing a
+     * potential SHA-1 computation on the entire file's contents),
+     * 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");
+    date = _notmuch_message_file_get_header (message_file, "date");
+
+    if ((from == NULL || *from == '\0') &&
+       (subject == NULL || *subject == '\0') &&
+       (to == NULL || *to == '\0')) {
+       ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+       goto DONE;
+    }
+
+    /* 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");
+    if (header && *header != '\0') {
+       message_id = _notmuch_message_id_parse (message_file, header, NULL);
+
+       /* So the header value isn't RFC-compliant, but it's
+        * better than no message-id at all.
+        */
+       if (message_id == NULL)
+           message_id = talloc_strdup (message_file, header);
+    }
+
+    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 (_notmuch_message_file_get_filename (message_file));
+
+       /* If that failed too, something is really wrong. Give up. */
+       if (sha1 == NULL) {
+           ret = NOTMUCH_STATUS_FILE_ERROR;
+           goto DONE;
+       }
+
+       message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
+       free (sha1);
+    }
+ DONE:
+    if (ret == NOTMUCH_STATUS_SUCCESS) {
+       if (from_out)
+           *from_out = from;
+       if (subject_out)
+           *subject_out = subject;
+       if (to_out)
+           *to_out = to;
+       if (date_out)
+           *date_out = date;
+       if (message_id_out)
+           *message_id_out = message_id;
+    }
+    return ret;
+}
diff --git a/lib/message-id.c b/lib/message-id.c
new file mode 100644 (file)
index 0000000..e71ce9f
--- /dev/null
@@ -0,0 +1,126 @@
+#include "notmuch-private.h"
+#include "string-util.h"
+
+/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
+ * a (potentially nested) parenthesized sequence with '\' used to
+ * escape any character (including parentheses).
+ *
+ * If the sequence to be skipped continues to the end of the string,
+ * then 'str' will be left pointing at the final terminating '\0'
+ * character.
+ */
+static void
+skip_space_and_comments (const char **str)
+{
+    const char *s;
+
+    s = *str;
+    while (*s && (isspace (*s) || *s == '(')) {
+       while (*s && isspace (*s))
+           s++;
+       if (*s == '(') {
+           int nesting = 1;
+           s++;
+           while (*s && nesting) {
+               if (*s == '(') {
+                   nesting++;
+               } else if (*s == ')') {
+                   nesting--;
+               } else if (*s == '\\') {
+                   if (*(s+1))
+                       s++;
+               }
+               s++;
+           }
+       }
+    }
+
+    *str = s;
+}
+
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next)
+{
+    const char *s, *end;
+    char *result;
+
+    if (message_id == NULL || *message_id == '\0')
+       return NULL;
+
+    s = message_id;
+
+    skip_space_and_comments (&s);
+
+    /* Skip any unstructured text as well. */
+    while (*s && *s != '<')
+       s++;
+
+    if (*s == '<') {
+       s++;
+    } else {
+       if (next)
+           *next = s;
+       return NULL;
+    }
+
+    skip_space_and_comments (&s);
+
+    end = s;
+    while (*end && *end != '>')
+       end++;
+    if (next) {
+       if (*end)
+           *next = end + 1;
+       else
+           *next = end;
+    }
+
+    if (end > s && *end == '>')
+       end--;
+    if (end <= s)
+       return NULL;
+
+    result = talloc_strndup (ctx, s, end - s + 1);
+
+    /* Finally, collapse any whitespace that is within the message-id
+     * itself. */
+    {
+       char *r;
+       int len;
+
+       for (r = result, len = strlen (r); *r; r++, len--)
+           if (*r == ' ' || *r == '\t')
+               memmove (r, r+1, len);
+    }
+
+    return result;
+}
+
+char *
+_notmuch_message_id_parse_strict (void *ctx, const char *message_id)
+{
+    const char *s, *end;
+
+    if (message_id == NULL || *message_id == '\0')
+       return NULL;
+
+    s = skip_space (message_id);
+    if (*s == '<')
+       s++;
+    else
+       return NULL;
+
+    for (end = s; *end && *end != '>'; end++)
+       if (isspace (*end))
+           return NULL;
+
+    if (*end != '>')
+       return NULL;
+    else {
+       const char *last = skip_space (end + 1);
+       if (*last != '\0')
+           return NULL;
+    }
+
+    return talloc_strndup (ctx, s, end - s);
+}
diff --git a/lib/message-private.h b/lib/message-private.h
new file mode 100644 (file)
index 0000000..73b080e
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef MESSAGE_PRIVATE_H
+#define MESSAGE_PRIVATE_H
+
+notmuch_string_map_t *
+_notmuch_message_property_map (notmuch_message_t *message);
+
+bool
+_notmuch_message_frozen (notmuch_message_t *message);
+
+void
+_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix);
+
+void
+_notmuch_message_invalidate_metadata (notmuch_message_t *message,  const char *prefix_name);
+
+#endif
diff --git a/lib/message-property.cc b/lib/message-property.cc
new file mode 100644 (file)
index 0000000..710ba04
--- /dev/null
@@ -0,0 +1,185 @@
+/* message-property.cc - Properties are like tags, but (key,value) pairs.
+ * keys are allowed to repeat.
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+#include "message-private.h"
+
+notmuch_status_t
+notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)
+{
+    if (! value)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    *value = _notmuch_string_map_get (_notmuch_message_property_map (message), key);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_count_properties (notmuch_message_t *message, const char *key, unsigned int *count)
+{
+    if (! count || ! key || ! message)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    notmuch_string_map_t *map;
+    map = _notmuch_message_property_map (message);
+    if (! map)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    notmuch_string_map_iterator_t *matcher = _notmuch_string_map_iterator_create (map, key, true);
+    if (! matcher)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    *count = 0;
+    while (_notmuch_string_map_iterator_valid (matcher)) {
+       (*count)++;
+       _notmuch_string_map_iterator_move_to_next (matcher);
+    }
+
+    _notmuch_string_map_iterator_destroy (matcher);
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_notmuch_message_modify_property (notmuch_message_t *message, const char *key, const char *value,
+                                 bool delete_it)
+{
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+    char *term = NULL;
+
+    status = _notmuch_database_ensure_writable (notmuch_message_get_database (message));
+    if (status)
+       return status;
+
+    if (key == NULL || value == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strchr (key, '='))
+       return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+
+    term = talloc_asprintf (message, "%s=%s", key, value);
+
+    if (delete_it)
+       private_status = _notmuch_message_remove_term (message, "property", term);
+    else
+       private_status = _notmuch_message_add_term (message, "property", term);
+
+    if (private_status)
+       return COERCE_STATUS (private_status,
+                             "Unhandled error modifying message property");
+    if (! _notmuch_message_frozen (message))
+       _notmuch_message_sync (message);
+
+    if (term)
+       talloc_free (term);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value)
+{
+    return _notmuch_message_modify_property (message, key, value, false);
+}
+
+notmuch_status_t
+notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value)
+{
+    return _notmuch_message_modify_property (message, key, value, true);
+}
+
+static
+notmuch_status_t
+_notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key, bool prefix)
+{
+    notmuch_status_t status;
+    const char * term_prefix;
+
+    status = _notmuch_database_ensure_writable (notmuch_message_get_database (message));
+    if (status)
+       return status;
+
+    _notmuch_message_invalidate_metadata (message, "property");
+    if (key)
+       term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
+                                      prefix ? "" : "=");
+    else
+       term_prefix = _find_prefix ("property");
+
+    /* XXX better error reporting ? */
+    _notmuch_message_remove_terms (message, term_prefix);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+{
+    return _notmuch_message_remove_all_properties (message, key, false);
+}
+
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix)
+{
+    return _notmuch_message_remove_all_properties (message, prefix, true);
+}
+
+notmuch_message_properties_t *
+notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact)
+{
+    notmuch_string_map_t *map;
+    map = _notmuch_message_property_map (message);
+    return _notmuch_string_map_iterator_create (map, key, exact);
+}
+
+notmuch_bool_t
+notmuch_message_properties_valid (notmuch_message_properties_t *properties)
+{
+    return _notmuch_string_map_iterator_valid (properties);
+}
+
+void
+notmuch_message_properties_move_to_next (notmuch_message_properties_t *properties)
+{
+    return _notmuch_string_map_iterator_move_to_next (properties);
+}
+
+const char *
+notmuch_message_properties_key (notmuch_message_properties_t *properties)
+{
+    return _notmuch_string_map_iterator_key (properties);
+}
+
+const char *
+notmuch_message_properties_value (notmuch_message_properties_t *properties)
+{
+    return _notmuch_string_map_iterator_value (properties);
+}
+
+void
+notmuch_message_properties_destroy (notmuch_message_properties_t *properties)
+{
+    _notmuch_string_map_iterator_destroy (properties);
+}
diff --git a/lib/message.cc b/lib/message.cc
new file mode 100644 (file)
index 0000000..6f2f634
--- /dev/null
@@ -0,0 +1,2185 @@
+/* message.cc - Results of message-based searches from a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+#include "message-private.h"
+
+#include <stdint.h>
+
+#include <gmime/gmime.h>
+
+struct _notmuch_message {
+    notmuch_database_t *notmuch;
+    Xapian::docid doc_id;
+    int frozen;
+    char *message_id;
+    char *thread_id;
+    size_t thread_depth;
+    char *in_reply_to;
+    notmuch_string_list_t *tag_list;
+    notmuch_string_list_t *filename_term_list;
+    notmuch_string_list_t *filename_list;
+    char *maildir_flags;
+    char *author;
+    notmuch_message_file_t *message_file;
+    notmuch_string_list_t *property_term_list;
+    notmuch_string_map_t *property_map;
+    notmuch_string_list_t *reference_list;
+    notmuch_message_list_t *replies;
+    unsigned long flags;
+    /* For flags that are initialized on-demand, lazy_flags indicates
+     * if each flag has been initialized. */
+    unsigned long lazy_flags;
+
+    /* Message document modified since last sync */
+    bool modified;
+
+    /* last view of database the struct is synced with */
+    unsigned long last_view;
+
+    Xapian::Document doc;
+    Xapian::termcount termpos;
+};
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+struct maildir_flag_tag {
+    char flag;
+    const char *tag;
+    bool inverse;
+};
+
+/* ASCII ordered table of Maildir flags and associated tags */
+static struct maildir_flag_tag flag2tag[] = {
+    { 'D', "draft",   false},
+    { 'F', "flagged", false},
+    { 'P', "passed",  false},
+    { 'R', "replied", false},
+    { 'S', "unread",  true }
+};
+
+/* We end up having to call the destructor explicitly because we had
+ * to use "placement new" in order to initialize C++ objects within a
+ * block that we allocated with talloc. So C++ is making talloc
+ * slightly less simple to use, (we wouldn't need
+ * talloc_set_destructor at all otherwise).
+ */
+static int
+_notmuch_message_destructor (notmuch_message_t *message)
+{
+    message->doc.~Document ();
+
+    return 0;
+}
+
+static notmuch_message_t *
+_notmuch_message_create_for_document (const void *talloc_owner,
+                                     notmuch_database_t *notmuch,
+                                     unsigned int doc_id,
+                                     Xapian::Document doc,
+                                     notmuch_private_status_t *status)
+{
+    notmuch_message_t *message;
+
+    if (status)
+       *status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
+
+    message = talloc (talloc_owner, notmuch_message_t);
+    if (unlikely (message == NULL)) {
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    message->notmuch = notmuch;
+    message->doc_id = doc_id;
+
+    message->frozen = 0;
+    message->flags = 0;
+    message->lazy_flags = 0;
+
+    /* the message is initially not synchronized with Xapian */
+    message->last_view = 0;
+
+    /* Calculated after the thread structure is computed */
+    message->thread_depth = 0;
+
+    /* Each of these will be lazily created as needed. */
+    message->message_id = NULL;
+    message->thread_id = NULL;
+    message->in_reply_to = NULL;
+    message->tag_list = NULL;
+    message->filename_term_list = NULL;
+    message->filename_list = NULL;
+    message->maildir_flags = NULL;
+    message->message_file = NULL;
+    message->author = NULL;
+    message->property_term_list = NULL;
+    message->property_map = NULL;
+    message->reference_list = NULL;
+
+    message->replies = _notmuch_message_list_create (message);
+    if (unlikely (message->replies == NULL)) {
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    /* This is C++'s creepy "placement new", which is really just an
+     * ugly way to call a constructor for a pre-allocated object. So
+     * it's really not an error to not be checking for OUT_OF_MEMORY
+     * here, since this "new" isn't actually allocating memory. This
+     * is language-design comedy of the wrong kind. */
+
+    new (&message->doc) Xapian::Document;
+
+    talloc_set_destructor (message, _notmuch_message_destructor);
+
+    message->doc = doc;
+    message->termpos = 0;
+
+    return message;
+}
+
+/* Create a new notmuch_message_t object for an existing document in
+ * the database.
+ *
+ * Here, 'talloc owner' is an optional talloc context to which the new
+ * message will belong. This allows for the caller to not bother
+ * calling notmuch_message_destroy on the message, and know that all
+ * memory will be reclaimed when 'talloc_owner' is freed. The caller
+ * still can call notmuch_message_destroy when finished with the
+ * message if desired.
+ *
+ * The 'talloc_owner' argument can also be NULL, in which case the
+ * caller *is* responsible for calling notmuch_message_destroy.
+ *
+ * If no document exists in the database with document ID of 'doc_id'
+ * then this function returns NULL and optionally sets *status to
+ * NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND.
+ *
+ * This function can also fail to due lack of available memory,
+ * returning NULL and optionally setting *status to
+ * NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY.
+ *
+ * The caller can pass NULL for status if uninterested in
+ * distinguishing these two cases.
+ */
+notmuch_message_t *
+_notmuch_message_create (const void *talloc_owner,
+                        notmuch_database_t *notmuch,
+                        unsigned int doc_id,
+                        notmuch_private_status_t *status)
+{
+    Xapian::Document doc;
+
+    try {
+       doc = notmuch->xapian_db->get_document (doc_id);
+    } catch (const Xapian::DocNotFoundError &error) {
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+       return NULL;
+    }
+
+    return _notmuch_message_create_for_document (talloc_owner, notmuch,
+                                                doc_id, doc, status);
+}
+
+/* Create a new notmuch_message_t object for a specific message ID,
+ * (which may or may not already exist in the database).
+ *
+ * The 'notmuch' database will be the talloc owner of the returned
+ * message.
+ *
+ * This function returns a valid notmuch_message_t whether or not
+ * there is already a document in the database with the given message
+ * ID. These two cases can be distinguished by the value of *status:
+ *
+ *
+ *   NOTMUCH_PRIVATE_STATUS_SUCCESS:
+ *
+ *     There is already a document with message ID 'message_id' in the
+ *     database. The returned message can be used to query/modify the
+ *     document. The message may be a ghost message.
+ *
+ *   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.  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
+ * not be NULL.)
+ */
+notmuch_message_t *
+_notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
+                                       const char *message_id,
+                                       notmuch_private_status_t *status_ret)
+{
+    notmuch_message_t *message;
+    Xapian::Document doc;
+    unsigned int doc_id;
+    char *term;
+
+    *status_ret = (notmuch_private_status_t) notmuch_database_find_message (notmuch,
+                                                                           message_id,
+                                                                           &message);
+    if (message)
+       return talloc_steal (notmuch, message);
+    else if (*status_ret)
+       return NULL;
+
+    /* If the message ID is too long, substitute its sha1 instead. */
+    if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
+       message_id = _notmuch_message_id_compressed (message, message_id);
+
+    term = talloc_asprintf (NULL, "%s%s",
+                           _find_prefix ("id"), message_id);
+    if (term == NULL) {
+       *status_ret = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+       INTERNAL_ERROR ("Failure to ensure database is writable.");
+
+    try {
+       doc.add_term (term, 0);
+       talloc_free (term);
+
+       doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id);
+
+       doc_id = _notmuch_database_generate_doc_id (notmuch);
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+                error.get_msg().c_str());
+       notmuch->exception_reported = true;
+       *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+       return NULL;
+    }
+
+    message = _notmuch_message_create_for_document (notmuch, notmuch,
+                                                   doc_id, doc, status_ret);
+
+    /* We want to inform the caller that we had to create a new
+     * document. */
+    if (*status_ret == NOTMUCH_PRIVATE_STATUS_SUCCESS)
+       *status_ret = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+
+    return message;
+}
+
+static char *
+_notmuch_message_get_term (notmuch_message_t *message,
+                          Xapian::TermIterator &i, Xapian::TermIterator &end,
+                          const char *prefix)
+{
+    int prefix_len = strlen (prefix);
+    char *value;
+
+    i.skip_to (prefix);
+
+    if (i == end)
+       return NULL;
+
+    const std::string &term = *i;
+    if (strncmp (term.c_str(), prefix, prefix_len))
+       return NULL;
+
+    value = talloc_strdup (message, term.c_str() + prefix_len);
+
+#if DEBUG_DATABASE_SANITY
+    i++;
+
+    if (i != end && strncmp ((*i).c_str (), prefix, prefix_len) == 0) {
+       INTERNAL_ERROR ("Mail (doc_id: %d) has duplicate %s terms: %s and %s\n",
+                       message->doc_id, prefix, value,
+                       (*i).c_str () + prefix_len);
+    }
+#endif
+
+    return value;
+}
+
+/*
+ * For special applications where we only want the thread id, reading
+ * in all metadata is a heavy I/O penalty.
+ */
+const char *
+_notmuch_message_get_thread_id_only (notmuch_message_t *message)
+{
+
+    Xapian::TermIterator i = message->doc.termlist_begin ();
+    Xapian::TermIterator end = message->doc.termlist_end ();
+
+    message->thread_id = _notmuch_message_get_term (message, i, end,
+                                                   _find_prefix ("thread"));
+    return message->thread_id;
+}
+
+
+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"),
+       *type_prefix = _find_prefix ("type"),
+       *filename_prefix = _find_prefix ("file-direntry"),
+       *property_prefix = _find_prefix ("property"),
+       *reference_prefix = _find_prefix ("reference"),
+       *replyto_prefix = _find_prefix ("replyto");
+
+    /* We do this all in a single pass because Xapian decompresses the
+     * term list every time you iterate over it.  Thus, while this is
+     * 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);
+           }
+
+           /* 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 references */
+           assert (strcmp (property_prefix, reference_prefix) < 0);
+           if (!message->reference_list) {
+               message->reference_list =
+                   _notmuch_database_get_terms_with_prefix (message, i, end,
+                                                            reference_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
+_notmuch_message_invalidate_metadata (notmuch_message_t *message,
+                                     const char *prefix_name)
+{
+    if (strcmp ("thread", prefix_name) == 0) {
+       talloc_free (message->thread_id);
+       message->thread_id = NULL;
+    }
+
+    if (strcmp ("tag", prefix_name) == 0) {
+       talloc_unlink (message, message->tag_list);
+       message->tag_list = NULL;
+    }
+
+    if (strcmp ("type", prefix_name) == 0) {
+       NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+       NOTMUCH_CLEAR_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+    }
+
+    if (strcmp ("file-direntry", prefix_name) == 0) {
+       talloc_free (message->filename_term_list);
+       talloc_free (message->filename_list);
+       message->filename_term_list = message->filename_list = NULL;
+    }
+
+    if (strcmp ("property", prefix_name) == 0) {
+
+       if (message->property_term_list)
+           talloc_free (message->property_term_list);
+       message->property_term_list = NULL;
+
+       if (message->property_map)
+           talloc_unlink (message, message->property_map);
+
+       message->property_map = NULL;
+    }
+
+    if (strcmp ("replyto", prefix_name) == 0) {
+       talloc_free (message->in_reply_to);
+       message->in_reply_to = NULL;
+    }
+}
+
+unsigned int
+_notmuch_message_get_doc_id (notmuch_message_t *message)
+{
+    return message->doc_id;
+}
+
+const char *
+notmuch_message_get_message_id (notmuch_message_t *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);
+    return message->message_id;
+}
+
+static void
+_notmuch_message_ensure_message_file (notmuch_message_t *message)
+{
+    const char *filename;
+
+    if (message->message_file)
+       return;
+
+    filename = notmuch_message_get_filename (message);
+    if (unlikely (filename == NULL))
+       return;
+
+    message->message_file = _notmuch_message_file_open_ctx (
+       notmuch_message_get_database (message), message, filename);
+}
+
+const char *
+notmuch_message_get_header (notmuch_message_t *message, const char *header)
+{
+    Xapian::valueno slot = Xapian::BAD_VALUENO;
+
+    /* Fetch header from the appropriate xapian value field if
+     * available */
+    if (strcasecmp (header, "from") == 0)
+       slot = NOTMUCH_VALUE_FROM;
+    else if (strcasecmp (header, "subject") == 0)
+       slot = NOTMUCH_VALUE_SUBJECT;
+    else if (strcasecmp (header, "message-id") == 0)
+       slot = NOTMUCH_VALUE_MESSAGE_ID;
+
+    if (slot != Xapian::BAD_VALUENO) {
+       try {
+           std::string value = message->doc.get_value (slot);
+
+           /* If we have NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES, then
+            * empty values indicate empty headers.  If we don't, then
+            * it could just mean we didn't record the header. */
+           if ((message->notmuch->features &
+                NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) ||
+               ! value.empty())
+               return talloc_strdup (message, value.c_str ());
+
+       } catch (Xapian::Error &error) {
+           _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading header: %s\n",
+                    error.get_msg().c_str());
+           message->notmuch->exception_reported = true;
+           return NULL;
+       }
+    }
+
+    /* Otherwise fall back to parsing the file */
+    _notmuch_message_ensure_message_file (message);
+    if (message->message_file == NULL)
+       return NULL;
+
+    return _notmuch_message_file_get_header (message->message_file, header);
+}
+
+/* Return the message ID from the In-Reply-To header of 'message'.
+ *
+ * Returns an empty string ("") if 'message' has no In-Reply-To
+ * header.
+ *
+ * Returns NULL if any error occurs.
+ */
+const char *
+_notmuch_message_get_in_reply_to (notmuch_message_t *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)
+{
+    _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);
+    return message->thread_id;
+}
+
+void
+_notmuch_message_add_reply (notmuch_message_t *message,
+                           notmuch_message_t *reply)
+{
+    _notmuch_message_list_add_message (message->replies, reply);
+}
+
+size_t
+_notmuch_message_get_thread_depth (notmuch_message_t *message) {
+    return message->thread_depth;
+}
+
+void
+_notmuch_message_label_depths (notmuch_message_t *message,
+                              size_t depth)
+{
+    message->thread_depth = depth;
+
+    for (notmuch_messages_t *messages = _notmuch_messages_create (message->replies);
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       notmuch_message_t *child = notmuch_messages_get (messages);
+       _notmuch_message_label_depths (child, depth+1);
+    }
+}
+
+const notmuch_string_list_t *
+_notmuch_message_get_references (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_metadata (message, message->reference_list);
+    return message->reference_list;
+}
+
+static int
+_cmpmsg (const void *pa, const void *pb)
+{
+    notmuch_message_t **a = (notmuch_message_t **) pa;
+    notmuch_message_t **b = (notmuch_message_t **) pb;
+    time_t time_a = notmuch_message_get_date (*a);
+    time_t time_b = notmuch_message_get_date (*b);
+
+    if (time_a == time_b)
+       return 0;
+    else if (time_a < time_b)
+       return -1;
+    else
+       return 1;
+}
+
+notmuch_message_list_t *
+_notmuch_message_sort_subtrees (void *ctx, notmuch_message_list_t *list)
+{
+
+    size_t count = 0;
+    size_t capacity = 16;
+
+    if (! list)
+       return list;
+
+    void *local = talloc_new (NULL);
+    notmuch_message_list_t *new_list = _notmuch_message_list_create (ctx);
+    notmuch_message_t **message_array = talloc_zero_array (local, notmuch_message_t *, capacity);
+
+    for (notmuch_messages_t *messages = _notmuch_messages_create (list);
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       notmuch_message_t *root = notmuch_messages_get (messages);
+       if (count >= capacity) {
+           capacity *= 2;
+           message_array = talloc_realloc (local, message_array, notmuch_message_t *, capacity);
+       }
+       message_array[count++] = root;
+       root->replies = _notmuch_message_sort_subtrees (root, root->replies);
+    }
+
+    qsort (message_array, count, sizeof (notmuch_message_t *), _cmpmsg);
+    for (size_t i = 0; i < count; i++) {
+       _notmuch_message_list_add_message (new_list, message_array[i]);
+    }
+
+    talloc_free (local);
+    talloc_free (list);
+    return new_list;
+}
+
+notmuch_messages_t *
+notmuch_message_get_replies (notmuch_message_t *message)
+{
+    return _notmuch_messages_create (message->replies);
+}
+
+void
+_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
+{
+    Xapian::TermIterator i;
+    size_t prefix_len = 0;
+
+    prefix_len = strlen (prefix);
+
+    while (1) {
+       i = message->doc.termlist_begin ();
+       i.skip_to (prefix);
+
+       /* Terminate loop when no terms remain with desired prefix. */
+       if (i == message->doc.termlist_end () ||
+           strncmp ((*i).c_str (), prefix, prefix_len))
+           break;
+
+       try {
+           message->doc.remove_term ((*i));
+           message->modified = true;
+       } catch (const Xapian::InvalidArgumentError) {
+           /* Ignore failure to remove non-existent term. */
+       }
+    }
+}
+
+
+/* Remove all terms generated by indexing, i.e. not tags or
+ * properties, along with any automatic tags*/
+notmuch_private_status_t
+_notmuch_message_remove_indexed_terms (notmuch_message_t *message)
+{
+    Xapian::TermIterator i;
+
+    const std::string
+       id_prefix = _find_prefix ("id"),
+       property_prefix = _find_prefix ("property"),
+       tag_prefix = _find_prefix ("tag"),
+       type_prefix = _find_prefix ("type");
+
+    for (i = message->doc.termlist_begin ();
+        i != message->doc.termlist_end (); i++) {
+
+       const std::string term = *i;
+
+       if (term.compare (0, type_prefix.size (), type_prefix) == 0)
+           continue;
+
+       if (term.compare (0, id_prefix.size (), id_prefix) == 0)
+           continue;
+
+       if (term.compare (0, property_prefix.size (), property_prefix) == 0)
+           continue;
+
+       if (term.compare (0, tag_prefix.size (), tag_prefix) == 0 &&
+           term.compare (1, strlen("encrypted"), "encrypted") != 0 &&
+           term.compare (1, strlen("signed"), "signed") != 0 &&
+           term.compare (1, strlen("attachment"), "attachment") != 0)
+           continue;
+
+       try {
+           message->doc.remove_term ((*i));
+           message->modified = true;
+       } catch (const Xapian::InvalidArgumentError) {
+           /* Ignore failure to remove non-existent term. */
+       } catch (const Xapian::Error &error) {
+           notmuch_database_t *notmuch = message->notmuch;
+
+           if (!notmuch->exception_reported) {
+               _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+                                     error.get_msg().c_str());
+               notmuch->exception_reported = true;
+           }
+           return NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+       }
+    }
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Return true if p points at "new" or "cur". */
+static bool is_maildir (const char *p)
+{
+    return strcmp (p, "cur") == 0 || strcmp (p, "new") == 0;
+}
+
+/* Add "folder:" term for directory. */
+static notmuch_status_t
+_notmuch_message_add_folder_terms (notmuch_message_t *message,
+                                  const char *directory)
+{
+    char *folder, *last;
+
+    folder = talloc_strdup (NULL, directory);
+    if (! folder)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    /*
+     * If the message file is in a leaf directory named "new" or
+     * "cur", presume maildir and index the parent directory. Thus a
+     * "folder:" prefix search matches messages in the specified
+     * maildir folder, i.e. in the specified directory and its "new"
+     * and "cur" subdirectories.
+     *
+     * Note that this means the "folder:" prefix can't be used for
+     * distinguishing between message files in "new" or "cur". The
+     * "path:" prefix needs to be used for that.
+     *
+     * Note the deliberate difference to _filename_is_in_maildir(). We
+     * don't want to index different things depending on the existence
+     * or non-existence of all maildir sibling directories "new",
+     * "cur", and "tmp". Doing so would be surprising, and difficult
+     * for the user to fix in case all subdirectories were not in
+     * place during indexing.
+     */
+    last = strrchr (folder, '/');
+    if (last) {
+       if (is_maildir (last + 1))
+           *last = '\0';
+    } else if (is_maildir (folder)) {
+       *folder = '\0';
+    }
+
+    _notmuch_message_add_term (message, "folder", folder);
+
+    talloc_free (folder);
+
+    message->modified = true;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+#define RECURSIVE_SUFFIX "/**"
+
+/* Add "path:" terms for directory. */
+static notmuch_status_t
+_notmuch_message_add_path_terms (notmuch_message_t *message,
+                                const char *directory)
+{
+    /* Add exact "path:" term. */
+    _notmuch_message_add_term (message, "path", directory);
+
+    if (strlen (directory)) {
+       char *path, *p;
+
+       path = talloc_asprintf (NULL, "%s%s", directory, RECURSIVE_SUFFIX);
+       if (! path)
+           return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+       /* Add recursive "path:" terms for directory and all parents. */
+       for (p = path + strlen (path) - 1; p > path; p--) {
+           if (*p == '/') {
+               strcpy (p, RECURSIVE_SUFFIX);
+               _notmuch_message_add_term (message, "path", path);
+           }
+       }
+
+       talloc_free (path);
+    }
+
+    /* Recursive all-matching path:** for consistency. */
+    _notmuch_message_add_term (message, "path", "**");
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Add directory based terms for all filenames of the message. */
+static notmuch_status_t
+_notmuch_message_add_directory_terms (void *ctx, notmuch_message_t *message)
+{
+    const char *direntry_prefix = _find_prefix ("file-direntry");
+    int direntry_prefix_len = strlen (direntry_prefix);
+    Xapian::TermIterator i = message->doc.termlist_begin ();
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    for (i.skip_to (direntry_prefix); i != message->doc.termlist_end (); i++) {
+       unsigned int directory_id;
+       const char *direntry, *directory;
+       char *colon;
+       const std::string &term = *i;
+
+       /* Terminate loop at first term without desired prefix. */
+       if (strncmp (term.c_str (), direntry_prefix, direntry_prefix_len))
+           break;
+
+       /* Indicate that there are filenames remaining. */
+       status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+
+       direntry = term.c_str ();
+       direntry += direntry_prefix_len;
+
+       directory_id = strtol (direntry, &colon, 10);
+
+       if (colon == NULL || *colon != ':')
+           INTERNAL_ERROR ("malformed direntry");
+
+       directory = _notmuch_database_get_directory_path (ctx,
+                                                         message->notmuch,
+                                                         directory_id);
+
+       _notmuch_message_add_folder_terms (message, directory);
+       _notmuch_message_add_path_terms (message, directory);
+    }
+
+    return status;
+}
+
+/* Add an additional 'filename' for 'message'.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync. */
+notmuch_status_t
+_notmuch_message_add_filename (notmuch_message_t *message,
+                              const char *filename)
+{
+    const char *relative, *directory;
+    notmuch_status_t status;
+    void *local = talloc_new (message);
+    char *direntry;
+
+    if (filename == NULL)
+       INTERNAL_ERROR ("Message filename cannot be NULL.");
+
+    if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) ||
+       ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER))
+       return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+    relative = _notmuch_database_relative_path (message->notmuch, filename);
+
+    status = _notmuch_database_split_path (local, relative, &directory, NULL);
+    if (status)
+       return status;
+
+    status = _notmuch_database_filename_to_direntry (
+       local, message->notmuch, filename, NOTMUCH_FIND_CREATE, &direntry);
+    if (status)
+       return status;
+
+    /* New file-direntry allows navigating to this message with
+     * notmuch_directory_get_child_files() . */
+    _notmuch_message_add_term (message, "file-direntry", direntry);
+
+    _notmuch_message_add_folder_terms (message, directory);
+    _notmuch_message_add_path_terms (message, directory);
+
+    talloc_free (local);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Remove a particular 'filename' from 'message'.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync.
+ *
+ * If this message still has other filenames, returns
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID.
+ *
+ * Note: This function does not remove a document from the database,
+ * even if the specified filename is the only filename for this
+ * message. For that functionality, see
+ * notmuch_database_remove_message. */
+notmuch_status_t
+_notmuch_message_remove_filename (notmuch_message_t *message,
+                                 const char *filename)
+{
+    void *local = talloc_new (message);
+    char *direntry;
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) ||
+       ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER))
+       return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+
+    status = _notmuch_database_filename_to_direntry (
+       local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
+    if (status || !direntry)
+       return status;
+
+    /* Unlink this file from its parent directory. */
+    private_status = _notmuch_message_remove_term (message,
+                                                  "file-direntry", direntry);
+    status = COERCE_STATUS (private_status,
+                           "Unexpected error from _notmuch_message_remove_term");
+    if (status)
+       return status;
+
+    /* Re-synchronize "folder:" and "path:" terms for this message. */
+
+    /* Remove all "folder:" terms. */
+    _notmuch_message_remove_terms (message, _find_prefix ("folder"));
+
+    /* Remove all "path:" terms. */
+    _notmuch_message_remove_terms (message, _find_prefix ("path"));
+
+    /* Add back terms for all remaining filenames of the message. */
+    status = _notmuch_message_add_directory_terms (local, message);
+
+    talloc_free (local);
+
+    return status;
+}
+
+/* Upgrade the "folder:" prefix from V1 to V2. */
+#define FOLDER_PREFIX_V1       "XFOLDER"
+#define ZFOLDER_PREFIX_V1      "Z" FOLDER_PREFIX_V1
+void
+_notmuch_message_upgrade_folder (notmuch_message_t *message)
+{
+    /* Remove all old "folder:" terms. */
+    _notmuch_message_remove_terms (message, FOLDER_PREFIX_V1);
+
+    /* Remove all old "folder:" stemmed terms. */
+    _notmuch_message_remove_terms (message, ZFOLDER_PREFIX_V1);
+
+    /* Add new boolean "folder:" and "path:" terms. */
+    _notmuch_message_add_directory_terms (message, message);
+}
+
+char *
+_notmuch_message_talloc_copy_data (notmuch_message_t *message)
+{
+    return talloc_strdup (message, message->doc.get_data ().c_str ());
+}
+
+void
+_notmuch_message_clear_data (notmuch_message_t *message)
+{
+    message->doc.set_data ("");
+    message->modified = true;
+}
+
+static void
+_notmuch_message_ensure_filename_list (notmuch_message_t *message)
+{
+    notmuch_string_node_t *node;
+
+    if (message->filename_list)
+       return;
+
+    _notmuch_message_ensure_metadata (message, message->filename_term_list);
+
+    message->filename_list = _notmuch_string_list_create (message);
+    node = message->filename_term_list->head;
+
+    if (!node) {
+       /* A message document created by an old version of notmuch
+        * (prior to rename support) will have the filename in the
+        * data of the document rather than as a file-direntry term.
+        *
+        * It would be nice to do the upgrade of the document directly
+        * here, but the database is likely open in read-only mode. */
+
+       std::string datastr = message->doc.get_data ();
+       const char *data = datastr.c_str ();
+
+       if (data == NULL)
+           INTERNAL_ERROR ("message with no filename");
+
+       _notmuch_string_list_append (message->filename_list, data);
+
+       return;
+    }
+
+    for (; node; node = node->next) {
+       void *local = talloc_new (message);
+       const char *db_path, *directory, *basename, *filename;
+       char *colon, *direntry = NULL;
+       unsigned int directory_id;
+
+       direntry = node->string;
+
+       directory_id = strtol (direntry, &colon, 10);
+
+       if (colon == NULL || *colon != ':')
+           INTERNAL_ERROR ("malformed direntry");
+
+       basename = colon + 1;
+
+       *colon = '\0';
+
+       db_path = notmuch_database_get_path (message->notmuch);
+
+       directory = _notmuch_database_get_directory_path (local,
+                                                         message->notmuch,
+                                                         directory_id);
+
+       if (strlen (directory))
+           filename = talloc_asprintf (message, "%s/%s/%s",
+                                       db_path, directory, basename);
+       else
+           filename = talloc_asprintf (message, "%s/%s",
+                                       db_path, basename);
+
+       _notmuch_string_list_append (message->filename_list, filename);
+
+       talloc_free (local);
+    }
+
+    talloc_free (message->filename_term_list);
+    message->filename_term_list = NULL;
+}
+
+const char *
+notmuch_message_get_filename (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_filename_list (message);
+
+    if (message->filename_list == NULL)
+       return NULL;
+
+    if (message->filename_list->head == NULL ||
+       message->filename_list->head->string == NULL)
+    {
+       INTERNAL_ERROR ("message with no filename");
+    }
+
+    return message->filename_list->head->string;
+}
+
+notmuch_filenames_t *
+notmuch_message_get_filenames (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_filename_list (message);
+
+    return _notmuch_filenames_create (message, message->filename_list);
+}
+
+int
+notmuch_message_count_files (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_filename_list (message);
+
+    return _notmuch_string_list_length (message->filename_list);
+}
+
+notmuch_bool_t
+notmuch_message_get_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag)
+{
+    if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
+       ! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
+       _notmuch_message_ensure_metadata (message, NULL);
+
+    return NOTMUCH_TEST_BIT (message->flags, flag);
+}
+
+void
+notmuch_message_set_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag, notmuch_bool_t enable)
+{
+    if (enable)
+       NOTMUCH_SET_BIT (&message->flags, flag);
+    else
+       NOTMUCH_CLEAR_BIT (&message->flags, flag);
+    NOTMUCH_SET_BIT (&message->lazy_flags, flag);
+}
+
+time_t
+notmuch_message_get_date (notmuch_message_t *message)
+{
+    std::string value;
+
+    try {
+       value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
+    } catch (Xapian::Error &error) {
+       _notmuch_database_log(notmuch_message_get_database (message), "A Xapian exception occurred when reading date: %s\n",
+                error.get_msg().c_str());
+       message->notmuch->exception_reported = true;
+       return 0;
+    }
+
+    if (value.empty ())
+       /* sortable_unserialise is undefined on empty string */
+       return 0;
+    return Xapian::sortable_unserialise (value);
+}
+
+notmuch_tags_t *
+notmuch_message_get_tags (notmuch_message_t *message)
+{
+    notmuch_tags_t *tags;
+
+    _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
+     * in this case it's still used by the message, so we add an
+     * *additional* talloc reference to the list.  As a result, it's
+     * possible to modify the message tags (which talloc_unlink's the
+     * current list from the message) while still iterating because
+     * the iterator will keep the current list alive. */
+    if (!talloc_reference (message, message->tag_list))
+       return NULL;
+
+    return tags;
+}
+
+const char *
+_notmuch_message_get_author (notmuch_message_t *message)
+{
+    return message->author;
+}
+
+void
+_notmuch_message_set_author (notmuch_message_t *message,
+                           const char *author)
+{
+    if (message->author)
+       talloc_free(message->author);
+    message->author = talloc_strdup(message, author);
+    return;
+}
+
+void
+_notmuch_message_set_header_values (notmuch_message_t *message,
+                                   const char *date,
+                                   const char *from,
+                                   const char *subject)
+{
+    time_t time_value;
+
+    /* GMime really doesn't want to see a NULL date, so protect its
+     * sensibilities. */
+    if (date == NULL || *date == '\0') {
+       time_value = 0;
+    } else {
+       time_value = g_mime_utils_header_decode_date_unix (date);
+       /*
+        * Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=779923
+        */
+       if (time_value < 0)
+           time_value = 0;
+    }
+
+    message->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
+                           Xapian::sortable_serialise (time_value));
+    message->doc.add_value (NOTMUCH_VALUE_FROM, from);
+    message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject);
+    message->modified = true;
+}
+
+/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD.  The caller
+ * must call _notmuch_message_sync. */
+void
+_notmuch_message_upgrade_last_mod (notmuch_message_t *message)
+{
+    /* _notmuch_message_sync will update the last modification
+     * revision; we just have to ask it to. */
+    message->modified = true;
+}
+
+/* Synchronize changes made to message->doc out into the database. */
+void
+_notmuch_message_sync (notmuch_message_t *message)
+{
+    Xapian::WritableDatabase *db;
+
+    if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
+       return;
+
+    if (! message->modified)
+       return;
+
+    /* Update the last modification of this message. */
+    if (message->notmuch->features & NOTMUCH_FEATURE_LAST_MOD)
+       /* sortable_serialise gives a reasonably compact encoding,
+        * which directly translates to reduced IO when scanning the
+        * value stream.  Since it's built for doubles, we only get 53
+        * effective bits, but that's still enough for the database to
+        * last a few centuries at 1 million revisions per second. */
+       message->doc.add_value (NOTMUCH_VALUE_LAST_MOD,
+                               Xapian::sortable_serialise (
+                                   _notmuch_database_new_revision (
+                                       message->notmuch)));
+
+    db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
+    db->replace_document (message->doc_id, message->doc);
+    message->modified = false;
+}
+
+/* Delete a message document from the database, leaving a ghost
+ * message in its place */
+notmuch_status_t
+_notmuch_message_delete (notmuch_message_t *message)
+{
+    notmuch_status_t status;
+    Xapian::WritableDatabase *db;
+    const char *mid, *tid, *query_string;
+    notmuch_message_t *ghost;
+    notmuch_private_status_t private_status;
+    notmuch_database_t *notmuch;
+    notmuch_query_t *query;
+    unsigned int count = 0;
+    bool is_ghost;
+
+    mid = notmuch_message_get_message_id (message);
+    tid = notmuch_message_get_thread_id (message);
+    notmuch = message->notmuch;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db->delete_document (message->doc_id);
+
+    /* if this was a ghost to begin with, we are done */
+    private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
+    if (private_status)
+       return COERCE_STATUS (private_status,
+                             "Error trying to determine whether message was a ghost");
+    if (is_ghost)
+       return NOTMUCH_STATUS_SUCCESS;
+
+    query_string = talloc_asprintf (message, "thread:%s", tid);
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+    status = notmuch_query_count_messages (query, &count);
+    if (status) {
+       notmuch_query_destroy (query);
+       return status;
+    }
+
+    if (count > 0) {
+       /* reintroduce a ghost in its place because there are still
+        * other active messages in this thread: */
+       ghost = _notmuch_message_create_for_message_id (notmuch, mid, &private_status);
+       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           private_status = _notmuch_message_initialize_ghost (ghost, tid);
+           if (! private_status)
+               _notmuch_message_sync (ghost);
+       } else if (private_status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+           /* this is deeply weird, and we should not have gotten
+              into this state.  is there a better error message to
+              return here? */
+           status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+       }
+
+       notmuch_message_destroy (ghost);
+       status = COERCE_STATUS (private_status, "Error converting to ghost message");
+    } else {
+       /* the thread is empty; drop all ghost messages from it */
+       notmuch_messages_t *messages;
+       status = _notmuch_query_search_documents (query,
+                                                 "ghost",
+                                                 &messages);
+       if (status == NOTMUCH_STATUS_SUCCESS) {
+           notmuch_status_t last_error = NOTMUCH_STATUS_SUCCESS;
+           while (notmuch_messages_valid (messages)) {
+               message = notmuch_messages_get (messages);
+               status = _notmuch_message_delete (message);
+               if (status) /* we'll report the last failure we see;
+                            * if there is more than one failure, we
+                            * forget about previous ones */
+                   last_error = status;
+               notmuch_message_destroy (message);
+               notmuch_messages_move_to_next (messages);
+           }
+           status = last_error;
+       }
+    }
+    notmuch_query_destroy (query);
+    return status;
+}
+
+/* Transform a blank message into a ghost message.  The caller must
+ * _notmuch_message_sync the message. */
+notmuch_private_status_t
+_notmuch_message_initialize_ghost (notmuch_message_t *message,
+                                  const char *thread_id)
+{
+    notmuch_private_status_t status;
+
+    status = _notmuch_message_add_term (message, "type", "ghost");
+    if (status)
+       return status;
+    status = _notmuch_message_add_term (message, "thread", thread_id);
+    if (status)
+       return status;
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Ensure that 'message' is not holding any file object open. Future
+ * calls to various functions will still automatically open the
+ * message file as needed.
+ */
+void
+_notmuch_message_close (notmuch_message_t *message)
+{
+    if (message->message_file) {
+       _notmuch_message_file_close (message->message_file);
+       message->message_file = NULL;
+    }
+}
+
+/* Add a name:value term to 'message', (the actual term will be
+ * encoded by prefixing the value with a short prefix). See
+ * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
+ * names to prefix values.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync. */
+notmuch_private_status_t
+_notmuch_message_add_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value)
+{
+
+    char *term;
+
+    if (value == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term = talloc_asprintf (message, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    if (strlen (term) > NOTMUCH_TERM_MAX)
+       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+    message->doc.add_term (term, 0);
+    message->modified = true;
+
+    talloc_free (term);
+
+    _notmuch_message_invalidate_metadata (message, prefix_name);
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Parse 'text' and add a term to 'message' for each parsed word. Each
+ * term will be added both prefixed (if prefix_name is not NULL) and
+ * also non-prefixed). */
+notmuch_private_status_t
+_notmuch_message_gen_terms (notmuch_message_t *message,
+                           const char *prefix_name,
+                           const char *text)
+{
+    Xapian::TermGenerator *term_gen = message->notmuch->term_gen;
+
+    if (text == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term_gen->set_document (message->doc);
+
+    if (prefix_name) {
+       const char *prefix = _find_prefix (prefix_name);
+
+       term_gen->set_termpos (message->termpos);
+       term_gen->index_text (text, 1, prefix);
+       /* 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);
+    term_gen->index_text (text);
+    /* Create a term gap, as above. */
+    message->termpos = term_gen->get_termpos () + 100;
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Remove a name:value term from 'message', (the actual term will be
+ * encoded by prefixing the value with a short prefix). See
+ * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
+ * names to prefix values.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_sync. */
+notmuch_private_status_t
+_notmuch_message_remove_term (notmuch_message_t *message,
+                             const char *prefix_name,
+                             const char *value)
+{
+    char *term;
+
+    if (value == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term = talloc_asprintf (message, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    if (strlen (term) > NOTMUCH_TERM_MAX)
+       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+    try {
+       message->doc.remove_term (term);
+       message->modified = true;
+    } catch (const Xapian::InvalidArgumentError) {
+       /* We'll let the philosophers try to wrestle with the
+        * question of whether failing to remove that which was not
+        * there in the first place is failure. For us, we'll silently
+        * consider it all good. */
+    }
+
+    talloc_free (term);
+
+    _notmuch_message_invalidate_metadata (message, prefix_name);
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+notmuch_private_status_t
+_notmuch_message_has_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value,
+                          bool *result)
+{
+    char *term;
+    bool out = false;
+    notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
+
+    if (value == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term = talloc_asprintf (message, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    if (strlen (term) > NOTMUCH_TERM_MAX)
+       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+    try {
+       /* Look for the exact term */
+       Xapian::TermIterator i = message->doc.termlist_begin ();
+       i.skip_to (term);
+       if (i != message->doc.termlist_end () &&
+           !strcmp ((*i).c_str (), term))
+           out = true;
+    } catch (Xapian::Error &error) {
+       status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+    }
+    talloc_free (term);
+
+    *result = out;
+    return status;
+}
+
+notmuch_status_t
+notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
+{
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    if (tag == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strlen (tag) > NOTMUCH_TAG_MAX)
+       return NOTMUCH_STATUS_TAG_TOO_LONG;
+
+    private_status = _notmuch_message_add_term (message, "tag", tag);
+    if (private_status) {
+       INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
+                       private_status);
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
+{
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    if (tag == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strlen (tag) > NOTMUCH_TAG_MAX)
+       return NOTMUCH_STATUS_TAG_TOO_LONG;
+
+    private_status = _notmuch_message_remove_term (message, "tag", tag);
+    if (private_status) {
+       INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
+                       private_status);
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Is the given filename within a maildir directory?
+ *
+ * Specifically, is the final directory component of 'filename' either
+ * "cur" or "new". If so, return a pointer to that final directory
+ * component within 'filename'. If not, return NULL.
+ *
+ * A non-NULL return value is guaranteed to be a valid string pointer
+ * pointing to the characters "new/" or "cur/", (but not
+ * NUL-terminated).
+ */
+static const char *
+_filename_is_in_maildir (const char *filename)
+{
+    const char *slash, *dir = NULL;
+
+    /* Find the last '/' separating directory from filename. */
+    slash = strrchr (filename, '/');
+    if (slash == NULL)
+       return NULL;
+
+    /* Jump back 4 characters to where the previous '/' will be if the
+     * directory is named "cur" or "new". */
+    if (slash - filename < 4)
+       return NULL;
+
+    slash -= 4;
+
+    if (*slash != '/')
+       return NULL;
+
+    dir = slash + 1;
+
+    if (STRNCMP_LITERAL (dir, "cur/") == 0 ||
+       STRNCMP_LITERAL (dir, "new/") == 0)
+    {
+       return dir;
+    }
+
+    return NULL;
+}
+
+static void
+_ensure_maildir_flags (notmuch_message_t *message, bool force)
+{
+    const char *flags;
+    notmuch_filenames_t *filenames;
+    const char *filename, *dir;
+    char *combined_flags = talloc_strdup (message, "");
+    int seen_maildir_info = 0;
+
+    if (message->maildir_flags) {
+       if (force) {
+           talloc_free (message->maildir_flags);
+           message->maildir_flags = NULL;
+       }
+    }
+
+    for (filenames = notmuch_message_get_filenames (message);
+        notmuch_filenames_valid (filenames);
+        notmuch_filenames_move_to_next (filenames))
+    {
+       filename = notmuch_filenames_get (filenames);
+       dir = _filename_is_in_maildir (filename);
+
+       if (! dir)
+           continue;
+
+       flags = strstr (filename, ":2,");
+       if (flags) {
+           seen_maildir_info = 1;
+           flags += 3;
+           combined_flags = talloc_strdup_append (combined_flags, flags);
+       } else if (STRNCMP_LITERAL (dir, "new/") == 0) {
+           /* Messages are delivered to new/ with no "info" part, but
+            * they effectively have default maildir flags.  According
+            * to the spec, we should ignore the info part for
+            * messages in new/, but some MUAs (mutt) can set maildir
+            * flags on messages in new/, so we're liberal in what we
+            * accept. */
+           seen_maildir_info = 1;
+       }
+    }
+    if (seen_maildir_info)
+       message->maildir_flags = combined_flags;
+}
+
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag)
+{
+    _ensure_maildir_flags (message, false);
+    return message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+}
+
+notmuch_status_t
+notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+{
+    notmuch_status_t status;
+    unsigned i;
+
+    _ensure_maildir_flags (message, true);
+    /* If none of the filenames have any maildir info field (not even
+     * an empty info with no flags set) then there's no information to
+     * go on, so do nothing. */
+    if (! message->maildir_flags)
+       return NOTMUCH_STATUS_SUCCESS;
+
+    status = notmuch_message_freeze (message);
+    if (status)
+       return status;
+
+    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
+       if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL)
+           ^
+           flag2tag[i].inverse)
+       {
+           status = notmuch_message_add_tag (message, flag2tag[i].tag);
+       } else {
+           status = notmuch_message_remove_tag (message, flag2tag[i].tag);
+       }
+       if (status)
+           return status;
+    }
+    status = notmuch_message_thaw (message);
+
+    return status;
+}
+
+/* From the set of tags on 'message' and the flag2tag table, compute a
+ * set of maildir-flag actions to be taken, (flags that should be
+ * either set or cleared).
+ *
+ * The result is returned as two talloced strings: to_set, and to_clear
+ */
+static void
+_get_maildir_flag_actions (notmuch_message_t *message,
+                          char **to_set_ret,
+                          char **to_clear_ret)
+{
+    char *to_set, *to_clear;
+    notmuch_tags_t *tags;
+    const char *tag;
+    unsigned i;
+
+    to_set = talloc_strdup (message, "");
+    to_clear = talloc_strdup (message, "");
+
+    /* First, find flags for all set tags. */
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       for (i = 0; i < ARRAY_SIZE (flag2tag); i++) {
+           if (strcmp (tag, flag2tag[i].tag) == 0) {
+               if (flag2tag[i].inverse)
+                   to_clear = talloc_asprintf_append (to_clear,
+                                                      "%c",
+                                                      flag2tag[i].flag);
+               else
+                   to_set = talloc_asprintf_append (to_set,
+                                                    "%c",
+                                                    flag2tag[i].flag);
+           }
+       }
+    }
+
+    /* Then, find the flags for all tags not present. */
+    for (i = 0; i < ARRAY_SIZE (flag2tag); i++) {
+       if (flag2tag[i].inverse) {
+           if (strchr (to_clear, flag2tag[i].flag) == NULL)
+               to_set = talloc_asprintf_append (to_set, "%c", flag2tag[i].flag);
+       } else {
+           if (strchr (to_set, flag2tag[i].flag) == NULL)
+               to_clear = talloc_asprintf_append (to_clear, "%c", flag2tag[i].flag);
+       }
+    }
+
+    *to_set_ret = to_set;
+    *to_clear_ret = to_clear;
+}
+
+/* Given 'filename' and a set of maildir flags to set and to clear,
+ * compute the new maildir filename.
+ *
+ * If the existing filename is in the directory "new", the new
+ * filename will be in the directory "cur", except for the case when
+ * no flags are changed and the existing filename does not contain
+ * maildir info (starting with ",2:").
+ *
+ * After a sequence of ":2," in the filename, any subsequent
+ * single-character flags will be added or removed according to the
+ * characters in flags_to_set and flags_to_clear. Any existing flags
+ * not mentioned in either string will remain. The final list of flags
+ * will be in ASCII order.
+ *
+ * If the original flags seem invalid, (repeated characters or
+ * non-ASCII ordering of flags), this function will return NULL
+ * (meaning that renaming would not be safe and should not occur).
+ */
+static char*
+_new_maildir_filename (void *ctx,
+                      const char *filename,
+                      const char *flags_to_set,
+                      const char *flags_to_clear)
+{
+    const char *info, *flags;
+    unsigned int flag, last_flag;
+    char *filename_new, *dir;
+    char flag_map[128];
+    int flags_in_map = 0;
+    bool flags_changed = false;
+    unsigned int i;
+    char *s;
+
+    memset (flag_map, 0, sizeof (flag_map));
+
+    info = strstr (filename, ":2,");
+
+    if (info == NULL) {
+       info = filename + strlen(filename);
+    } else {
+       /* Loop through existing flags in filename. */
+       for (flags = info + 3, last_flag = 0;
+            *flags;
+            last_flag = flag, flags++)
+       {
+           flag = *flags;
+
+           /* Original flags not in ASCII order. Abort. */
+           if (flag < last_flag)
+               return NULL;
+
+           /* Non-ASCII flag. Abort. */
+           if (flag > sizeof(flag_map) - 1)
+               return NULL;
+
+           /* Repeated flag value. Abort. */
+           if (flag_map[flag])
+               return NULL;
+
+           flag_map[flag] = 1;
+           flags_in_map++;
+       }
+    }
+
+    /* Then set and clear our flags from tags. */
+    for (flags = flags_to_set; *flags; flags++) {
+       flag = *flags;
+       if (flag_map[flag] == 0) {
+           flag_map[flag] = 1;
+           flags_in_map++;
+           flags_changed = true;
+       }
+    }
+
+    for (flags = flags_to_clear; *flags; flags++) {
+       flag = *flags;
+       if (flag_map[flag]) {
+           flag_map[flag] = 0;
+           flags_in_map--;
+           flags_changed = true;
+       }
+    }
+
+    /* Messages in new/ without maildir info can be kept in new/ if no
+     * flags have changed. */
+    dir = (char *) _filename_is_in_maildir (filename);
+    if (dir && STRNCMP_LITERAL (dir, "new/") == 0 && !*info && !flags_changed)
+       return talloc_strdup (ctx, filename);
+
+    filename_new = (char *) talloc_size (ctx,
+                                        info - filename +
+                                        strlen (":2,") + flags_in_map + 1);
+    if (unlikely (filename_new == NULL))
+       return NULL;
+
+    strncpy (filename_new, filename, info - filename);
+    filename_new[info - filename] = '\0';
+
+    strcat (filename_new, ":2,");
+
+    s = filename_new + strlen (filename_new);
+    for (i = 0; i < sizeof (flag_map); i++)
+    {
+       if (flag_map[i]) {
+           *s = i;
+           s++;
+       }
+    }
+    *s = '\0';
+
+    /* If message is in new/ move it under cur/. */
+    dir = (char *) _filename_is_in_maildir (filename_new);
+    if (dir && STRNCMP_LITERAL (dir, "new/") == 0)
+       memcpy (dir, "cur/", 4);
+
+    return filename_new;
+}
+
+notmuch_status_t
+notmuch_message_tags_to_maildir_flags (notmuch_message_t *message)
+{
+    notmuch_filenames_t *filenames;
+    const char *filename;
+    char *filename_new;
+    char *to_set, *to_clear;
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+    _get_maildir_flag_actions (message, &to_set, &to_clear);
+
+    for (filenames = notmuch_message_get_filenames (message);
+        notmuch_filenames_valid (filenames);
+        notmuch_filenames_move_to_next (filenames))
+    {
+       filename = notmuch_filenames_get (filenames);
+
+       if (! _filename_is_in_maildir (filename))
+           continue;
+
+       filename_new = _new_maildir_filename (message, filename,
+                                             to_set, to_clear);
+       if (filename_new == NULL)
+           continue;
+
+       if (strcmp (filename, filename_new)) {
+           int err;
+           notmuch_status_t new_status;
+
+           err = rename (filename, filename_new);
+           if (err)
+               continue;
+
+           new_status = _notmuch_message_remove_filename (message,
+                                                          filename);
+           /* Hold on to only the first error. */
+           if (! status && new_status
+               && new_status != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+               status = new_status;
+               continue;
+           }
+
+           new_status = _notmuch_message_add_filename (message,
+                                                       filename_new);
+           /* Hold on to only the first error. */
+           if (! status && new_status) {
+               status = new_status;
+               continue;
+           }
+
+           _notmuch_message_sync (message);
+       }
+
+       talloc_free (filename_new);
+    }
+
+    talloc_free (to_set);
+    talloc_free (to_clear);
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_message_remove_all_tags (notmuch_message_t *message)
+{
+    notmuch_private_status_t private_status;
+    notmuch_status_t status;
+    notmuch_tags_t *tags;
+    const char *tag;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       private_status = _notmuch_message_remove_term (message, "tag", tag);
+       if (private_status) {
+           INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
+                           private_status);
+       }
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+
+    talloc_free (tags);
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_freeze (notmuch_message_t *message)
+{
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    message->frozen++;
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_thaw (notmuch_message_t *message)
+{
+    notmuch_status_t status;
+
+    status = _notmuch_database_ensure_writable (message->notmuch);
+    if (status)
+       return status;
+
+    if (message->frozen > 0) {
+       message->frozen--;
+       if (message->frozen == 0)
+           _notmuch_message_sync (message);
+       return NOTMUCH_STATUS_SUCCESS;
+    } else {
+       return NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW;
+    }
+}
+
+void
+notmuch_message_destroy (notmuch_message_t *message)
+{
+    talloc_free (message);
+}
+
+notmuch_database_t *
+notmuch_message_get_database (const notmuch_message_t *message)
+{
+    return message->notmuch;
+}
+
+static void
+_notmuch_message_ensure_property_map (notmuch_message_t *message)
+{
+    notmuch_string_node_t *node;
+
+    if (message->property_map)
+       return;
+
+    _notmuch_message_ensure_metadata (message, message->property_term_list);
+
+    message->property_map = _notmuch_string_map_create (message);
+
+    for (node = message->property_term_list->head; node; node = node->next) {
+       const char *key;
+       char *value;
+
+       value = strchr(node->string, '=');
+       if (!value)
+           INTERNAL_ERROR ("malformed property term");
+
+       *value = '\0';
+       value++;
+       key = node->string;
+
+       _notmuch_string_map_append (message->property_map, key, value);
+
+    }
+
+    talloc_free (message->property_term_list);
+    message->property_term_list = NULL;
+}
+
+notmuch_string_map_t *
+_notmuch_message_property_map (notmuch_message_t *message)
+{
+    _notmuch_message_ensure_property_map (message);
+
+    return message->property_map;
+}
+
+bool
+_notmuch_message_frozen (notmuch_message_t *message)
+{
+    return message->frozen;
+}
+
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+                        notmuch_indexopts_t *indexopts)
+{
+    notmuch_database_t *notmuch = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+    notmuch_filenames_t *orig_filenames = NULL;
+    const char *orig_thread_id = NULL;
+    notmuch_message_file_t *message_file = NULL;
+
+    int found = 0;
+
+    if (message == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    /* Save in case we need to delete message */
+    orig_thread_id = notmuch_message_get_thread_id (message);
+    if (!orig_thread_id) {
+       /* XXX TODO: make up new error return? */
+       INTERNAL_ERROR ("message without thread-id");
+    }
+
+    /* strdup it because the metadata may be invalidated */
+    orig_thread_id = talloc_strdup (message, orig_thread_id);
+
+    notmuch = notmuch_message_get_database (message);
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    orig_filenames = notmuch_message_get_filenames (message);
+
+    private_status = _notmuch_message_remove_indexed_terms (message);
+    if (private_status) {
+       ret = COERCE_STATUS(private_status, "error removing terms");
+       goto DONE;
+    }
+
+    ret = notmuch_message_remove_all_properties_with_prefix (message, "index.");
+    if (ret)
+       goto DONE; /* XXX TODO: distinguish from other error returns above? */
+    if (indexopts && notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_FALSE) {
+       ret = notmuch_message_remove_all_properties (message, "session-key");
+       if (ret)
+           goto DONE;
+    }
+
+    /* re-add the filenames with the associated indexopts */
+    for (; notmuch_filenames_valid (orig_filenames);
+        notmuch_filenames_move_to_next (orig_filenames)) {
+
+       const char *date;
+       const char *from, *to, *subject;
+       char *message_id = NULL;
+       const char *thread_id = NULL;
+
+       const char *filename = notmuch_filenames_get (orig_filenames);
+
+       message_file = _notmuch_message_file_open (notmuch, filename);
+       if (message_file == NULL)
+           continue;
+
+       ret = _notmuch_message_file_get_headers (message_file,
+                                                &from, &subject, &to, &date,
+                                                &message_id);
+       if (ret)
+           goto DONE;
+
+       /* XXX TODO: deal with changing message id? */
+
+       _notmuch_message_add_filename (message, filename);
+
+       ret = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                        message_file,
+                                                        &thread_id);
+       if (ret)
+           goto DONE;
+
+       if (thread_id == NULL)
+           thread_id = orig_thread_id;
+
+       _notmuch_message_add_term (message, "thread", thread_id);
+       /* Take header values only from first filename */
+       if (found == 0)
+           _notmuch_message_set_header_values (message, date, from, subject);
+
+       ret = _notmuch_message_index_file (message, indexopts, message_file);
+
+       if (ret == NOTMUCH_STATUS_FILE_ERROR)
+           continue;
+       if (ret)
+           goto DONE;
+
+       found++;
+       _notmuch_message_file_close (message_file);
+       message_file = NULL;
+    }
+    if (found == 0) {
+       /* put back thread id to help cleanup */
+       _notmuch_message_add_term (message, "thread", orig_thread_id);
+       ret = _notmuch_message_delete (message);
+    } else {
+       _notmuch_message_sync (message);
+    }
+
+ DONE:
+    if (message_file)
+       _notmuch_message_file_close (message_file);
+
+    /* XXX TODO destroy orig_filenames? */
+    return ret;
+}
diff --git a/lib/messages.c b/lib/messages.c
new file mode 100644 (file)
index 0000000..04fa19f
--- /dev/null
@@ -0,0 +1,194 @@
+/* messages.c - Iterator for a set of messages
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include <glib.h>
+
+/* Create a new notmuch_message_list_t object, with 'ctx' as its
+ * talloc owner.
+ *
+ * This function can return NULL in case of out-of-memory.
+ */
+notmuch_message_list_t *
+_notmuch_message_list_create (const void *ctx)
+{
+    notmuch_message_list_t *list;
+
+    list = talloc (ctx, notmuch_message_list_t);
+    if (unlikely (list == NULL))
+       return NULL;
+
+    list->head = NULL;
+    list->tail = &list->head;
+
+    return list;
+}
+
+/* Append 'message' to the end of 'list'. */
+void
+_notmuch_message_list_add_message (notmuch_message_list_t *list,
+                                  notmuch_message_t *message)
+{
+    notmuch_message_node_t *node = talloc (list, notmuch_message_node_t);
+
+    node->message = message;
+    node->next = NULL;
+
+    *(list->tail) = node;
+    list->tail = &node->next;
+}
+
+bool
+_notmuch_message_list_empty (notmuch_message_list_t *list)
+{
+    if (list == NULL)
+       return TRUE;
+
+    return (list->head == NULL);
+}
+
+notmuch_messages_t *
+_notmuch_messages_create (notmuch_message_list_t *list)
+{
+    notmuch_messages_t *messages;
+
+    if (list->head == NULL)
+       return NULL;
+
+    messages = talloc (list, notmuch_messages_t);
+    if (unlikely (messages == NULL))
+       return NULL;
+
+    messages->is_of_list_type = true;
+    messages->iterator = list->head;
+
+    return messages;
+}
+
+/* We're using the "is_of_type_list" to conditionally defer to the
+ * notmuch_mset_messages_t implementation of notmuch_messages_t in
+ * query.cc. It's ugly that that's over in query.cc, and it's ugly
+ * that we're not using a union here. Both of those uglies are due to
+ * C++:
+ *
+ *     1. I didn't want to force a C++ header file onto
+ *        notmuch-private.h and suddenly subject all our code to a
+ *        C++ compiler and its rules.
+ *
+ *     2. C++ won't allow me to put C++ objects, (with non-trivial
+ *        constructors) into a union anyway. Even though I'd
+ *        carefully control object construction with placement new
+ *        anyway. *sigh*
+ */
+notmuch_bool_t
+notmuch_messages_valid (notmuch_messages_t *messages)
+{
+    if (messages == NULL)
+       return false;
+
+    if (! messages->is_of_list_type)
+       return _notmuch_mset_messages_valid (messages);
+
+    return (messages->iterator != NULL);
+}
+
+bool
+_notmuch_messages_has_next (notmuch_messages_t *messages)
+{
+    if (! notmuch_messages_valid (messages))
+       return false;
+
+    if (! messages->is_of_list_type)
+       INTERNAL_ERROR("_notmuch_messages_has_next not implimented for msets");
+
+    return (messages->iterator->next != NULL);
+}
+
+notmuch_message_t *
+notmuch_messages_get (notmuch_messages_t *messages)
+{
+    if (! messages->is_of_list_type)
+       return _notmuch_mset_messages_get (messages);
+
+    if (messages->iterator == NULL)
+       return NULL;
+
+    return messages->iterator->message;
+}
+
+void
+notmuch_messages_move_to_next (notmuch_messages_t *messages)
+{
+    if (! messages->is_of_list_type) {
+       _notmuch_mset_messages_move_to_next (messages);
+       return;
+    }
+
+    if (messages->iterator == NULL)
+       return;
+
+    messages->iterator = messages->iterator->next;
+}
+
+void
+notmuch_messages_destroy (notmuch_messages_t *messages)
+{
+    talloc_free (messages);
+}
+
+
+notmuch_tags_t *
+notmuch_messages_collect_tags (notmuch_messages_t *messages)
+{
+    notmuch_string_list_t *tags;
+    notmuch_tags_t *msg_tags;
+    notmuch_message_t *msg;
+    GHashTable *htable;
+    GList *keys, *l;
+    const char *tag;
+
+    tags = _notmuch_string_list_create (messages);
+    if (tags == NULL) return NULL;
+
+    htable = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL);
+
+    while ((msg = notmuch_messages_get (messages))) {
+       msg_tags = notmuch_message_get_tags (msg);
+       while ((tag = notmuch_tags_get (msg_tags))) {
+           g_hash_table_insert (htable, xstrdup (tag), NULL);
+           notmuch_tags_move_to_next (msg_tags);
+       }
+       notmuch_tags_destroy (msg_tags);
+       notmuch_message_destroy (msg);
+       notmuch_messages_move_to_next (messages);
+    }
+
+    keys = g_hash_table_get_keys (htable);
+    for (l = keys; l; l = l->next) {
+       _notmuch_string_list_append (tags, (char *)l->data);
+    }
+
+    g_list_free (keys);
+    g_hash_table_destroy (htable);
+
+    _notmuch_string_list_sort (tags);
+    return _notmuch_tags_create (messages, tags);
+}
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
new file mode 100644 (file)
index 0000000..df32d39
--- /dev/null
@@ -0,0 +1,699 @@
+/* notmuch-private.h - Internal interfaces for notmuch.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_PRIVATE_H
+#define NOTMUCH_PRIVATE_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* For getline and asprintf */
+#endif
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "compat.h"
+
+#include "notmuch.h"
+
+NOTMUCH_BEGIN_DECLS
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <talloc.h>
+
+#include "gmime-extra.h"
+
+#include "xutil.h"
+#include "error_util.h"
+#include "string-util.h"
+#include "crypto.h"
+
+#ifdef DEBUG
+# define DEBUG_DATABASE_SANITY 1
+# define DEBUG_THREADING 1
+# define DEBUG_QUERY 1
+#endif
+
+#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - 2*!(pred)]))
+
+#define STRNCMP_LITERAL(var, literal) \
+    strncmp ((var), (literal), sizeof (literal) - 1)
+
+/* Robust bit test/set/reset macros */
+#define _NOTMUCH_VALID_BIT(bit) \
+    ((bit) >= 0 && ((unsigned long) bit) < CHAR_BIT * sizeof (unsigned long long))
+#define NOTMUCH_TEST_BIT(val, bit) \
+    (_NOTMUCH_VALID_BIT(bit) ? !!((val) & (1ull << (bit))) : 0)
+#define NOTMUCH_SET_BIT(valp, bit) \
+    (_NOTMUCH_VALID_BIT(bit) ? (*(valp) |= (1ull << (bit))) : *(valp))
+#define NOTMUCH_CLEAR_BIT(valp,  bit) \
+    (_NOTMUCH_VALID_BIT(bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp))
+
+#define unused(x) x __attribute__ ((unused))
+
+/* Thanks to Andrew Tridgell's (SAMBA's) talloc for this definition of
+ * unlikely. The talloc source code comes to us via the GNU LGPL v. 3.
+ */
+/* these macros gain us a few percent of speed on gcc */
+#if (__GNUC__ >= 3)
+/* the strange !! is to ensure that __builtin_expect() takes either 0 or 1
+   as its first argument */
+#ifndef likely
+#define likely(x)   __builtin_expect(!!(x), 1)
+#endif
+#ifndef unlikely
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#endif
+#else
+#ifndef likely
+#define likely(x) (x)
+#endif
+#ifndef unlikely
+#define unlikely(x) (x)
+#endif
+#endif
+
+typedef enum {
+    NOTMUCH_VALUE_TIMESTAMP = 0,
+    NOTMUCH_VALUE_MESSAGE_ID,
+    NOTMUCH_VALUE_FROM,
+    NOTMUCH_VALUE_SUBJECT,
+    NOTMUCH_VALUE_LAST_MOD,
+} notmuch_value_t;
+
+/* Xapian (with flint backend) complains if we provide a term longer
+ * than this, but I haven't yet found a way to query the limit
+ * programmatically. */
+#define NOTMUCH_TERM_MAX 245
+
+#define NOTMUCH_METADATA_THREAD_ID_PREFIX "thread_id_"
+
+/* For message IDs we have to be even more restrictive. Beyond fitting
+ * into the term limit, we also use message IDs to construct
+ * metadata-key values. And the documentation says that these should
+ * be restricted to about 200 characters. (The actual limit for the
+ * chert backend at least is 252.)
+ */
+#define NOTMUCH_MESSAGE_ID_MAX (200 - sizeof (NOTMUCH_METADATA_THREAD_ID_PREFIX))
+
+typedef enum _notmuch_private_status {
+    /* First, copy all the public status values. */
+    NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS,
+    NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
+    NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE = NOTMUCH_STATUS_READ_ONLY_DATABASE,
+    NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+    NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL,
+    NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER,
+    NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG,
+    NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+
+    /* Then add our own private values. */
+    NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG = NOTMUCH_STATUS_LAST_STATUS,
+    NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND,
+
+    NOTMUCH_PRIVATE_STATUS_LAST_STATUS
+} notmuch_private_status_t;
+
+/* Coerce a notmuch_private_status_t value to a notmuch_status_t
+ * value, generating an internal error if the private value is equal
+ * to or greater than NOTMUCH_STATUS_LAST_STATUS. (The idea here is
+ * that the caller has previously handled any expected
+ * notmuch_private_status_t values.)
+ *
+ * Note that the function _internal_error does not return. Evaluating
+ * to NOTMUCH_STATUS_SUCCESS is done purely to appease the compiler.
+ */
+#define COERCE_STATUS(private_status, format, ...)                     \
+    ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\
+     ?                                                                 \
+     _internal_error (format " (%s).\n",                               \
+                     ##__VA_ARGS__,                                    \
+                     __location__),                                    \
+     (notmuch_status_t) NOTMUCH_PRIVATE_STATUS_SUCCESS                 \
+     :                                                                 \
+     (notmuch_status_t) private_status)
+
+/* Flags shared by various lookup functions. */
+typedef enum _notmuch_find_flags {
+    /* Lookup without creating any documents.  This is the default
+     * behavior. */
+    NOTMUCH_FIND_LOOKUP = 0,
+    /* If set, create the necessary document (or documents) if they
+     * are missing.  Requires a read/write database. */
+    NOTMUCH_FIND_CREATE = 1<<0,
+} notmuch_find_flags_t;
+
+typedef struct _notmuch_doc_id_set notmuch_doc_id_set_t;
+
+/* database.cc */
+
+/* Lookup a prefix value by name.
+ *
+ * XXX: This should really be static inside of message.cc, and we can
+ * do that once we convert database.cc to use the
+ * _notmuch_message_add/remove_term functions. */
+const char *
+_find_prefix (const char *name);
+
+char *
+_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, ...);
+
+void
+_notmuch_database_log_append (notmuch_database_t *notmuch,
+                             const char *format, ...);
+
+unsigned long
+_notmuch_database_new_revision (notmuch_database_t *notmuch);
+
+const char *
+_notmuch_database_relative_path (notmuch_database_t *notmuch,
+                                const char *path);
+
+notmuch_status_t
+_notmuch_database_split_path (void *ctx,
+                             const char *path,
+                             const char **directory,
+                             const char **basename);
+
+const char *
+_notmuch_database_get_directory_db_path (const char *path);
+
+unsigned int
+_notmuch_database_generate_doc_id (notmuch_database_t *notmuch);
+
+notmuch_private_status_t
+_notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch,
+                                     const char *prefix_name,
+                                     const char *value,
+                                     unsigned int *doc_id);
+
+notmuch_status_t
+_notmuch_database_find_directory_id (notmuch_database_t *database,
+                                    const char *path,
+                                    notmuch_find_flags_t flags,
+                                    unsigned int *directory_id);
+
+const char *
+_notmuch_database_get_directory_path (void *ctx,
+                                     notmuch_database_t *notmuch,
+                                     unsigned int doc_id);
+
+notmuch_status_t
+_notmuch_database_filename_to_direntry (void *ctx,
+                                       notmuch_database_t *notmuch,
+                                       const char *filename,
+                                       notmuch_find_flags_t flags,
+                                       char **direntry);
+
+/* directory.cc */
+
+notmuch_directory_t *
+_notmuch_directory_create (notmuch_database_t *notmuch,
+                          const char *path,
+                          notmuch_find_flags_t flags,
+                          notmuch_status_t *status_ret);
+
+unsigned int
+_notmuch_directory_get_document_id (notmuch_directory_t *directory);
+
+/* message.cc */
+
+notmuch_message_t *
+_notmuch_message_create (const void *talloc_owner,
+                        notmuch_database_t *notmuch,
+                        unsigned int doc_id,
+                        notmuch_private_status_t *status);
+
+notmuch_message_t *
+_notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
+                                       const char *message_id,
+                                       notmuch_private_status_t *status);
+
+unsigned int
+_notmuch_message_get_doc_id (notmuch_message_t *message);
+
+const char *
+_notmuch_message_get_in_reply_to (notmuch_message_t *message);
+
+notmuch_private_status_t
+_notmuch_message_add_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value);
+
+notmuch_private_status_t
+_notmuch_message_remove_term (notmuch_message_t *message,
+                             const char *prefix_name,
+                             const char *value);
+
+notmuch_private_status_t
+_notmuch_message_has_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value,
+                          bool *result);
+
+notmuch_private_status_t
+_notmuch_message_gen_terms (notmuch_message_t *message,
+                           const char *prefix_name,
+                           const char *text);
+
+void
+_notmuch_message_upgrade_filename_storage (notmuch_message_t *message);
+
+void
+_notmuch_message_upgrade_folder (notmuch_message_t *message);
+
+notmuch_status_t
+_notmuch_message_add_filename (notmuch_message_t *message,
+                              const char *filename);
+
+notmuch_status_t
+_notmuch_message_remove_filename (notmuch_message_t *message,
+                                 const char *filename);
+
+notmuch_status_t
+_notmuch_message_rename (notmuch_message_t *message,
+                        const char *new_filename);
+
+void
+_notmuch_message_ensure_thread_id (notmuch_message_t *message);
+
+void
+_notmuch_message_set_header_values (notmuch_message_t *message,
+                                   const char *date,
+                                   const char *from,
+                                   const char *subject);
+
+void
+_notmuch_message_upgrade_last_mod (notmuch_message_t *message);
+
+void
+_notmuch_message_sync (notmuch_message_t *message);
+
+notmuch_status_t
+_notmuch_message_delete (notmuch_message_t *message);
+
+notmuch_private_status_t
+_notmuch_message_initialize_ghost (notmuch_message_t *message,
+                                  const char *thread_id);
+
+void
+_notmuch_message_close (notmuch_message_t *message);
+
+/* Get a copy of the data in this message document.
+ *
+ * Caller should talloc_free the result when done.
+ *
+ * This function is intended to support database upgrade and really
+ * shouldn't be used otherwise. */
+char *
+_notmuch_message_talloc_copy_data (notmuch_message_t *message);
+
+/* Clear the data in this message document.
+ *
+ * This function is intended to support database upgrade and really
+ * shouldn't be used otherwise. */
+void
+_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);
+
+/* Get the author member of 'message' */
+const char *
+_notmuch_message_get_author (notmuch_message_t *message);
+
+/* message-file.c */
+
+/* XXX: I haven't decided yet whether these will actually get exported
+ * into the public interface in notmuch.h
+ */
+
+typedef struct _notmuch_message_file notmuch_message_file_t;
+
+/* Open a file containing a single email message.
+ *
+ * The caller should call notmuch_message_close when done with this.
+ *
+ * Returns NULL if any error occurs.
+ */
+notmuch_message_file_t *
+_notmuch_message_file_open (notmuch_database_t *notmuch, const char *filename);
+
+/* Like notmuch_message_file_open but with 'ctx' as the talloc owner. */
+notmuch_message_file_t *
+_notmuch_message_file_open_ctx (notmuch_database_t *notmuch,
+                               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);
+
+/* Parse the message.
+ *
+ * This will be done automatically as necessary on other calls
+ * depending on it, but an explicit call allows for better error
+ * status reporting.
+ */
+notmuch_status_t
+_notmuch_message_file_parse (notmuch_message_file_t *message);
+
+/* Get the gmime message of a message file.
+ *
+ * The message file is parsed as necessary.
+ *
+ * The GMimeMessage* is set to *mime_message on success (which the
+ * caller must not unref).
+ *
+ * XXX: Would be nice to not have to expose GMimeMessage here.
+ */
+notmuch_status_t
+_notmuch_message_file_get_mime_message (notmuch_message_file_t *message,
+                                       GMimeMessage **mime_message);
+
+/* Get the value of the specified header from the message as a UTF-8 string.
+ *
+ * The message file is parsed as necessary.
+ *
+ * The header name is case insensitive.
+ *
+ * The Received: header is special - for it all Received: headers in
+ * the message are concatenated
+ *
+ * The returned value is owned by the notmuch message and is valid
+ * only until the message is closed. The caller should copy it if
+ * needing to modify the value or to hold onto it for longer.
+ *
+ * Returns NULL on errors, empty string if the message does not
+ * contain a header line matching 'header'.
+ */
+const char *
+_notmuch_message_file_get_header (notmuch_message_file_t *message,
+                                const char *header);
+
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+                                  const char **from_out,
+                                  const char **subject_out,
+                                  const char **to_out,
+                                  const char **date_out,
+                                  char **message_id_out);
+
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message);
+
+/* add-message.cc */
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id);
+/* index.cc */
+
+notmuch_status_t
+_notmuch_message_index_file (notmuch_message_t *message,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_file_t *message_file);
+
+/* messages.c */
+
+typedef struct _notmuch_message_node {
+    notmuch_message_t *message;
+    struct _notmuch_message_node *next;
+} notmuch_message_node_t;
+
+typedef struct _notmuch_message_list {
+    notmuch_message_node_t *head;
+    notmuch_message_node_t **tail;
+} notmuch_message_list_t;
+
+/* There's a rumor that there's an alternate struct _notmuch_messages
+ * somewhere with some nasty C++ objects in it. We'll try to maintain
+ * ignorance of that here. (See notmuch_mset_messages_t in query.cc)
+ */
+struct _notmuch_messages {
+    bool is_of_list_type;
+    notmuch_doc_id_set_t *excluded_doc_ids;
+    notmuch_message_node_t *iterator;
+};
+
+notmuch_message_list_t *
+_notmuch_message_list_create (const void *ctx);
+
+bool
+_notmuch_message_list_empty (notmuch_message_list_t *list);
+
+void
+_notmuch_message_list_add_message (notmuch_message_list_t *list,
+                                  notmuch_message_t *message);
+
+notmuch_messages_t *
+_notmuch_messages_create (notmuch_message_list_t *list);
+
+bool
+_notmuch_messages_has_next (notmuch_messages_t *messages);
+
+/* query.cc */
+
+bool
+_notmuch_mset_messages_valid (notmuch_messages_t *messages);
+
+notmuch_message_t *
+_notmuch_mset_messages_get (notmuch_messages_t *messages);
+
+void
+_notmuch_mset_messages_move_to_next (notmuch_messages_t *messages);
+
+bool
+_notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
+                             unsigned int doc_id);
+
+void
+_notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
+                           unsigned int doc_id);
+
+/* querying xapian documents by type (e.g. "mail" or "ghost"): */
+notmuch_status_t
+_notmuch_query_search_documents (notmuch_query_t *query,
+                                const char *type,
+                                notmuch_messages_t **out);
+
+notmuch_status_t
+_notmuch_query_count_documents (notmuch_query_t *query,
+                               const char *type,
+                               unsigned *count_out);
+/* message-id.c */
+
+/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
+ * comments, and the '<' and '>' delimiters.
+ *
+ * If not NULL, then *next will be made to point to the first character
+ * not parsed, (possibly pointing to the final '\0' terminator.
+ *
+ * Returns a newly talloc'ed string belonging to 'ctx'.
+ *
+ * Returns NULL if there is any error parsing the message-id. */
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next);
+
+/* Parse a message-id, discarding leading and trailing whitespace, and
+ * '<' and '>' delimiters.
+ *
+ * Apply a probably-stricter-than RFC definition of what is allowed in
+ * a message-id. In particular, forbid whitespace.
+ *
+ * Returns a newly talloc'ed string belonging to 'ctx'.
+ *
+ * Returns NULL if there is any error parsing the message-id.
+ */
+
+char *
+_notmuch_message_id_parse_strict (void *ctx, const char *message_id);
+
+
+/* message.cc */
+
+void
+_notmuch_message_add_reply (notmuch_message_t *message,
+                           notmuch_message_t *reply);
+
+void
+_notmuch_message_remove_unprefixed_terms (notmuch_message_t *message);
+
+const char *
+_notmuch_message_get_thread_id_only(notmuch_message_t *message);
+
+size_t _notmuch_message_get_thread_depth (notmuch_message_t *message);
+
+void
+_notmuch_message_label_depths (notmuch_message_t *message,
+                              size_t depth);
+
+notmuch_message_list_t *
+_notmuch_message_sort_subtrees (void *ctx, notmuch_message_list_t *list);
+
+/* sha1.c */
+
+char *
+_notmuch_sha1_of_string (const char *str);
+
+char *
+_notmuch_sha1_of_file (const char *filename);
+
+/* string-list.c */
+
+typedef struct _notmuch_string_node {
+    char *string;
+    struct _notmuch_string_node *next;
+} notmuch_string_node_t;
+
+typedef struct _notmuch_string_list {
+    int length;
+    notmuch_string_node_t *head;
+    notmuch_string_node_t **tail;
+} notmuch_string_list_t;
+
+notmuch_string_list_t *
+_notmuch_string_list_create (const void *ctx);
+
+/*
+ * return the number of strings in 'list'
+ */
+int
+_notmuch_string_list_length (notmuch_string_list_t *list);
+
+/* Add 'string' to 'list'.
+ *
+ * The list will create its own talloced copy of 'string'.
+ */
+void
+_notmuch_string_list_append (notmuch_string_list_t *list,
+                            const char *string);
+
+void
+_notmuch_string_list_sort (notmuch_string_list_t *list);
+
+const notmuch_string_list_t *
+_notmuch_message_get_references(notmuch_message_t *message);
+
+/* string-map.c */
+typedef struct _notmuch_string_map  notmuch_string_map_t;
+typedef struct _notmuch_string_map_iterator notmuch_string_map_iterator_t;
+notmuch_string_map_t *
+_notmuch_string_map_create (const void *ctx);
+
+void
+_notmuch_string_map_append (notmuch_string_map_t *map,
+                           const char *key,
+                           const char *value);
+
+const char *
+_notmuch_string_map_get (notmuch_string_map_t *map, const char *key);
+
+notmuch_string_map_iterator_t *
+_notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
+                                    bool exact);
+
+bool
+_notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iter);
+
+void
+_notmuch_string_map_iterator_move_to_next (notmuch_string_map_iterator_t *iter);
+
+const char *
+_notmuch_string_map_iterator_key (notmuch_string_map_iterator_t *iterator);
+
+const char *
+_notmuch_string_map_iterator_value (notmuch_string_map_iterator_t *iterator);
+
+void
+_notmuch_string_map_iterator_destroy (notmuch_string_map_iterator_t *iterator);
+
+/* tags.c */
+
+notmuch_tags_t *
+_notmuch_tags_create (const void *ctx, notmuch_string_list_t *list);
+
+/* filenames.c */
+
+/* The notmuch_filenames_t iterates over a notmuch_string_list_t of
+ * file names */
+notmuch_filenames_t *
+_notmuch_filenames_create (const void *ctx,
+                          notmuch_string_list_t *list);
+
+/* thread.cc */
+
+notmuch_thread_t *
+_notmuch_thread_create (void *ctx,
+                       notmuch_database_t *notmuch,
+                       unsigned int seed_doc_id,
+                       notmuch_doc_id_set_t *match_set,
+                       notmuch_string_list_t *excluded_terms,
+                       notmuch_exclude_t omit_exclude,
+                       notmuch_sort_t sort);
+
+/* indexopts.c */
+
+struct _notmuch_indexopts {
+    _notmuch_crypto_t crypto;
+};
+
+NOTMUCH_END_DECLS
+
+#ifdef __cplusplus
+/* Implicit typecast from 'void *' to 'T *' is okay in C, but not in
+ * C++. In talloc_steal, an explicit cast is provided for type safety
+ * in some GCC versions. Otherwise, a cast is required. Provide a
+ * template function for this to maintain type safety, and redefine
+ * talloc_steal to use it.
+ */
+#if !(__GNUC__ >= 3)
+template <class T> T *
+_notmuch_talloc_steal (const void *new_ctx, const T *ptr)
+{
+    return static_cast<T *> (talloc_steal (new_ctx, ptr));
+}
+#undef talloc_steal
+#define talloc_steal _notmuch_talloc_steal
+#endif
+#endif
+
+#endif
diff --git a/lib/notmuch.h b/lib/notmuch.h
new file mode 100644 (file)
index 0000000..247f6ad
--- /dev/null
@@ -0,0 +1,2317 @@
+/* notmuch - Not much of an email library, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+/**
+ * @defgroup notmuch The notmuch API
+ *
+ * Not much of an email library, (just index and search)
+ *
+ * @{
+ */
+
+#ifndef NOTMUCH_H
+#define NOTMUCH_H
+
+#ifndef __DOXYGEN__
+
+#ifdef  __cplusplus
+# define NOTMUCH_BEGIN_DECLS  extern "C" {
+# define NOTMUCH_END_DECLS    }
+#else
+# define NOTMUCH_BEGIN_DECLS
+# define NOTMUCH_END_DECLS
+#endif
+
+NOTMUCH_BEGIN_DECLS
+
+#include <time.h>
+
+#pragma GCC visibility push(default)
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/*
+ * The library version number.  This must agree with the soname
+ * version in Makefile.local.
+ */
+#define LIBNOTMUCH_MAJOR_VERSION       5
+#define LIBNOTMUCH_MINOR_VERSION       2
+#define LIBNOTMUCH_MICRO_VERSION       0
+
+
+#if defined (__clang_major__) && __clang_major__ >= 3 \
+    || defined (__GNUC__) && __GNUC__ >= 5 \
+    || defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 5
+#define NOTMUCH_DEPRECATED(major,minor) \
+    __attribute__ ((deprecated ("function deprecated as of libnotmuch " #major "." #minor)))
+#else
+#define NOTMUCH_DEPRECATED(major,minor) __attribute__ ((deprecated))
+#endif
+
+
+#endif /* __DOXYGEN__ */
+
+/**
+ * Check the version of the notmuch library being compiled against.
+ *
+ * Return true if the library being compiled against is of the
+ * specified version or above. For example:
+ *
+ * @code
+ * #if LIBNOTMUCH_CHECK_VERSION(3, 1, 0)
+ *     (code requiring libnotmuch 3.1.0 or above)
+ * #endif
+ * @endcode
+ *
+ * LIBNOTMUCH_CHECK_VERSION has been defined since version 3.1.0; to
+ * check for versions prior to that, use:
+ *
+ * @code
+ * #if !defined(NOTMUCH_CHECK_VERSION)
+ *     (code requiring libnotmuch prior to 3.1.0)
+ * #endif
+ * @endcode
+ */
+#define LIBNOTMUCH_CHECK_VERSION(major, minor, micro)                  \
+    (LIBNOTMUCH_MAJOR_VERSION > (major) ||                                     \
+     (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION > (minor)) || \
+     (LIBNOTMUCH_MAJOR_VERSION == (major) && LIBNOTMUCH_MINOR_VERSION == (minor) && \
+      LIBNOTMUCH_MICRO_VERSION >= (micro)))
+
+/**
+ * Notmuch boolean type.
+ */
+typedef int notmuch_bool_t;
+
+/**
+ * Status codes used for the return values of most functions.
+ *
+ * A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function
+ * completed without error. Any other value indicates an error.
+ */
+typedef enum _notmuch_status {
+    /**
+     * No error occurred.
+     */
+    NOTMUCH_STATUS_SUCCESS = 0,
+    /**
+     * Out of memory.
+     */
+    NOTMUCH_STATUS_OUT_OF_MEMORY,
+    /**
+     * An attempt was made to write to a database opened in read-only
+     * mode.
+     */
+    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.)
+     */
+    NOTMUCH_STATUS_FILE_ERROR,
+    /**
+     * A file was presented that doesn't appear to be an email
+     * message.
+     */
+    NOTMUCH_STATUS_FILE_NOT_EMAIL,
+    /**
+     * A file contains a message ID that is identical to a message
+     * already in the database.
+     */
+    NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID,
+    /**
+     * The user erroneously passed a NULL pointer to a notmuch
+     * function.
+     */
+    NOTMUCH_STATUS_NULL_POINTER,
+    /**
+     * A tag value is too long (exceeds NOTMUCH_TAG_MAX).
+     */
+    NOTMUCH_STATUS_TAG_TOO_LONG,
+    /**
+     * The notmuch_message_thaw function has been called more times
+     * than notmuch_message_freeze.
+     */
+    NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+    /**
+     * notmuch_database_end_atomic has been called more times than
+     * notmuch_database_begin_atomic.
+     */
+    NOTMUCH_STATUS_UNBALANCED_ATOMIC,
+    /**
+     * The operation is not supported.
+     */
+    NOTMUCH_STATUS_UNSUPPORTED_OPERATION,
+    /**
+     * The operation requires a database upgrade.
+     */
+    NOTMUCH_STATUS_UPGRADE_REQUIRED,
+    /**
+     * There is a problem with the proposed path, e.g. a relative path
+     * passed to a function expecting an absolute path.
+     */
+    NOTMUCH_STATUS_PATH_ERROR,
+    /**
+     * The requested operation was ignored. Depending on the function,
+     * this may not be an actual error.
+     */
+    NOTMUCH_STATUS_IGNORED,
+    /**
+     * One of the arguments violates the preconditions for the
+     * function, in a way not covered by a more specific argument.
+     */
+    NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+    /**
+     * A MIME object claimed to have cryptographic protection which
+     * notmuch tried to handle, but the protocol was not specified in
+     * an intelligible way.
+     */
+    NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL,
+    /**
+     * Notmuch attempted to do crypto processing, but could not
+     * initialize the engine needed to do so.
+     */
+    NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION,
+    /**
+     * A MIME object claimed to have cryptographic protection, and
+     * notmuch attempted to process it, but the specific protocol was
+     * something that notmuch doesn't know how to handle.
+     */
+    NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
+    /**
+     * Not an actual status value. Just a way to find out how many
+     * valid status values there are.
+     */
+    NOTMUCH_STATUS_LAST_STATUS
+} notmuch_status_t;
+
+/**
+ * Get a string representation of a notmuch_status_t value.
+ *
+ * The result is read-only.
+ */
+const char *
+notmuch_status_to_string (notmuch_status_t status);
+
+/* Various opaque data types. For each notmuch_<foo>_t see the various
+ * notmuch_<foo> functions below. */
+#ifndef __DOXYGEN__
+typedef struct _notmuch_database notmuch_database_t;
+typedef struct _notmuch_query notmuch_query_t;
+typedef struct _notmuch_threads notmuch_threads_t;
+typedef struct _notmuch_thread notmuch_thread_t;
+typedef struct _notmuch_messages notmuch_messages_t;
+typedef struct _notmuch_message notmuch_message_t;
+typedef struct _notmuch_tags notmuch_tags_t;
+typedef struct _notmuch_directory notmuch_directory_t;
+typedef struct _notmuch_filenames notmuch_filenames_t;
+typedef struct _notmuch_config_list notmuch_config_list_t;
+typedef struct _notmuch_indexopts notmuch_indexopts_t;
+#endif /* __DOXYGEN__ */
+
+/**
+ * Create a new, empty notmuch database located at 'path'.
+ *
+ * The path should be a top-level directory to a collection of
+ * plain-text email messages (one message per file). This call will
+ * create a new ".notmuch" directory within 'path' where notmuch will
+ * store its data.
+ *
+ * After a successful call to notmuch_database_create, the returned
+ * database will be open so the caller should call
+ * notmuch_database_destroy when finished with it.
+ *
+ * The database will not yet have any data in it
+ * (notmuch_database_create itself is a very cheap function). Messages
+ * contained within 'path' can be added to the database by calling
+ * notmuch_database_index_file.
+ *
+ * In case of any failure, this function returns an error status and
+ * sets *database to NULL (after printing an error message on stderr).
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully created the database.
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'path' argument is NULL.
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to create the
+ *     database file (such as permission denied, or file not found,
+ *     etc.), or the database already exists.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ */
+notmuch_status_t
+notmuch_database_create (const char *path, notmuch_database_t **database);
+
+/**
+ * Like notmuch_database_create, except optionally return an error
+ * message. This message is allocated by malloc and should be freed by
+ * the caller.
+ */
+notmuch_status_t
+notmuch_database_create_verbose (const char *path,
+                                notmuch_database_t **database,
+                                char **error_message);
+
+/**
+ * Database open mode for notmuch_database_open.
+ */
+typedef enum {
+    /**
+     * Open database for reading only.
+     */
+    NOTMUCH_DATABASE_MODE_READ_ONLY = 0,
+    /**
+     * Open database for reading and writing.
+     */
+    NOTMUCH_DATABASE_MODE_READ_WRITE
+} notmuch_database_mode_t;
+
+/**
+ * Open an existing notmuch database located at 'path'.
+ *
+ * The database should have been created at some time in the past,
+ * (not necessarily by this process), by calling
+ * notmuch_database_create with 'path'. By default the database should be
+ * opened for reading only. In order to write to the database you need to
+ * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode.
+ *
+ * An existing notmuch database can be identified by the presence of a
+ * directory named ".notmuch" below 'path'.
+ *
+ * The caller should call notmuch_database_destroy when finished with
+ * this database.
+ *
+ * In case of any failure, this function returns an error status and
+ * sets *database to NULL (after printing an error message on stderr).
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully opened the database.
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'path' argument is NULL.
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
+ *     database file (such as permission denied, or file not found,
+ *     etc.), or the database version is unknown.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ */
+notmuch_status_t
+notmuch_database_open (const char *path,
+                      notmuch_database_mode_t mode,
+                      notmuch_database_t **database);
+/**
+ * Like notmuch_database_open, except optionally return an error
+ * message. This message is allocated by malloc and should be freed by
+ * the caller.
+ */
+
+notmuch_status_t
+notmuch_database_open_verbose (const char *path,
+                              notmuch_database_mode_t mode,
+                              notmuch_database_t **database,
+                              char **error_message);
+
+/**
+ * Retrieve last status string for given database.
+ *
+ */
+const char *
+notmuch_database_status_string (const notmuch_database_t *notmuch);
+
+/**
+ * Commit changes and close the given notmuch database.
+ *
+ * After notmuch_database_close has been called, calls to other
+ * functions on objects derived from this database may either behave
+ * as if the database had not been closed (e.g., if the required data
+ * has been cached) or may fail with a
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION. The only further operation
+ * permitted on the database itself is to call
+ * notmuch_database_destroy.
+ *
+ * notmuch_database_close can be called multiple times.  Later calls
+ * have no effect.
+ *
+ * For writable databases, notmuch_database_close commits all changes
+ * to disk before closing the database.  If the caller is currently in
+ * an atomic section (there was a notmuch_database_begin_atomic
+ * without a matching notmuch_database_end_atomic), this will discard
+ * changes made in that atomic section (but still commit changes made
+ * prior to entering the atomic section).
+ *
+ * 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.
+ */
+notmuch_status_t
+notmuch_database_close (notmuch_database_t *database);
+
+/**
+ * A callback invoked by notmuch_database_compact to notify the user
+ * of the progress of the compaction process.
+ */
+typedef void (*notmuch_compact_status_cb_t)(const char *message, void *closure);
+
+/**
+ * Compact a notmuch database, backing up the original database to the
+ * given path.
+ *
+ * The database will be opened with NOTMUCH_DATABASE_MODE_READ_WRITE
+ * during the compaction process to ensure no writes are made.
+ *
+ * If the optional callback function 'status_cb' is non-NULL, it will
+ * be called with diagnostic and informational messages. The argument
+ * 'closure' is passed verbatim to any callback invoked.
+ */
+notmuch_status_t
+notmuch_database_compact (const char* path,
+                         const char* backup_path,
+                         notmuch_compact_status_cb_t status_cb,
+                         void *closure);
+
+/**
+ * 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.
+ */
+notmuch_status_t
+notmuch_database_destroy (notmuch_database_t *database);
+
+/**
+ * Return the database path of the given database.
+ *
+ * The return value is a string owned by notmuch so should not be
+ * modified nor freed by the caller.
+ */
+const char *
+notmuch_database_get_path (notmuch_database_t *database);
+
+/**
+ * Return the database format version of the given database.
+ */
+unsigned int
+notmuch_database_get_version (notmuch_database_t *database);
+
+/**
+ * Can the database be upgraded to a newer database version?
+ *
+ * If this function returns TRUE, then the caller may call
+ * notmuch_database_upgrade to upgrade the database.  If the caller
+ * does not upgrade an out-of-date database, then some functions may
+ * fail with NOTMUCH_STATUS_UPGRADE_REQUIRED.  This always returns
+ * FALSE for a read-only database because there's no way to upgrade a
+ * read-only database.
+ */
+notmuch_bool_t
+notmuch_database_needs_upgrade (notmuch_database_t *database);
+
+/**
+ * Upgrade the current database to the latest supported version.
+ *
+ * This ensures that all current notmuch functionality will be
+ * available on the database.  After opening a database in read-write
+ * mode, it is recommended that clients check if an upgrade is needed
+ * (notmuch_database_needs_upgrade) and if so, upgrade with this
+ * function before making any modifications.  If
+ * notmuch_database_needs_upgrade returns FALSE, this will be a no-op.
+ *
+ * The optional progress_notify callback can be used by the caller to
+ * provide progress indication to the user. If non-NULL it will be
+ * called periodically with 'progress' as a floating-point value in
+ * the range of [0.0 .. 1.0] indicating the progress made so far in
+ * the upgrade process.  The argument 'closure' is passed verbatim to
+ * any callback invoked.
+ */
+notmuch_status_t
+notmuch_database_upgrade (notmuch_database_t *database,
+                         void (*progress_notify) (void *closure,
+                                                  double progress),
+                         void *closure);
+
+/**
+ * Begin an atomic database operation.
+ *
+ * Any modifications performed between a successful begin and a
+ * notmuch_database_end_atomic will be applied to the database
+ * atomically.  Note that, unlike a typical database transaction, this
+ * only ensures atomicity, not durability; neither begin nor end
+ * necessarily flush modifications to disk.
+ *
+ * Atomic sections may be nested.  begin_atomic and end_atomic must
+ * always be called in pairs.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully entered atomic section.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred;
+ *     atomic section not entered.
+ */
+notmuch_status_t
+notmuch_database_begin_atomic (notmuch_database_t *notmuch);
+
+/**
+ * Indicate the end of an atomic database operation.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully completed atomic section.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred;
+ *     atomic section not ended.
+ *
+ * NOTMUCH_STATUS_UNBALANCED_ATOMIC: The database is not currently in
+ *     an atomic section.
+ */
+notmuch_status_t
+notmuch_database_end_atomic (notmuch_database_t *notmuch);
+
+/**
+ * Return the committed database revision and UUID.
+ *
+ * The database revision number increases monotonically with each
+ * commit to the database.  Hence, all messages and message changes
+ * committed to the database (that is, visible to readers) have a last
+ * modification revision <= the committed database revision.  Any
+ * messages committed in the future will be assigned a modification
+ * revision > the committed database revision.
+ *
+ * The UUID is a NUL-terminated opaque string that uniquely identifies
+ * this database.  Two revision numbers are only comparable if they
+ * have the same database UUID.
+ */
+unsigned long
+notmuch_database_get_revision (notmuch_database_t *notmuch,
+                               const char **uuid);
+
+/**
+ * Retrieve a directory object from the database for 'path'.
+ *
+ * Here, 'path' should be a path relative to the path of 'database'
+ * (see notmuch_database_get_path), or else should be an absolute path
+ * with initial components that match the path of 'database'.
+ *
+ * If this directory object does not exist in the database, this
+ * returns NOTMUCH_STATUS_SUCCESS and sets *directory to NULL.
+ *
+ * Otherwise the returned directory object is owned by the database
+ * and as such, will only be valid until notmuch_database_destroy is
+ * called.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successfully retrieved directory.
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'directory' argument is NULL.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred;
+ *     directory not retrieved.
+ *
+ * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
+ *     database to use this function.
+ */
+notmuch_status_t
+notmuch_database_get_directory (notmuch_database_t *database,
+                               const char *path,
+                               notmuch_directory_t **directory);
+
+/**
+ * Add a message file to a database, indexing it for retrieval by
+ * future searches.  If a message already exists with the same message
+ * ID as the specified file, their indexes will be merged, and this
+ * new filename will also be associated with the existing message.
+ *
+ * Here, 'filename' should be a path relative to the path of
+ * 'database' (see notmuch_database_get_path), or else should be an
+ * absolute filename with initial components that match the path of
+ * 'database'.
+ *
+ * The file should be a single mail message (not a multi-message mbox)
+ * that is expected to remain at its current location, (since the
+ * notmuch database will reference the filename, and will not copy the
+ * entire contents of the file.
+ *
+ * If another message with the same message ID already exists in the
+ * database, rather than creating a new message, this adds the search
+ * terms from the identified file to the existing message's index, and
+ * adds 'filename' to the list of filenames known for the message.
+ *
+ * The 'indexopts' parameter can be NULL (meaning, use the indexing
+ * defaults from the database), or can be an explicit choice of
+ * indexing options that should govern the indexing of this specific
+ * 'filename'.
+ *
+ * If 'message' is not NULL, then, on successful return
+ * (NOTMUCH_STATUS_SUCCESS or NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) '*message'
+ * will be initialized to a message object that can be used for things
+ * such as adding tags to the just-added message. The user should call
+ * notmuch_message_destroy when done with the message. On any failure
+ * '*message' will be set to NULL.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not added.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
+ *     ID as another message already in the database. The new
+ *     filename was successfully added to the message in the database
+ *     (if not already present) and the existing message is returned.
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
+ *     file, (such as permission denied, or file not found,
+ *     etc.). Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
+ *     like an email message. Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be added.
+ *
+ * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
+ *     database to use this function.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *database,
+                            const char *filename,
+                            notmuch_indexopts_t *indexopts,
+                            notmuch_message_t **message);
+
+/**
+ * Deprecated alias for notmuch_database_index_file called with
+ * NULL indexopts.
+ *
+ * @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please
+ * use notmuch_database_index_file instead.
+ *
+ */
+NOTMUCH_DEPRECATED(5,1)
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *database,
+                             const char *filename,
+                             notmuch_message_t **message);
+
+/**
+ * Remove a message filename from the given notmuch database. If the
+ * message has no more filenames, remove the message.
+ *
+ * If the same message (as determined by the message ID) is still
+ * available via other filenames, then the message will persist in the
+ * database for those filenames. When the last filename is removed for
+ * a particular message, the database content for that message will be
+ * entirely removed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the
+ *     message was removed from the database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred,
+ *     message not removed.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but
+ *     the message persists in the database with at least one other
+ *     filename.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be removed.
+ *
+ * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
+ *     database to use this function.
+ */
+notmuch_status_t
+notmuch_database_remove_message (notmuch_database_t *database,
+                                const char *filename);
+
+/**
+ * Find a message with the given message_id.
+ *
+ * If a message with the given message_id is found then, on successful return
+ * (NOTMUCH_STATUS_SUCCESS) '*message' will be initialized to a message
+ * object.  The caller should call notmuch_message_destroy when done with the
+ * message.
+ *
+ * On any failure or when the message is not found, this function initializes
+ * '*message' to NULL. This means, when NOTMUCH_STATUS_SUCCESS is returned, the
+ * caller is supposed to check '*message' for NULL to find out whether the
+ * message with the given message_id was found.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successful return, check '*message'.
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'message' argument is NULL
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory, creating message object
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
+ */
+notmuch_status_t
+notmuch_database_find_message (notmuch_database_t *database,
+                              const char *message_id,
+                              notmuch_message_t **message);
+
+/**
+ * Find a message with the given filename.
+ *
+ * If the database contains a message with the given filename then, on
+ * successful return (NOTMUCH_STATUS_SUCCESS) '*message' will be initialized to
+ * a message object. The caller should call notmuch_message_destroy when done
+ * with the message.
+ *
+ * On any failure or when the message is not found, this function initializes
+ * '*message' to NULL. This means, when NOTMUCH_STATUS_SUCCESS is returned, the
+ * caller is supposed to check '*message' for NULL to find out whether the
+ * message with the given filename is found.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Successful return, check '*message'
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The given 'message' argument is NULL
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory, creating the message object
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
+ *
+ * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
+ *     database to use this function.
+ */
+notmuch_status_t
+notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
+                                          const char *filename,
+                                          notmuch_message_t **message);
+
+/**
+ * Return a list of all tags found in the database.
+ *
+ * This function creates a list of all tags found in the database. The
+ * resulting list contains all tags from all messages found in the database.
+ *
+ * On error this function returns NULL.
+ */
+notmuch_tags_t *
+notmuch_database_get_all_tags (notmuch_database_t *db);
+
+/**
+ * Create a new query for 'database'.
+ *
+ * Here, 'database' should be an open database, (see
+ * notmuch_database_open and notmuch_database_create).
+ *
+ * For the query string, we'll document the syntax here more
+ * completely in the future, but it's likely to be a specialized
+ * version of the general Xapian query syntax:
+ *
+ * https://xapian.org/docs/queryparser.html
+ *
+ * As a special case, passing either a length-zero string, (that is ""),
+ * or a string consisting of a single asterisk (that is "*"), will
+ * result in a query that returns all messages in the database.
+ *
+ * See notmuch_query_set_sort for controlling the order of results.
+ * See notmuch_query_search_messages and notmuch_query_search_threads
+ * to actually execute the query.
+ *
+ * User should call notmuch_query_destroy when finished with this
+ * query.
+ *
+ * Will return NULL if insufficient memory is available.
+ */
+notmuch_query_t *
+notmuch_query_create (notmuch_database_t *database,
+                     const char *query_string);
+
+/**
+ * Sort values for notmuch_query_set_sort.
+ */
+typedef enum {
+    /**
+     * Oldest first.
+     */
+    NOTMUCH_SORT_OLDEST_FIRST,
+    /**
+     * Newest first.
+     */
+    NOTMUCH_SORT_NEWEST_FIRST,
+    /**
+     * Sort by message-id.
+     */
+    NOTMUCH_SORT_MESSAGE_ID,
+    /**
+     * Do not sort.
+     */
+    NOTMUCH_SORT_UNSORTED
+} notmuch_sort_t;
+
+/**
+ * Return the query_string of this query. See notmuch_query_create.
+ */
+const char *
+notmuch_query_get_query_string (const notmuch_query_t *query);
+
+/**
+ * Return the notmuch database of this query. See notmuch_query_create.
+ */
+notmuch_database_t *
+notmuch_query_get_database (const notmuch_query_t *query);
+
+/**
+ * Exclude values for notmuch_query_set_omit_excluded. The strange
+ * order is to maintain backward compatibility: the old FALSE/TRUE
+ * options correspond to the new
+ * NOTMUCH_EXCLUDE_FLAG/NOTMUCH_EXCLUDE_TRUE options.
+ */
+typedef enum {
+    NOTMUCH_EXCLUDE_FLAG,
+    NOTMUCH_EXCLUDE_TRUE,
+    NOTMUCH_EXCLUDE_FALSE,
+    NOTMUCH_EXCLUDE_ALL
+} notmuch_exclude_t;
+
+/**
+ * Specify whether to omit excluded results or simply flag them.  By
+ * default, this is set to TRUE.
+ *
+ * If set to TRUE or ALL, notmuch_query_search_messages will omit excluded
+ * messages from the results, and notmuch_query_search_threads will omit
+ * threads that match only in excluded messages.  If set to TRUE,
+ * notmuch_query_search_threads will include all messages in threads that
+ * match in at least one non-excluded message.  Otherwise, if set to ALL,
+ * notmuch_query_search_threads will omit excluded messages from all threads.
+ *
+ * If set to FALSE or FLAG then both notmuch_query_search_messages and
+ * notmuch_query_search_threads will return all matching
+ * messages/threads regardless of exclude status. If set to FLAG then
+ * the exclude flag will be set for any excluded message that is
+ * returned by notmuch_query_search_messages, and the thread counts
+ * for threads returned by notmuch_query_search_threads will be the
+ * number of non-excluded messages/matches. Otherwise, if set to
+ * FALSE, then the exclude status is completely ignored.
+ *
+ * The performance difference when calling
+ * notmuch_query_search_messages should be relatively small (and both
+ * should be very fast).  However, in some cases,
+ * notmuch_query_search_threads is very much faster when omitting
+ * excluded messages as it does not need to construct the threads that
+ * only match in excluded messages.
+ */
+void
+notmuch_query_set_omit_excluded (notmuch_query_t *query,
+                                notmuch_exclude_t omit_excluded);
+
+/**
+ * Specify the sorting desired for this query.
+ */
+void
+notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
+
+/**
+ * Return the sort specified for this query. See
+ * notmuch_query_set_sort.
+ */
+notmuch_sort_t
+notmuch_query_get_sort (const notmuch_query_t *query);
+
+/**
+ * Add a tag that will be excluded from the query results by default.
+ * This exclusion will be ignored if this tag appears explicitly in
+ * the query.
+ *
+ * @returns
+ *
+ * NOTMUCH_STATUS_SUCCESS: excluded was added successfully.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occurred.
+ *      Most likely a problem lazily parsing the query string.
+ *
+ * NOTMUCH_STATUS_IGNORED: tag is explicitly present in the query, so
+ *             not excluded.
+ */
+notmuch_status_t
+notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag);
+
+/**
+ * Execute a query for threads, returning a notmuch_threads_t object
+ * which can be used to iterate over the results. The returned threads
+ * object is owned by the query and as such, will only be valid until
+ * notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_threads_t *threads;
+ *     notmuch_thread_t *thread;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (threads = notmuch_query_search_threads (query);
+ *          notmuch_threads_valid (threads);
+ *          notmuch_threads_move_to_next (threads))
+ *     {
+ *         thread = notmuch_threads_get (threads);
+ *         ....
+ *         notmuch_thread_destroy (thread);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a thread before its containing
+ * query, you can call notmuch_thread_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your thread objects
+ * are long-lived, then you don't need to call notmuch_thread_destroy
+ * and all the memory will still be reclaimed when the query is
+ * destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_threads_t object. (For consistency, we do provide a
+ * notmuch_threads_destroy function, but there's no good reason
+ * to call it if the query is about to be destroyed).
+ *
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+notmuch_status_t
+notmuch_query_search_threads (notmuch_query_t *query,
+                             notmuch_threads_t **out);
+
+/**
+ * Deprecated alias for notmuch_query_search_threads.
+ *
+ * @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please
+ * use notmuch_query_search_threads instead.
+ *
+ */
+NOTMUCH_DEPRECATED(5,0)
+notmuch_status_t
+notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out);
+
+/**
+ * Execute a query for messages, returning a notmuch_messages_t object
+ * which can be used to iterate over the results. The returned
+ * messages object is owned by the query and as such, will only be
+ * valid until notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_messages_t *messages;
+ *     notmuch_message_t *message;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (messages = notmuch_query_search_messages (query);
+ *          notmuch_messages_valid (messages);
+ *          notmuch_messages_move_to_next (messages))
+ *     {
+ *         message = notmuch_messages_get (messages);
+ *         ....
+ *         notmuch_message_destroy (message);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a message before its containing
+ * query, you can call notmuch_message_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your message
+ * objects are long-lived, then you don't need to call
+ * notmuch_message_destroy and all the memory will still be reclaimed
+ * when the query is destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_messages_t object. (For consistency, we do provide a
+ * notmuch_messages_destroy function, but there's no good
+ * reason to call it if the query is about to be destroyed).
+ *
+ * If a Xapian exception occurs this function will return NULL.
+ *
+ * @since libnotmuch 5 (notmuch 0.25)
+ */
+notmuch_status_t
+notmuch_query_search_messages (notmuch_query_t *query,
+                              notmuch_messages_t **out);
+/**
+ * Deprecated alias for notmuch_query_search_messages
+ *
+ * @deprecated Deprecated as of libnotmuch 5 (notmuch 0.25). Please use
+ * notmuch_query_search_messages instead.
+ *
+ */
+
+NOTMUCH_DEPRECATED(5,0)
+notmuch_status_t
+notmuch_query_search_messages_st (notmuch_query_t *query,
+                                 notmuch_messages_t **out);
+
+/**
+ * Destroy a notmuch_query_t along with any associated resources.
+ *
+ * This will in turn destroy any notmuch_threads_t and
+ * notmuch_messages_t objects generated by this query, (and in
+ * turn any notmuch_thread_t and notmuch_message_t objects generated
+ * from those results, etc.), if such objects haven't already been
+ * destroyed.
+ */
+void
+notmuch_query_destroy (notmuch_query_t *query);
+
+/**
+ * Is the given 'threads' iterator pointing at a valid thread.
+ *
+ * When this function returns TRUE, notmuch_threads_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_threads_get will return NULL.
+ *
+ * If passed a NULL pointer, this function returns FALSE
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+notmuch_bool_t
+notmuch_threads_valid (notmuch_threads_t *threads);
+
+/**
+ * Get the current thread from 'threads' as a notmuch_thread_t.
+ *
+ * Note: The returned thread belongs to 'threads' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+notmuch_thread_t *
+notmuch_threads_get (notmuch_threads_t *threads);
+
+/**
+ * Move the 'threads' iterator to the next thread.
+ *
+ * If 'threads' is already pointing at the last thread then the
+ * iterator will be moved to a point just beyond that last thread,
+ * (where notmuch_threads_valid will return FALSE and
+ * notmuch_threads_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+void
+notmuch_threads_move_to_next (notmuch_threads_t *threads);
+
+/**
+ * Destroy a notmuch_threads_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_threads_t object will be reclaimed when the
+ * containing query object is destroyed.
+ */
+void
+notmuch_threads_destroy (notmuch_threads_t *threads);
+
+/**
+ * Return the number of messages matching a search.
+ *
+ * This function performs a search and returns the number of matching
+ * messages.
+ *
+ * @returns
+ *
+ * NOTMUCH_STATUS_SUCCESS: query completed successfully.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occurred. The
+ *      value of *count is not defined.
+ *
+ * @since libnotmuch 5 (notmuch 0.25)
+ */
+notmuch_status_t
+notmuch_query_count_messages (notmuch_query_t *query, unsigned int *count);
+
+/**
+ * Deprecated alias for notmuch_query_count_messages
+ *
+ *
+ * @deprecated Deprecated since libnotmuch 5.0 (notmuch 0.25). Please
+ * use notmuch_query_count_messages instead.
+ */
+NOTMUCH_DEPRECATED(5,0)
+notmuch_status_t
+notmuch_query_count_messages_st (notmuch_query_t *query, unsigned int *count);
+
+/**
+ * Return the number of threads matching a search.
+ *
+ * This function performs a search and returns the number of unique thread IDs
+ * in the matching messages. This is the same as number of threads matching a
+ * search.
+ *
+ * Note that this is a significantly heavier operation than
+ * notmuch_query_count_messages{_st}().
+ *
+ * @returns
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Memory allocation failed. The value
+ *      of *count is not defined
+
+ * NOTMUCH_STATUS_SUCCESS: query completed successfully.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occurred. The
+ *      value of *count is not defined.
+ *
+ * @since libnotmuch 5 (notmuch 0.25)
+ */
+notmuch_status_t
+notmuch_query_count_threads (notmuch_query_t *query, unsigned *count);
+
+/**
+ * Deprecated alias for notmuch_query_count_threads
+ *
+ * @deprecated Deprecated as of libnotmuch 5.0 (notmuch 0.25). Please
+ * use notmuch_query_count_threads_st instead.
+ */
+NOTMUCH_DEPRECATED(5,0)
+notmuch_status_t
+notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count);
+
+/**
+ * Get the thread ID of 'thread'.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+const char *
+notmuch_thread_get_thread_id (notmuch_thread_t *thread);
+
+/**
+ * Get the total number of messages in 'thread'.
+ *
+ * This count consists of all messages in the database belonging to
+ * this thread. Contrast with notmuch_thread_get_matched_messages() .
+ */
+int
+notmuch_thread_get_total_messages (notmuch_thread_t *thread);
+
+/**
+ * Get the total number of files in 'thread'.
+ *
+ * This sums notmuch_message_count_files over all messages in the
+ * thread
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread);
+
+/**
+ * Get a notmuch_messages_t iterator for the top-level messages in
+ * 'thread' in oldest-first order.
+ *
+ * This iterator will not necessarily iterate over all of the messages
+ * in the thread. It will only iterate over the messages in the thread
+ * which are not replies to other messages in the thread.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+notmuch_messages_t *
+notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread);
+
+/**
+ * Get a notmuch_thread_t iterator for all messages in 'thread' in
+ * oldest-first order.
+ *
+ * The returned list will be destroyed when the thread is destroyed.
+ */
+notmuch_messages_t *
+notmuch_thread_get_messages (notmuch_thread_t *thread);
+
+/**
+ * Get the number of messages in 'thread' that matched the search.
+ *
+ * This count includes only the messages in this thread that were
+ * matched by the search from which the thread was created and were
+ * not excluded by any exclude tags passed in with the query (see
+ * notmuch_query_add_tag_exclude). Contrast with
+ * notmuch_thread_get_total_messages() .
+ */
+int
+notmuch_thread_get_matched_messages (notmuch_thread_t *thread);
+
+/**
+ * Get the authors of 'thread' as a UTF-8 string.
+ *
+ * The returned string is a comma-separated list of the names of the
+ * authors of mail messages in the query results that belong to this
+ * thread.
+ *
+ * The string contains authors of messages matching the query first, then
+ * non-matched authors (with the two groups separated by '|'). Within
+ * each group, authors are ordered by date.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+const char *
+notmuch_thread_get_authors (notmuch_thread_t *thread);
+
+/**
+ * Get the subject of 'thread' as a UTF-8 string.
+ *
+ * The subject is taken from the first message (according to the query
+ * order---see notmuch_query_set_sort) in the query results that
+ * belongs to this thread.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+const char *
+notmuch_thread_get_subject (notmuch_thread_t *thread);
+
+/**
+ * Get the date of the oldest message in 'thread' as a time_t value.
+ */
+time_t
+notmuch_thread_get_oldest_date (notmuch_thread_t *thread);
+
+/**
+ * Get the date of the newest message in 'thread' as a time_t value.
+ */
+time_t
+notmuch_thread_get_newest_date (notmuch_thread_t *thread);
+
+/**
+ * Get the tags for 'thread', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * Note: In the Notmuch database, tags are stored on individual
+ * messages, not on threads. So the tags returned here will be all
+ * tags of the messages which matched the search and which belong to
+ * this thread.
+ *
+ * The tags object is owned by the thread and as such, will only be
+ * valid for as long as the thread is valid, (for example, until
+ * notmuch_thread_destroy or until the query from which it derived is
+ * destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_thread_t *thread;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     thread = notmuch_threads_get (threads);
+ *
+ *     for (tags = notmuch_thread_get_tags (thread);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_tags_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_thread_destroy (thread);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+notmuch_tags_t *
+notmuch_thread_get_tags (notmuch_thread_t *thread);
+
+/**
+ * Destroy a notmuch_thread_t object.
+ */
+void
+notmuch_thread_destroy (notmuch_thread_t *thread);
+
+/**
+ * Is the given 'messages' iterator pointing at a valid message.
+ *
+ * When this function returns TRUE, notmuch_messages_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_messages_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+notmuch_bool_t
+notmuch_messages_valid (notmuch_messages_t *messages);
+
+/**
+ * Get the current message from 'messages' as a notmuch_message_t.
+ *
+ * Note: The returned message belongs to 'messages' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+notmuch_message_t *
+notmuch_messages_get (notmuch_messages_t *messages);
+
+/**
+ * Move the 'messages' iterator to the next message.
+ *
+ * If 'messages' is already pointing at the last message then the
+ * iterator will be moved to a point just beyond that last message,
+ * (where notmuch_messages_valid will return FALSE and
+ * notmuch_messages_get will return NULL).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+void
+notmuch_messages_move_to_next (notmuch_messages_t *messages);
+
+/**
+ * Destroy a notmuch_messages_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_messages_t object will be reclaimed when the containing
+ * query object is destroyed.
+ */
+void
+notmuch_messages_destroy (notmuch_messages_t *messages);
+
+/**
+ * Return a list of tags from all messages.
+ *
+ * The resulting list is guaranteed not to contain duplicated tags.
+ *
+ * WARNING: You can no longer iterate over messages after calling this
+ * function, because the iterator will point at the end of the list.
+ * We do not have a function to reset the iterator yet and the only
+ * way how you can iterate over the list again is to recreate the
+ * message list.
+ *
+ * The function returns NULL on error.
+ */
+notmuch_tags_t *
+notmuch_messages_collect_tags (notmuch_messages_t *messages);
+
+/**
+ * Get the database associated with this message.
+ *
+ * @since libnotmuch 5.2 (notmuch 0.27)
+ */
+notmuch_database_t *
+notmuch_message_get_database (const notmuch_message_t *message);
+
+/**
+ * Get the message ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (which is until the query from which it derived
+ * is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message has a unique message ID, (Notmuch will generate an ID for a
+ * message if the original file does not contain one).
+ */
+const char *
+notmuch_message_get_message_id (notmuch_message_t *message);
+
+/**
+ * Get the thread ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (for example, until the user calls
+ * notmuch_message_destroy on 'message' or until a query from which it
+ * derived is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message belongs to a single thread.
+ */
+const char *
+notmuch_message_get_thread_id (notmuch_message_t *message);
+
+/**
+ * Get a notmuch_messages_t iterator for all of the replies to
+ * 'message'.
+ *
+ * Note: This call only makes sense if 'message' was ultimately
+ * obtained from a notmuch_thread_t object, (such as by coming
+ * directly from the result of calling notmuch_thread_get_
+ * toplevel_messages or by any number of subsequent
+ * calls to notmuch_message_get_replies).
+ *
+ * If 'message' was obtained through some non-thread means, (such as
+ * by a call to notmuch_query_search_messages), then this function
+ * will return NULL.
+ *
+ * If there are no replies to 'message', this function will return
+ * NULL. (Note that notmuch_messages_valid will accept that NULL
+ * value as legitimate, and simply return FALSE for it.)
+ */
+notmuch_messages_t *
+notmuch_message_get_replies (notmuch_message_t *message);
+
+/**
+ * Get the total number of files associated with a message.
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+int
+notmuch_message_count_files (notmuch_message_t *message);
+
+/**
+ * Get a filename for the email corresponding to 'message'.
+ *
+ * The returned filename is an absolute filename, (the initial
+ * component will match notmuch_database_get_path() ).
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Note: If this message corresponds to multiple files in the mail
+ * store, (that is, multiple files contain identical message IDs),
+ * this function will arbitrarily return a single one of those
+ * filenames. See notmuch_message_get_filenames for returning the
+ * complete list of filenames.
+ */
+const char *
+notmuch_message_get_filename (notmuch_message_t *message);
+
+/**
+ * Get all filenames for the email corresponding to 'message'.
+ *
+ * Returns a notmuch_filenames_t iterator listing all the filenames
+ * associated with 'message'. These files may not have identical
+ * content, but each will have the identical Message-ID.
+ *
+ * Each filename in the iterator is an absolute filename, (the initial
+ * component will match notmuch_database_get_path() ).
+ */
+notmuch_filenames_t *
+notmuch_message_get_filenames (notmuch_message_t *message);
+
+/**
+ * Re-index the e-mail corresponding to 'message' using the supplied index options
+ *
+ * Returns the status of the re-index operation.  (see the return
+ * codes documented in notmuch_database_index_file)
+ *
+ * After reindexing, the user should discard the message object passed
+ * in here by calling notmuch_message_destroy, since it refers to the
+ * original message, not to the reindexed message.
+ */
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+                        notmuch_indexopts_t *indexopts);
+
+/**
+ * Message flags.
+ */
+typedef enum _notmuch_message_flag {
+    NOTMUCH_MESSAGE_FLAG_MATCH,
+    NOTMUCH_MESSAGE_FLAG_EXCLUDED,
+
+    /* This message is a "ghost message", meaning it has no filenames
+     * or content, but we know it exists because it was referenced by
+     * some other message.  A ghost message has only a message ID and
+     * thread ID.
+     */
+    NOTMUCH_MESSAGE_FLAG_GHOST,
+} notmuch_message_flag_t;
+
+/**
+ * Get a value of a flag for the email corresponding to 'message'.
+ */
+notmuch_bool_t
+notmuch_message_get_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag);
+
+/**
+ * Set a value of a flag for the email corresponding to 'message'.
+ */
+void
+notmuch_message_set_flag (notmuch_message_t *message,
+                         notmuch_message_flag_t flag, notmuch_bool_t value);
+
+/**
+ * Get the date of 'message' as a time_t value.
+ *
+ * For the original textual representation of the Date header from the
+ * message call notmuch_message_get_header() with a header value of
+ * "date".
+ */
+time_t
+notmuch_message_get_date  (notmuch_message_t *message);
+
+/**
+ * Get the value of the specified header from 'message' as a UTF-8 string.
+ *
+ * Common headers are stored in the database when the message is
+ * indexed and will be returned from the database.  Other headers will
+ * be read from the actual message file.
+ *
+ * The header name is case insensitive.
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Returns an empty string ("") if the message does not contain a
+ * header line matching 'header'. Returns NULL if any error occurs.
+ */
+const char *
+notmuch_message_get_header (notmuch_message_t *message, const char *header);
+
+/**
+ * Get the tags for 'message', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * The tags object is owned by the message and as such, will only be
+ * valid for as long as the message is valid, (which is until the
+ * query from which it derived is destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_message_t *message;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     message = notmuch_database_find_message (database, message_id);
+ *
+ *     for (tags = notmuch_message_get_tags (message);
+ *          notmuch_tags_valid (tags);
+ *          notmuch_tags_move_to_next (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_message_destroy (message);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+notmuch_tags_t *
+notmuch_message_get_tags (notmuch_message_t *message);
+
+/**
+ * The longest possible tag value.
+ */
+#define NOTMUCH_TAG_MAX 200
+
+/**
+ * Add a tag to the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+notmuch_status_t
+notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
+
+/**
+ * Remove a tag from the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+notmuch_status_t
+notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
+
+/**
+ * Remove all tags from the given message.
+ *
+ * See notmuch_message_freeze for an example showing how to safely
+ * replace tag values.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+notmuch_status_t
+notmuch_message_remove_all_tags (notmuch_message_t *message);
+
+/**
+ * Add/remove tags according to maildir flags in the message filename(s).
+ *
+ * This function examines the filenames of 'message' for maildir
+ * flags, and adds or removes tags on 'message' as follows when these
+ * flags are present:
+ *
+ *     Flag    Action if present
+ *     ----    -----------------
+ *     'D'     Adds the "draft" tag to the message
+ *     'F'     Adds the "flagged" tag to the message
+ *     'P'     Adds the "passed" tag to the message
+ *     'R'     Adds the "replied" tag to the message
+ *     'S'     Removes the "unread" tag from the message
+ *
+ * For each flag that is not present, the opposite action (add/remove)
+ * is performed for the corresponding tags.
+ *
+ * Flags are identified as trailing components of the filename after a
+ * sequence of ":2,".
+ *
+ * If there are multiple filenames associated with this message, the
+ * flag is considered present if it appears in one or more
+ * filenames. (That is, the flags from the multiple filenames are
+ * combined with the logical OR operator.)
+ *
+ * A client can ensure that notmuch database tags remain synchronized
+ * with maildir flags by calling this function after each call to
+ * notmuch_database_index_file. See also
+ * notmuch_message_tags_to_maildir_flags for synchronizing tag changes
+ * back to maildir flags.
+ */
+notmuch_status_t
+notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);
+
+/**
+ * return TRUE if any filename of 'message' has maildir flag 'flag',
+ * FALSE otherwise.
+ *
+ */
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag);
+
+/**
+ * Rename message filename(s) to encode tags as maildir flags.
+ *
+ * Specifically, for each filename corresponding to this message:
+ *
+ * If the filename is not in a maildir directory, do nothing.  (A
+ * maildir directory is determined as a directory named "new" or
+ * "cur".) Similarly, if the filename has invalid maildir info,
+ * (repeated or outof-ASCII-order flag characters after ":2,"), then
+ * do nothing.
+ *
+ * If the filename is in a maildir directory, rename the file so that
+ * its filename ends with the sequence ":2," followed by zero or more
+ * of the following single-character flags (in ASCII order):
+ *
+ *   * flag 'D' iff the message has the "draft" tag
+ *   * flag 'F' iff the message has the "flagged" tag
+ *   * flag 'P' iff the message has the "passed" tag
+ *   * flag 'R' iff the message has the "replied" tag
+ *   * flag 'S' iff the message does not have the "unread" tag
+ *
+ * Any existing flags unmentioned in the list above will be preserved
+ * in the renaming.
+ *
+ * Also, if this filename is in a directory named "new", rename it to
+ * be within the neighboring directory named "cur".
+ *
+ * A client can ensure that maildir filename flags remain synchronized
+ * with notmuch database tags by calling this function after changing
+ * tags, (after calls to notmuch_message_add_tag,
+ * notmuch_message_remove_tag, or notmuch_message_freeze/
+ * notmuch_message_thaw). See also notmuch_message_maildir_flags_to_tags
+ * for synchronizing maildir flag changes back to tags.
+ */
+notmuch_status_t
+notmuch_message_tags_to_maildir_flags (notmuch_message_t *message);
+
+/**
+ * Freeze the current state of 'message' within the database.
+ *
+ * This means that changes to the message state, (via
+ * notmuch_message_add_tag, notmuch_message_remove_tag, and
+ * notmuch_message_remove_all_tags), will not be committed to the
+ * database until the message is thawed with notmuch_message_thaw.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls will
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * The ability to do freeze/thaw allows for safe transactions to
+ * change tag values. For example, explicitly setting a message to
+ * have a given set of tags might look like this:
+ *
+ *    notmuch_message_freeze (message);
+ *
+ *    notmuch_message_remove_all_tags (message);
+ *
+ *    for (i = 0; i < NUM_TAGS; i++)
+ *        notmuch_message_add_tag (message, tags[i]);
+ *
+ *    notmuch_message_thaw (message);
+ *
+ * With freeze/thaw used like this, the message in the database is
+ * guaranteed to have either the full set of original tag values, or
+ * the full set of new tag values, but nothing in between.
+ *
+ * Imagine the example above without freeze/thaw and the operation
+ * somehow getting interrupted. This could result in the message being
+ * left with no tags if the interruption happened after
+ * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully frozen.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so message cannot be modified.
+ */
+notmuch_status_t
+notmuch_message_freeze (notmuch_message_t *message);
+
+/**
+ * Thaw the current 'message', synchronizing any changes that may have
+ * occurred while 'message' was frozen into the notmuch database.
+ *
+ * See notmuch_message_freeze for an example of how to use this
+ * function to safely provide tag changes.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls with
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
+ *     its frozen count has successfully been reduced by 1).
+ *
+ * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw
+ *     an unfrozen message. That is, there have been an unbalanced
+ *     number of calls to notmuch_message_freeze and
+ *     notmuch_message_thaw.
+ */
+notmuch_status_t
+notmuch_message_thaw (notmuch_message_t *message);
+
+/**
+ * Destroy a notmuch_message_t object.
+ *
+ * It can be useful to call this function in the case of a single
+ * query object with many messages in the result, (such as iterating
+ * over the entire database). Otherwise, it's fine to never call this
+ * function and there will still be no memory leaks. (The memory from
+ * the messages get reclaimed when the containing query is destroyed.)
+ */
+void
+notmuch_message_destroy (notmuch_message_t *message);
+
+/**
+ * @name Message Properties
+ *
+ * This interface provides the ability to attach arbitrary (key,value)
+ * string pairs to a message, to remove such pairs, and to iterate
+ * over them.  The caller should take some care as to what keys they
+ * add or delete values for, as other subsystems or extensions may
+ * depend on these properties.
+ *
+ * Please see notmuch-properties(7) for more details about specific
+ * properties and conventions around their use.
+ *
+ */
+/**@{*/
+/**
+ * Retrieve the value for a single property key
+ *
+ * *value* is set to a string owned by the message or NULL if there is
+ * no such key. In the case of multiple values for the given key, the
+ * first one is retrieved.
+ *
+ * @returns
+ * - NOTMUCH_STATUS_NULL_POINTER: *value* may not be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occurred.
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value);
+
+/**
+ * Add a (key,value) pair to a message
+ *
+ * @returns
+ * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.
+ * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occurred.
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value);
+
+/**
+ * Remove a (key,value) pair from a message.
+ *
+ * It is not an error to remove a non-existant (key,value) pair
+ *
+ * @returns
+ * - NOTMUCH_STATUS_ILLEGAL_ARGUMENT: *key* may not contain an '=' character.
+ * - NOTMUCH_STATUS_NULL_POINTER: Neither *key* nor *value* may be NULL.
+ * - NOTMUCH_STATUS_SUCCESS: No error occurred.
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value);
+
+/**
+ * Remove all (key,value) pairs from the given message.
+ *
+ * @param[in,out] message  message to operate on.
+ * @param[in]     key      key to delete properties for. If NULL, delete
+ *                        properties for all keys
+ * @returns
+ * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ *   read-only mode so message cannot be modified.
+ * - NOTMUCH_STATUS_SUCCESS: No error occurred.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key);
+
+/**
+ * Remove all (prefix*,value) pairs from the given message
+ *
+ * @param[in,out] message  message to operate on.
+ * @param[in]     prefix   delete properties with keys that start with prefix.
+ *                        If NULL, delete all properties
+ * @returns
+ * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ *   read-only mode so message cannot be modified.
+ * - NOTMUCH_STATUS_SUCCESS: No error occurred.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix);
+
+/**
+ * Opaque message property iterator
+ */
+typedef struct _notmuch_string_map_iterator notmuch_message_properties_t;
+
+/**
+ * Get the properties for *message*, returning a
+ * notmuch_message_properties_t object which can be used to iterate
+ * over all properties.
+ *
+ * The notmuch_message_properties_t object is owned by the message and
+ * as such, will only be valid for as long as the message is valid,
+ * (which is until the query from which it derived is destroyed).
+ *
+ * @param[in] message  The message to examine
+ * @param[in] key      key or key prefix
+ * @param[in] exact    if TRUE, require exact match with key. Otherwise
+ *                    treat as prefix.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_message_properties_t *list;
+ *
+ *     for (list = notmuch_message_get_properties (message, "testkey1", TRUE);
+ *          notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
+ *        printf("%s\n", notmuch_message_properties_value(list));
+ *     }
+ *
+ *     notmuch_message_properties_destroy (list);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_message_properties_t object. (For consistency, we do
+ * provide a notmuch_message_properities_destroy function, but there's
+ * no good reason to call it if the message is about to be destroyed).
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_message_properties_t *
+notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact);
+
+/**
+ * Return the number of properties named "key" belonging to the specific message.
+ *
+ * @param[in] message  The message to examine
+ * @param[in] key      key to count
+ * @param[out] count   The number of matching properties associated with this message.
+ *
+ * @returns
+ *
+ * NOTMUCH_STATUS_SUCCESS: successful count, possibly some other error.
+ *
+ * @since libnotmuch 5.2 (notmuch 0.27)
+ */
+notmuch_status_t
+notmuch_message_count_properties (notmuch_message_t *message, const char *key, unsigned int *count);
+
+/**
+ * Is the given *properties* iterator pointing at a valid (key,value)
+ * pair.
+ *
+ * When this function returns TRUE,
+ * notmuch_message_properties_{key,value} will return a valid string,
+ * and notmuch_message_properties_move_to_next will do what it
+ * says. Whereas when this function returns FALSE, calling any of
+ * these functions results in undefined behaviour.
+ *
+ * See the documentation of notmuch_message_get_properties for example
+ * code showing how to iterate over a notmuch_message_properties_t
+ * object.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_bool_t
+notmuch_message_properties_valid (notmuch_message_properties_t *properties);
+
+/**
+ * Move the *properties* iterator to the next (key,value) pair
+ *
+ * If *properties* is already pointing at the last pair then the iterator
+ * will be moved to a point just beyond that last pair, (where
+ * notmuch_message_properties_valid will return FALSE).
+ *
+ * See the documentation of notmuch_message_get_properties for example
+ * code showing how to iterate over a notmuch_message_properties_t object.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+void
+notmuch_message_properties_move_to_next (notmuch_message_properties_t *properties);
+
+/**
+ * Return the key from the current (key,value) pair.
+ *
+ * this could be useful if iterating for a prefix
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+const char *
+notmuch_message_properties_key (notmuch_message_properties_t *properties);
+
+/**
+ * Return the value from the current (key,value) pair.
+ *
+ * This could be useful if iterating for a prefix.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+const char *
+notmuch_message_properties_value (notmuch_message_properties_t *properties);
+
+
+/**
+ * Destroy a notmuch_message_properties_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_message_properties_t object will be reclaimed when the
+ * containing message object is destroyed.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+void
+notmuch_message_properties_destroy (notmuch_message_properties_t *properties);
+
+/**@}*/
+
+/**
+ * Is the given 'tags' iterator pointing at a valid tag.
+ *
+ * When this function returns TRUE, notmuch_tags_get will return a
+ * valid string. Whereas when this function returns FALSE,
+ * notmuch_tags_get will return NULL.
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+notmuch_bool_t
+notmuch_tags_valid (notmuch_tags_t *tags);
+
+/**
+ * Get the current tag from 'tags' as a string.
+ *
+ * Note: The returned string belongs to 'tags' and has a lifetime
+ * identical to it (and the query to which it ultimately belongs).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+const char *
+notmuch_tags_get (notmuch_tags_t *tags);
+
+/**
+ * Move the 'tags' iterator to the next tag.
+ *
+ * If 'tags' is already pointing at the last tag then the iterator
+ * will be moved to a point just beyond that last tag, (where
+ * notmuch_tags_valid will return FALSE and notmuch_tags_get will
+ * return NULL).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+void
+notmuch_tags_move_to_next (notmuch_tags_t *tags);
+
+/**
+ * Destroy a notmuch_tags_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_tags_t object will be reclaimed when the containing
+ * message or query objects are destroyed.
+ */
+void
+notmuch_tags_destroy (notmuch_tags_t *tags);
+
+/**
+ * Store an mtime within the database for 'directory'.
+ *
+ * The 'directory' should be an object retrieved from the database
+ * with notmuch_database_get_directory for a particular path.
+ *
+ * The intention is for the caller to use the mtime to allow efficient
+ * identification of new messages to be added to the database. The
+ * recommended usage is as follows:
+ *
+ *   o Read the mtime of a directory from the filesystem
+ *
+ *   o Call index_file for all mail files in the directory
+ *
+ *   o Call notmuch_directory_set_mtime with the mtime read from the
+ *     filesystem.
+ *
+ * Then, when wanting to check for updates to the directory in the
+ * future, the client can call notmuch_directory_get_mtime and know
+ * that it only needs to add files if the mtime of the directory and
+ * files are newer than the stored timestamp.
+ *
+ * Note: The notmuch_directory_get_mtime function does not allow the
+ * caller to distinguish a timestamp of 0 from a non-existent
+ * timestamp. So don't store a timestamp of 0 unless you are
+ * comfortable with that.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: mtime successfully stored in database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception
+ *     occurred, mtime not stored.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so directory mtime cannot be modified.
+ */
+notmuch_status_t
+notmuch_directory_set_mtime (notmuch_directory_t *directory,
+                            time_t mtime);
+
+/**
+ * Get the mtime of a directory, (as previously stored with
+ * notmuch_directory_set_mtime).
+ *
+ * Returns 0 if no mtime has previously been stored for this
+ * directory.
+ */
+time_t
+notmuch_directory_get_mtime (notmuch_directory_t *directory);
+
+/**
+ * Get a notmuch_filenames_t iterator listing all the filenames of
+ * messages in the database within the given directory.
+ *
+ * The returned filenames will be the basename-entries only (not
+ * complete paths).
+ */
+notmuch_filenames_t *
+notmuch_directory_get_child_files (notmuch_directory_t *directory);
+
+/**
+ * Get a notmuch_filenames_t iterator listing all the filenames of
+ * sub-directories in the database within the given directory.
+ *
+ * The returned filenames will be the basename-entries only (not
+ * complete paths).
+ */
+notmuch_filenames_t *
+notmuch_directory_get_child_directories (notmuch_directory_t *directory);
+
+/**
+ * Delete directory document from the database, and destroy the
+ * notmuch_directory_t object. Assumes any child directories and files
+ * have been deleted by the caller.
+ *
+ * @since libnotmuch 4.3 (notmuch 0.21)
+ */
+notmuch_status_t
+notmuch_directory_delete (notmuch_directory_t *directory);
+
+/**
+ * Destroy a notmuch_directory_t object.
+ */
+void
+notmuch_directory_destroy (notmuch_directory_t *directory);
+
+/**
+ * Is the given 'filenames' iterator pointing at a valid filename.
+ *
+ * When this function returns TRUE, notmuch_filenames_get will return
+ * a valid string. Whereas when this function returns FALSE,
+ * notmuch_filenames_get will return NULL.
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will always return FALSE.
+ */
+notmuch_bool_t
+notmuch_filenames_valid (notmuch_filenames_t *filenames);
+
+/**
+ * Get the current filename from 'filenames' as a string.
+ *
+ * Note: The returned string belongs to 'filenames' and has a lifetime
+ * identical to it (and the directory to which it ultimately belongs).
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will always return NULL.
+ */
+const char *
+notmuch_filenames_get (notmuch_filenames_t *filenames);
+
+/**
+ * Move the 'filenames' iterator to the next filename.
+ *
+ * If 'filenames' is already pointing at the last filename then the
+ * iterator will be moved to a point just beyond that last filename,
+ * (where notmuch_filenames_valid will return FALSE and
+ * notmuch_filenames_get will return NULL).
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will do nothing.
+ */
+void
+notmuch_filenames_move_to_next (notmuch_filenames_t *filenames);
+
+/**
+ * Destroy a notmuch_filenames_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_filenames_t object will be reclaimed when the
+ * containing directory object is destroyed.
+ *
+ * It is acceptable to pass NULL for 'filenames', in which case this
+ * function will do nothing.
+ */
+void
+notmuch_filenames_destroy (notmuch_filenames_t *filenames);
+
+
+/**
+ * set config 'key' to 'value'
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
+
+/**
+ * retrieve config item 'key', assign to  'value'
+ *
+ * keys which have not been previously set with n_d_set_config will
+ * return an empty string.
+ *
+ * return value is allocated by malloc and should be freed by the
+ * caller.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
+
+/**
+ * Create an iterator for all config items with keys matching a given prefix
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_status_t
+notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out);
+
+/**
+ * Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be called).
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_bool_t
+notmuch_config_list_valid (notmuch_config_list_t *config_list);
+
+/**
+ * return key for current config pair
+ *
+ * return value is owned by the iterator, and will be destroyed by the
+ * next call to notmuch_config_list_key or notmuch_config_list_destroy.
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+const char *
+notmuch_config_list_key (notmuch_config_list_t *config_list);
+
+/**
+ * return 'value' for current config pair
+ *
+ * return value is owned by the iterator, and will be destroyed by the
+ * next call to notmuch_config_list_value or notmuch config_list_destroy
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+const char *
+notmuch_config_list_value (notmuch_config_list_t *config_list);
+
+
+/**
+ * move 'config_list' iterator to the next pair
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+void
+notmuch_config_list_move_to_next (notmuch_config_list_t *config_list);
+
+/**
+ * free any resources held by 'config_list'
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+void
+notmuch_config_list_destroy (notmuch_config_list_t *config_list);
+
+
+/**
+ * get the current default indexing options for a given database.
+ *
+ * This object will survive until the database itself is destroyed,
+ * but the caller may also release it earlier with
+ * notmuch_indexopts_destroy.
+ *
+ * This object represents a set of options on how a message can be
+ * added to the index.  At the moment it is a featureless stub.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db);
+
+/**
+ * Stating a policy about how to decrypt messages.
+ *
+ * See index.decrypt in notmuch-config(1) for more details.
+ */
+typedef enum {
+    NOTMUCH_DECRYPT_FALSE,
+    NOTMUCH_DECRYPT_TRUE,
+    NOTMUCH_DECRYPT_AUTO,
+    NOTMUCH_DECRYPT_NOSTASH,
+} notmuch_decryption_policy_t;
+
+/**
+ * Specify whether to decrypt encrypted parts while indexing.
+ *
+ * Be aware that the index is likely sufficient to reconstruct the
+ * cleartext of the message itself, so please ensure that the notmuch
+ * message index is adequately protected. DO NOT SET THIS FLAG TO TRUE
+ * without considering the security of your index.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_indexopts_set_decrypt_policy (notmuch_indexopts_t *indexopts,
+                                     notmuch_decryption_policy_t decrypt_policy);
+
+/**
+ * Return whether to decrypt encrypted parts while indexing.
+ * see notmuch_indexopts_set_decrypt_policy.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_decryption_policy_t
+notmuch_indexopts_get_decrypt_policy (const notmuch_indexopts_t *indexopts);
+
+/**
+ * Destroy a notmuch_indexopts_t object.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *options);
+
+
+/**
+ * interrogate the library for compile time features
+ *
+ * @since libnotmuch 4.4 (notmuch 0.23)
+ */
+notmuch_bool_t
+notmuch_built_with (const char *name);
+/* @} */
+
+#pragma GCC visibility pop
+
+NOTMUCH_END_DECLS
+
+#endif
diff --git a/lib/notmuch.sym b/lib/notmuch.sym
new file mode 100644 (file)
index 0000000..7d0c0af
--- /dev/null
@@ -0,0 +1,7 @@
+{
+global:
+       _ZTI*;
+       _ZTS*;
+       notmuch_*;
+local: *;
+};
diff --git a/lib/parse-time-vrp.cc b/lib/parse-time-vrp.cc
new file mode 100644 (file)
index 0000000..dd69149
--- /dev/null
@@ -0,0 +1,87 @@
+/* parse-time-vrp.cc - date range query glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2012 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/ .
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#include "database-private.h"
+#include "parse-time-vrp.h"
+#include "parse-time-string.h"
+
+#define PREFIX "date:"
+
+/* See *ValueRangeProcessor in xapian-core/api/valuerangeproc.cc */
+Xapian::valueno
+ParseTimeValueRangeProcessor::operator() (std::string &begin, std::string &end)
+{
+    time_t t, now;
+    std::string b;
+
+    /* Require date: prefix in start of the range... */
+    if (STRNCMP_LITERAL (begin.c_str (), PREFIX))
+       return Xapian::BAD_VALUENO;
+
+    /* ...and remove it. */
+    begin.erase (0, sizeof (PREFIX) - 1);
+    b = begin;
+
+    /* Use the same 'now' for begin and end. */
+    if (time (&now) == (time_t) -1)
+       return Xapian::BAD_VALUENO;
+
+    if (!begin.empty ()) {
+       if (parse_time_string (begin.c_str (), &t, &now, PARSE_TIME_ROUND_DOWN))
+           return Xapian::BAD_VALUENO;
+
+       begin.assign (Xapian::sortable_serialise ((double) t));
+    }
+
+    if (!end.empty ()) {
+       if (end == "!" && ! b.empty ())
+           end = b;
+
+       if (parse_time_string (end.c_str (), &t, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
+           return Xapian::BAD_VALUENO;
+
+       end.assign (Xapian::sortable_serialise ((double) t));
+    }
+
+    return valno;
+}
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+/* XXX TODO: is throwing an exception the right thing to do here? */
+Xapian::Query DateFieldProcessor::operator()(const std::string & str) {
+    time_t from, to, now;
+
+    /* Use the same 'now' for begin and end. */
+    if (time (&now) == (time_t) -1)
+       throw Xapian::QueryParserError("Unable to get current time");
+
+    if (parse_time_string (str.c_str (), &from, &now, PARSE_TIME_ROUND_DOWN))
+       throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+
+    if (parse_time_string (str.c_str (), &to, &now, PARSE_TIME_ROUND_UP_INCLUSIVE))
+       throw Xapian::QueryParserError ("Didn't understand date specification '" + str + "'");
+
+    return Xapian::Query(Xapian::Query::OP_AND,
+                        Xapian::Query(Xapian::Query::OP_VALUE_GE, 0, Xapian::sortable_serialise ((double) from)),
+                        Xapian::Query(Xapian::Query::OP_VALUE_LE, 0, Xapian::sortable_serialise ((double) to)));
+}
+#endif
diff --git a/lib/parse-time-vrp.h b/lib/parse-time-vrp.h
new file mode 100644 (file)
index 0000000..c024dba
--- /dev/null
@@ -0,0 +1,45 @@
+/* parse-time-vrp.h - date range query glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2012 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/ .
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#ifndef NOTMUCH_PARSE_TIME_VRP_H
+#define NOTMUCH_PARSE_TIME_VRP_H
+
+#include <xapian.h>
+
+/* see *ValueRangeProcessor in xapian-core/include/xapian/queryparser.h */
+class ParseTimeValueRangeProcessor : public Xapian::ValueRangeProcessor {
+protected:
+    Xapian::valueno valno;
+
+public:
+    ParseTimeValueRangeProcessor (Xapian::valueno slot_)
+       : valno(slot_) { }
+
+    Xapian::valueno operator() (std::string &begin, std::string &end);
+};
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+class DateFieldProcessor : public Xapian::FieldProcessor {
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
+#endif /* NOTMUCH_PARSE_TIME_VRP_H */
diff --git a/lib/query-fp.cc b/lib/query-fp.cc
new file mode 100644 (file)
index 0000000..c39f591
--- /dev/null
@@ -0,0 +1,43 @@
+/* query-fp.cc - "query:" field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "database-private.h"
+#include "query-fp.h"
+#include <iostream>
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+
+Xapian::Query
+QueryFieldProcessor::operator() (const std::string & name)
+{
+    std::string key = "query." + name;
+    char *expansion;
+    notmuch_status_t status;
+
+    status = notmuch_database_get_config (notmuch, key.c_str (), &expansion);
+    if (status) {
+       throw Xapian::QueryParserError ("error looking up key" + name);
+    }
+
+    return parser.parse_query (expansion, NOTMUCH_QUERY_PARSER_FLAGS);
+}
+#endif
diff --git a/lib/query-fp.h b/lib/query-fp.h
new file mode 100644 (file)
index 0000000..d6e4b31
--- /dev/null
@@ -0,0 +1,42 @@
+/* query-fp.h - query field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#ifndef NOTMUCH_QUERY_FP_H
+#define NOTMUCH_QUERY_FP_H
+
+#include <xapian.h>
+#include "notmuch.h"
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+class QueryFieldProcessor : public Xapian::FieldProcessor {
+ protected:
+    Xapian::QueryParser &parser;
+    notmuch_database_t *notmuch;
+
+ public:
+    QueryFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_)
+       : parser(parser_), notmuch(notmuch_) { };
+
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
+#endif /* NOTMUCH_QUERY_FP_H */
diff --git a/lib/query.cc b/lib/query.cc
new file mode 100644 (file)
index 0000000..7fdf992
--- /dev/null
@@ -0,0 +1,732 @@
+/* query.cc - Support for searching a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#include <glib.h> /* GHashTable, GPtrArray */
+
+struct _notmuch_query {
+    notmuch_database_t *notmuch;
+    const char *query_string;
+    notmuch_sort_t sort;
+    notmuch_string_list_t *exclude_terms;
+    notmuch_exclude_t omit_excluded;
+    bool parsed;
+    Xapian::Query xapian_query;
+    std::set<std::string> terms;
+};
+
+typedef struct _notmuch_mset_messages {
+    notmuch_messages_t base;
+    notmuch_database_t *notmuch;
+    Xapian::MSetIterator iterator;
+    Xapian::MSetIterator iterator_end;
+} notmuch_mset_messages_t;
+
+struct _notmuch_doc_id_set {
+    unsigned char *bitmap;
+    unsigned int bound;
+};
+
+#define DOCIDSET_WORD(bit) ((bit) / CHAR_BIT)
+#define DOCIDSET_BIT(bit) ((bit) % CHAR_BIT)
+
+struct _notmuch_threads {
+    notmuch_query_t *query;
+
+    /* The ordered list of doc ids matched by the query. */
+    GArray *doc_ids;
+    /* Our iterator's current position in doc_ids. */
+    unsigned int doc_id_pos;
+    /* The set of matched docid's that have not been assigned to a
+     * thread. Initially, this contains every docid in doc_ids. */
+    notmuch_doc_id_set_t match_set;
+};
+
+/* We need this in the message functions so forward declare. */
+static bool
+_notmuch_doc_id_set_init (void *ctx,
+                         notmuch_doc_id_set_t *doc_ids,
+                         GArray *arr);
+
+static bool
+_debug_query (void)
+{
+    char *env = getenv ("NOTMUCH_DEBUG_QUERY");
+    return (env && strcmp (env, "") != 0);
+}
+
+/* Explicit destructor call for placement new */
+static int
+_notmuch_query_destructor (notmuch_query_t *query) {
+    query->xapian_query.~Query();
+    query->terms.~set<std::string>();
+    return 0;
+}
+
+notmuch_query_t *
+notmuch_query_create (notmuch_database_t *notmuch,
+                     const char *query_string)
+{
+    notmuch_query_t *query;
+
+    if (_debug_query ())
+       fprintf (stderr, "Query string is:\n%s\n", query_string);
+
+    query = talloc (notmuch, notmuch_query_t);
+    if (unlikely (query == NULL))
+       return NULL;
+
+    new (&query->xapian_query) Xapian::Query ();
+    new (&query->terms) std::set<std::string> ();
+    query->parsed = false;
+
+    talloc_set_destructor (query, _notmuch_query_destructor);
+
+    query->notmuch = notmuch;
+
+    query->query_string = talloc_strdup (query, query_string);
+
+    query->sort = NOTMUCH_SORT_NEWEST_FIRST;
+
+    query->exclude_terms = _notmuch_string_list_create (query);
+
+    query->omit_excluded = NOTMUCH_EXCLUDE_TRUE;
+
+    return query;
+}
+
+static notmuch_status_t
+_notmuch_query_ensure_parsed (notmuch_query_t *query)
+{
+    if (query->parsed)
+       return NOTMUCH_STATUS_SUCCESS;
+
+    try {
+       query->xapian_query =
+           query->notmuch->query_parser->
+               parse_query (query->query_string, NOTMUCH_QUERY_PARSER_FLAGS);
+
+       /* Xapian doesn't support skip_to on terms from a query since
+       *  they are unordered, so cache a copy of all terms in
+       *  something searchable.
+       */
+
+       for (Xapian::TermIterator t = query->xapian_query.get_terms_begin ();
+            t != query->xapian_query.get_terms_end (); ++t)
+           query->terms.insert (*t);
+
+       query->parsed = true;
+
+    } catch (const Xapian::Error &error) {
+       if (!query->notmuch->exception_reported) {
+           _notmuch_database_log (query->notmuch,
+                                  "A Xapian exception occurred parsing query: %s\n",
+                                  error.get_msg ().c_str ());
+           _notmuch_database_log_append (query->notmuch,
+                                         "Query string was: %s\n",
+                                         query->query_string);
+           query->notmuch->exception_reported = true;
+       }
+
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+const char *
+notmuch_query_get_query_string (const notmuch_query_t *query)
+{
+    return query->query_string;
+}
+
+void
+notmuch_query_set_omit_excluded (notmuch_query_t *query,
+                                notmuch_exclude_t omit_excluded)
+{
+    query->omit_excluded = omit_excluded;
+}
+
+void
+notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
+{
+    query->sort = sort;
+}
+
+notmuch_sort_t
+notmuch_query_get_sort (const notmuch_query_t *query)
+{
+    return query->sort;
+}
+
+notmuch_status_t
+notmuch_query_add_tag_exclude (notmuch_query_t *query, const char *tag)
+{
+    notmuch_status_t status;
+    char *term;
+
+    status = _notmuch_query_ensure_parsed (query);
+    if (status)
+       return status;
+
+    term = talloc_asprintf (query, "%s%s", _find_prefix ("tag"), tag);
+    if (query->terms.count(term) != 0)
+       return NOTMUCH_STATUS_IGNORED;
+
+    _notmuch_string_list_append (query->exclude_terms, term);
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* We end up having to call the destructors explicitly because we had
+ * to use "placement new" in order to initialize C++ objects within a
+ * block that we allocated with talloc. So C++ is making talloc
+ * slightly less simple to use, (we wouldn't need
+ * talloc_set_destructor at all otherwise).
+ */
+static int
+_notmuch_messages_destructor (notmuch_mset_messages_t *messages)
+{
+    messages->iterator.~MSetIterator ();
+    messages->iterator_end.~MSetIterator ();
+
+    return 0;
+}
+
+/* Return a query that matches messages with the excluded tags
+ * registered with query. The caller of this function has to combine the returned
+ * query appropriately.*/
+static Xapian::Query
+_notmuch_exclude_tags (notmuch_query_t *query)
+{
+    Xapian::Query exclude_query = Xapian::Query::MatchNothing;
+
+    for (notmuch_string_node_t *term = query->exclude_terms->head; term;
+        term = term->next) {
+       exclude_query = Xapian::Query (Xapian::Query::OP_OR,
+                                      exclude_query, Xapian::Query (term->string));
+    }
+    return exclude_query;
+}
+
+
+notmuch_status_t
+notmuch_query_search_messages_st (notmuch_query_t *query,
+                                 notmuch_messages_t **out)
+{
+    return notmuch_query_search_messages (query, out);
+}
+
+notmuch_status_t
+notmuch_query_search_messages (notmuch_query_t *query,
+                                 notmuch_messages_t **out)
+{
+    return _notmuch_query_search_documents (query, "mail", out);
+}
+
+notmuch_status_t
+_notmuch_query_search_documents (notmuch_query_t *query,
+                                const char *type,
+                                notmuch_messages_t **out)
+{
+    notmuch_database_t *notmuch = query->notmuch;
+    const char *query_string = query->query_string;
+    notmuch_mset_messages_t *messages;
+    notmuch_status_t status;
+
+    status = _notmuch_query_ensure_parsed (query);
+    if (status)
+       return status;
+
+    messages = talloc (query, notmuch_mset_messages_t);
+    if (unlikely (messages == NULL))
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    try {
+
+       messages->base.is_of_list_type = false;
+       messages->base.iterator = NULL;
+       messages->notmuch = notmuch;
+       new (&messages->iterator) Xapian::MSetIterator ();
+       new (&messages->iterator_end) Xapian::MSetIterator ();
+
+       talloc_set_destructor (messages, _notmuch_messages_destructor);
+
+       Xapian::Enquire enquire (*notmuch->xapian_db);
+       Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
+                                                  _find_prefix ("type"),
+                                                  type));
+       Xapian::Query final_query, exclude_query;
+       Xapian::MSet mset;
+       Xapian::MSetIterator iterator;
+
+       if (strcmp (query_string, "") == 0 ||
+           strcmp (query_string, "*") == 0)
+       {
+           final_query = mail_query;
+       } else {
+           final_query = Xapian::Query (Xapian::Query::OP_AND,
+                                        mail_query, query->xapian_query);
+       }
+       messages->base.excluded_doc_ids = NULL;
+
+       if ((query->omit_excluded != NOTMUCH_EXCLUDE_FALSE) && (query->exclude_terms)) {
+           exclude_query = _notmuch_exclude_tags (query);
+
+           if (query->omit_excluded == NOTMUCH_EXCLUDE_TRUE ||
+               query->omit_excluded == NOTMUCH_EXCLUDE_ALL)
+           {
+               final_query = Xapian::Query (Xapian::Query::OP_AND_NOT,
+                                            final_query, exclude_query);
+           } else { /* NOTMUCH_EXCLUDE_FLAG */
+               exclude_query = Xapian::Query (Xapian::Query::OP_AND,
+                                          exclude_query, final_query);
+
+               enquire.set_weighting_scheme (Xapian::BoolWeight());
+               enquire.set_query (exclude_query);
+
+               mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
+
+               GArray *excluded_doc_ids = g_array_new (false, false, sizeof (unsigned int));
+
+               for (iterator = mset.begin (); iterator != mset.end (); iterator++) {
+                   unsigned int doc_id = *iterator;
+                   g_array_append_val (excluded_doc_ids, doc_id);
+               }
+               messages->base.excluded_doc_ids = talloc (messages, _notmuch_doc_id_set);
+               _notmuch_doc_id_set_init (query, messages->base.excluded_doc_ids,
+                                         excluded_doc_ids);
+               g_array_unref (excluded_doc_ids);
+           }
+       }
+
+
+       enquire.set_weighting_scheme (Xapian::BoolWeight());
+
+       switch (query->sort) {
+       case NOTMUCH_SORT_OLDEST_FIRST:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, false);
+           break;
+       case NOTMUCH_SORT_NEWEST_FIRST:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, true);
+           break;
+       case NOTMUCH_SORT_MESSAGE_ID:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, false);
+           break;
+       case NOTMUCH_SORT_UNSORTED:
+           break;
+       }
+
+       if (_debug_query ()) {
+           fprintf (stderr, "Exclude query is:\n%s\n",
+                    exclude_query.get_description ().c_str ());
+           fprintf (stderr, "Final query is:\n%s\n",
+                    final_query.get_description ().c_str ());
+       }
+
+       enquire.set_query (final_query);
+
+       mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
+
+       messages->iterator = mset.begin ();
+       messages->iterator_end = mset.end ();
+
+       *out = &messages->base;
+       return NOTMUCH_STATUS_SUCCESS;
+
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch,
+                              "A Xapian exception occurred performing query: %s\n",
+                              error.get_msg().c_str());
+       _notmuch_database_log_append (notmuch,
+                              "Query string was: %s\n",
+                              query->query_string);
+
+       notmuch->exception_reported = true;
+       talloc_free (messages);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+}
+
+bool
+_notmuch_mset_messages_valid (notmuch_messages_t *messages)
+{
+    notmuch_mset_messages_t *mset_messages;
+
+    mset_messages = (notmuch_mset_messages_t *) messages;
+
+    return (mset_messages->iterator != mset_messages->iterator_end);
+}
+
+static Xapian::docid
+_notmuch_mset_messages_get_doc_id (notmuch_messages_t *messages)
+{
+    notmuch_mset_messages_t *mset_messages;
+
+    mset_messages = (notmuch_mset_messages_t *) messages;
+
+    if (! _notmuch_mset_messages_valid (&mset_messages->base))
+       return 0;
+
+    return *mset_messages->iterator;
+}
+
+notmuch_message_t *
+_notmuch_mset_messages_get (notmuch_messages_t *messages)
+{
+    notmuch_message_t *message;
+    Xapian::docid doc_id;
+    notmuch_private_status_t status;
+    notmuch_mset_messages_t *mset_messages;
+
+    mset_messages = (notmuch_mset_messages_t *) messages;
+
+    if (! _notmuch_mset_messages_valid (&mset_messages->base))
+       return NULL;
+
+    doc_id = *mset_messages->iterator;
+
+    message = _notmuch_message_create (mset_messages,
+                                      mset_messages->notmuch, doc_id,
+                                      &status);
+
+    if (message == NULL &&
+       status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+    {
+       INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n");
+    }
+
+    if (messages->excluded_doc_ids &&
+       _notmuch_doc_id_set_contains (messages->excluded_doc_ids, doc_id))
+       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
+
+    return message;
+}
+
+void
+_notmuch_mset_messages_move_to_next (notmuch_messages_t *messages)
+{
+    notmuch_mset_messages_t *mset_messages;
+
+    mset_messages = (notmuch_mset_messages_t *) messages;
+
+    mset_messages->iterator++;
+}
+
+static bool
+_notmuch_doc_id_set_init (void *ctx,
+                         notmuch_doc_id_set_t *doc_ids,
+                         GArray *arr)
+{
+    unsigned int max = 0;
+    unsigned char *bitmap;
+
+    for (unsigned int i = 0; i < arr->len; i++)
+       max = MAX(max, g_array_index (arr, unsigned int, i));
+    bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1);
+
+    if (bitmap == NULL)
+       return false;
+
+    doc_ids->bitmap = bitmap;
+    doc_ids->bound = max + 1;
+
+    for (unsigned int i = 0; i < arr->len; i++) {
+       unsigned int doc_id = g_array_index (arr, unsigned int, i);
+       bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id);
+    }
+
+    return true;
+}
+
+bool
+_notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
+                             unsigned int doc_id)
+{
+    if (doc_id >= doc_ids->bound)
+       return false;
+    return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id));
+}
+
+void
+_notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
+                           unsigned int doc_id)
+{
+    if (doc_id < doc_ids->bound)
+       doc_ids->bitmap[DOCIDSET_WORD(doc_id)] &= ~(1 << DOCIDSET_BIT(doc_id));
+}
+
+/* Glib objects force use to use a talloc destructor as well, (but not
+ * nearly as ugly as the for messages due to C++ objects). At
+ * this point, I'd really like to have some talloc-friendly
+ * equivalents for the few pieces of glib that I'm using. */
+static int
+_notmuch_threads_destructor (notmuch_threads_t *threads)
+{
+    if (threads->doc_ids)
+       g_array_unref (threads->doc_ids);
+
+    return 0;
+}
+
+notmuch_status_t
+notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out)
+{
+    return notmuch_query_search_threads(query, out);
+}
+
+notmuch_status_t
+notmuch_query_search_threads (notmuch_query_t *query,
+                             notmuch_threads_t **out)
+{
+    notmuch_threads_t *threads;
+    notmuch_messages_t *messages;
+    notmuch_status_t status;
+
+    threads = talloc (query, notmuch_threads_t);
+    if (threads == NULL)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+    threads->doc_ids = NULL;
+    talloc_set_destructor (threads, _notmuch_threads_destructor);
+
+    threads->query = query;
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (status) {
+       talloc_free (threads);
+       return status;
+    }
+
+    threads->doc_ids = g_array_new (false, false, sizeof (unsigned int));
+    while (notmuch_messages_valid (messages)) {
+       unsigned int doc_id = _notmuch_mset_messages_get_doc_id (messages);
+       g_array_append_val (threads->doc_ids, doc_id);
+       notmuch_messages_move_to_next (messages);
+    }
+    threads->doc_id_pos = 0;
+
+    talloc_free (messages);
+
+    if (! _notmuch_doc_id_set_init (threads, &threads->match_set,
+                                   threads->doc_ids)) {
+       talloc_free (threads);
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+    }
+
+    *out = threads;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+void
+notmuch_query_destroy (notmuch_query_t *query)
+{
+    talloc_free (query);
+}
+
+notmuch_bool_t
+notmuch_threads_valid (notmuch_threads_t *threads)
+{
+    unsigned int doc_id;
+
+    if (! threads)
+       return false;
+
+    while (threads->doc_id_pos < threads->doc_ids->len) {
+       doc_id = g_array_index (threads->doc_ids, unsigned int,
+                               threads->doc_id_pos);
+       if (_notmuch_doc_id_set_contains (&threads->match_set, doc_id))
+           break;
+
+       threads->doc_id_pos++;
+    }
+
+    return threads->doc_id_pos < threads->doc_ids->len;
+}
+
+notmuch_thread_t *
+notmuch_threads_get (notmuch_threads_t *threads)
+{
+    unsigned int doc_id;
+
+    if (! notmuch_threads_valid (threads))
+       return NULL;
+
+    doc_id = g_array_index (threads->doc_ids, unsigned int,
+                           threads->doc_id_pos);
+    return _notmuch_thread_create (threads->query,
+                                  threads->query->notmuch,
+                                  doc_id,
+                                  &threads->match_set,
+                                  threads->query->exclude_terms,
+                                  threads->query->omit_excluded,
+                                  threads->query->sort);
+}
+
+void
+notmuch_threads_move_to_next (notmuch_threads_t *threads)
+{
+    threads->doc_id_pos++;
+}
+
+void
+notmuch_threads_destroy (notmuch_threads_t *threads)
+{
+    talloc_free (threads);
+}
+
+notmuch_status_t
+notmuch_query_count_messages_st (notmuch_query_t *query, unsigned *count_out)
+{
+    return notmuch_query_count_messages (query, count_out);
+}
+
+notmuch_status_t
+notmuch_query_count_messages (notmuch_query_t *query, unsigned *count_out)
+{
+    return _notmuch_query_count_documents (query, "mail", count_out);
+}
+
+notmuch_status_t
+_notmuch_query_count_documents (notmuch_query_t *query, const char *type, unsigned *count_out)
+{
+    notmuch_database_t *notmuch = query->notmuch;
+    const char *query_string = query->query_string;
+    Xapian::doccount count = 0;
+    notmuch_status_t status;
+
+    status = _notmuch_query_ensure_parsed (query);
+    if (status)
+       return status;
+
+    try {
+       Xapian::Enquire enquire (*notmuch->xapian_db);
+       Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
+                                                  _find_prefix ("type"),
+                                                  type));
+       Xapian::Query final_query, exclude_query;
+       Xapian::MSet mset;
+
+       if (strcmp (query_string, "") == 0 ||
+           strcmp (query_string, "*") == 0)
+       {
+           final_query = mail_query;
+       } else {
+           final_query = Xapian::Query (Xapian::Query::OP_AND,
+                                        mail_query, query->xapian_query);
+       }
+
+       exclude_query = _notmuch_exclude_tags (query);
+
+       final_query = Xapian::Query (Xapian::Query::OP_AND_NOT,
+                                        final_query, exclude_query);
+
+       enquire.set_weighting_scheme(Xapian::BoolWeight());
+       enquire.set_docid_order(Xapian::Enquire::ASCENDING);
+
+       if (_debug_query ()) {
+           fprintf (stderr, "Exclude query is:\n%s\n",
+                    exclude_query.get_description ().c_str ());
+           fprintf (stderr, "Final query is:\n%s\n",
+                    final_query.get_description ().c_str ());
+       }
+
+       enquire.set_query (final_query);
+
+       /*
+        * Set the checkatleast parameter to the number of documents
+        * in the database to make get_matches_estimated() exact.
+        * Set the max parameter to 1 to avoid fetching documents we will discard.
+        */
+       mset = enquire.get_mset (0, 1,
+                                notmuch->xapian_db->get_doccount ());
+
+       count = mset.get_matches_estimated();
+
+    } catch (const Xapian::Error &error) {
+       _notmuch_database_log (notmuch,
+                              "A Xapian exception occurred performing query: %s\n",
+                              error.get_msg().c_str());
+       _notmuch_database_log_append (notmuch,
+                                     "Query string was: %s\n",
+                                     query->query_string);
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    *count_out = count;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_query_count_threads_st (notmuch_query_t *query, unsigned *count)
+{
+    return notmuch_query_count_threads (query, count);
+}
+
+notmuch_status_t
+notmuch_query_count_threads (notmuch_query_t *query, unsigned *count)
+{
+    notmuch_messages_t *messages;
+    GHashTable *hash;
+    notmuch_sort_t sort;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    sort = query->sort;
+    query->sort = NOTMUCH_SORT_UNSORTED;
+    ret = notmuch_query_search_messages (query, &messages);
+    if (ret)
+       return ret;
+    query->sort = sort;
+    if (messages == NULL)
+       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+
+    hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+    if (hash == NULL) {
+       talloc_free (messages);
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+    }
+
+    while (notmuch_messages_valid (messages)) {
+       notmuch_message_t *message = notmuch_messages_get (messages);
+       const char *thread_id = notmuch_message_get_thread_id (message);
+       char *thread_id_copy = talloc_strdup (messages, thread_id);
+       if (unlikely (thread_id_copy == NULL)) {
+           notmuch_message_destroy (message);
+           ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+           goto DONE;
+       }
+       g_hash_table_insert (hash, thread_id_copy, NULL);
+       notmuch_message_destroy (message);
+       notmuch_messages_move_to_next (messages);
+    }
+
+    *count = g_hash_table_size (hash);
+
+  DONE:
+    g_hash_table_unref (hash);
+    talloc_free (messages);
+
+    return ret;
+}
+
+notmuch_database_t *
+notmuch_query_get_database (const notmuch_query_t *query)
+{
+    return query->notmuch;
+}
diff --git a/lib/regexp-fields.cc b/lib/regexp-fields.cc
new file mode 100644 (file)
index 0000000..084bc8c
--- /dev/null
@@ -0,0 +1,210 @@
+/* regexp-fields.cc - field processor glue for regex supporting fields
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2015 Austin Clements
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: Austin Clements <aclements@csail.mit.edu>
+ *                David Bremner <david@tethera.net>
+ */
+
+#include "regexp-fields.h"
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+static void
+compile_regex (regex_t &regexp, const char *str)
+{
+    int err = regcomp (&regexp, str, REG_EXTENDED | REG_NOSUB);
+
+    if (err != 0) {
+       size_t len = regerror (err, &regexp, NULL, 0);
+       char *buffer = new char[len];
+       std::string msg;
+       (void) regerror (err, &regexp, buffer, len);
+       msg.assign (buffer, len);
+       delete[] buffer;
+
+       throw Xapian::QueryParserError (msg);
+    }
+}
+
+RegexpPostingSource::RegexpPostingSource (Xapian::valueno slot, const std::string &regexp)
+    : slot_ (slot)
+{
+    compile_regex (regexp_, regexp.c_str ());
+}
+
+RegexpPostingSource::~RegexpPostingSource ()
+{
+    regfree (&regexp_);
+}
+
+void
+RegexpPostingSource::init (const Xapian::Database &db)
+{
+    db_ = db;
+    it_ = db_.valuestream_begin (slot_);
+    end_ = db.valuestream_end (slot_);
+    started_ = false;
+}
+
+Xapian::doccount
+RegexpPostingSource::get_termfreq_min () const
+{
+    return 0;
+}
+
+Xapian::doccount
+RegexpPostingSource::get_termfreq_est () const
+{
+    return get_termfreq_max () / 2;
+}
+
+Xapian::doccount
+RegexpPostingSource::get_termfreq_max () const
+{
+    return db_.get_value_freq (slot_);
+}
+
+Xapian::docid
+RegexpPostingSource::get_docid () const
+{
+    return it_.get_docid ();
+}
+
+bool
+RegexpPostingSource::at_end () const
+{
+    return it_ == end_;
+}
+
+void
+RegexpPostingSource::next (unused (double min_wt))
+{
+    if (started_ && ! at_end ())
+       ++it_;
+    started_ = true;
+
+    for (; ! at_end (); ++it_) {
+       std::string value = *it_;
+       if (regexec (&regexp_, value.c_str (), 0, NULL, 0) == 0)
+           break;
+    }
+}
+
+void
+RegexpPostingSource::skip_to (Xapian::docid did, unused (double min_wt))
+{
+    started_ = true;
+    it_.skip_to (did);
+    for (; ! at_end (); ++it_) {
+       std::string value = *it_;
+       if (regexec (&regexp_, value.c_str (), 0, NULL, 0) == 0)
+           break;
+    }
+}
+
+bool
+RegexpPostingSource::check (Xapian::docid did, unused (double min_wt))
+{
+    started_ = true;
+    if (!it_.check (did) || at_end ())
+       return false;
+    return (regexec (&regexp_, (*it_).c_str (), 0, NULL, 0) == 0);
+}
+
+static inline Xapian::valueno _find_slot (std::string prefix)
+{
+    if (prefix == "from")
+       return NOTMUCH_VALUE_FROM;
+    else if (prefix == "subject")
+       return NOTMUCH_VALUE_SUBJECT;
+    else if (prefix == "mid")
+       return NOTMUCH_VALUE_MESSAGE_ID;
+    else
+       return Xapian::BAD_VALUENO;
+}
+
+RegexpFieldProcessor::RegexpFieldProcessor (std::string prefix,
+                                           notmuch_field_flag_t options_,
+                                           Xapian::QueryParser &parser_,
+                                           notmuch_database_t *notmuch_)
+       : slot (_find_slot (prefix)),
+         term_prefix (_find_prefix (prefix.c_str ())),
+         options (options_),
+         parser (parser_),
+         notmuch (notmuch_)
+{
+};
+
+Xapian::Query
+RegexpFieldProcessor::operator() (const std::string & str)
+{
+    if (str.empty ()) {
+       if (options & NOTMUCH_FIELD_PROBABILISTIC) {
+           return Xapian::Query(Xapian::Query::OP_AND_NOT,
+                            Xapian::Query::MatchAll,
+                            Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
+       } else {
+           return Xapian::Query (term_prefix);
+       }
+    }
+
+    if (str.at (0) == '/') {
+       if (str.length() > 1 && str.at (str.size () - 1) == '/'){
+           std::string regexp_str = str.substr(1,str.size () - 2);
+           if (slot != Xapian::BAD_VALUENO) {
+               RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
+               return Xapian::Query (postings->release ());
+           } else {
+               std::vector<std::string> terms;
+               regex_t regexp;
+
+               compile_regex(regexp, regexp_str.c_str ());
+               for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
+                    it != notmuch->xapian_db->allterms_end (); ++it) {
+                   if (regexec (&regexp, (*it).c_str () + term_prefix.size(),
+                                0, NULL, 0) == 0)
+                       terms.push_back(*it);
+               }
+               return Xapian::Query (Xapian::Query::OP_OR, terms.begin(), terms.end());
+           }
+       } else {
+           throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'");
+       }
+    } else {
+       if (options & NOTMUCH_FIELD_PROBABILISTIC) {
+           /* TODO replace this with a nicer API level triggering of
+            * phrase parsing, when possible */
+           std::string query_str;
+
+           if (str.find (' ') != std::string::npos)
+               query_str = '"' + str + '"';
+           else
+               query_str = str;
+
+           return parser.parse_query (query_str, NOTMUCH_QUERY_PARSER_FLAGS, term_prefix);
+       } else {
+           /* Boolean prefix */
+           std::string term = term_prefix + str;
+           return Xapian::Query (term);
+       }
+    }
+}
+#endif
diff --git a/lib/regexp-fields.h b/lib/regexp-fields.h
new file mode 100644 (file)
index 0000000..d5f9344
--- /dev/null
@@ -0,0 +1,81 @@
+/* regex-fields.h - xapian glue for semi-bruteforce regexp search
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2015 Austin Clements
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: Austin Clements <aclements@csail.mit.edu>
+ *                David Bremner <david@tethera.net>
+ */
+
+#ifndef NOTMUCH_REGEXP_FIELDS_H
+#define NOTMUCH_REGEXP_FIELDS_H
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+#include <sys/types.h>
+#include <regex.h>
+#include "database-private.h"
+#include "notmuch-private.h"
+
+/* A posting source that returns documents where a value matches a
+ * regexp.
+ */
+class RegexpPostingSource : public Xapian::PostingSource
+{
+ protected:
+    const Xapian::valueno slot_;
+    regex_t regexp_;
+    Xapian::Database db_;
+    bool started_;
+    Xapian::ValueIterator it_, end_;
+
+/* No copying */
+    RegexpPostingSource (const RegexpPostingSource &);
+    RegexpPostingSource &operator= (const RegexpPostingSource &);
+
+ public:
+    RegexpPostingSource (Xapian::valueno slot, const std::string &regexp);
+    ~RegexpPostingSource ();
+    void init (const Xapian::Database &db);
+    Xapian::doccount get_termfreq_min () const;
+    Xapian::doccount get_termfreq_est () const;
+    Xapian::doccount get_termfreq_max () const;
+    Xapian::docid get_docid () const;
+    bool at_end () const;
+    void next (unused (double min_wt));
+    void skip_to (Xapian::docid did, unused (double min_wt));
+    bool check (Xapian::docid did, unused (double min_wt));
+};
+
+
+class RegexpFieldProcessor : public Xapian::FieldProcessor {
+ protected:
+    Xapian::valueno slot;
+    std::string term_prefix;
+    notmuch_field_flag_t options;
+    Xapian::QueryParser &parser;
+    notmuch_database_t *notmuch;
+
+ public:
+    RegexpFieldProcessor (std::string prefix, notmuch_field_flag_t options,
+                         Xapian::QueryParser &parser_, notmuch_database_t *notmuch_);
+
+    ~RegexpFieldProcessor () { };
+
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
+#endif /* NOTMUCH_REGEXP_FIELDS_H */
diff --git a/lib/sha1.c b/lib/sha1.c
new file mode 100644 (file)
index 0000000..cb55b49
--- /dev/null
@@ -0,0 +1,93 @@
+/* sha1.c - Interfaces to SHA-1 hash for the notmuch mail system
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include <glib.h>
+
+/* Create a hexadecimal string version of the SHA-1 digest of 'str'
+ * (including its null terminating character).
+ *
+ * This function returns a newly allocated string which the caller
+ * should free() when finished.
+ */
+char *
+_notmuch_sha1_of_string (const char *str)
+{
+    GChecksum *sha1;
+    char *digest;
+
+    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);
+
+    return digest;
+}
+
+/* Create a hexadecimal string version of the SHA-1 digest of the
+ * contents of the named file.
+ *
+ * This function returns a newly allocated string which the caller
+ * should free() when finished.
+ *
+ * If any error occurs while reading the file, (permission denied,
+ * file not found, etc.), this function returns NULL.
+ */
+char *
+_notmuch_sha1_of_file (const char *filename)
+{
+    FILE *file;
+#define BLOCK_SIZE 4096
+    unsigned char block[BLOCK_SIZE];
+    size_t bytes_read;
+    GChecksum *sha1;
+    char *digest = NULL;
+
+    file = fopen (filename, "r");
+    if (file == NULL)
+       return NULL;
+
+    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))
+               break;
+           else if (ferror (file))
+               goto DONE;
+       } else {
+           g_checksum_update (sha1, block, bytes_read);
+       }
+    }
+
+    digest = xstrdup (g_checksum_get_string (sha1));
+
+  DONE:
+    if (sha1)
+       g_checksum_free (sha1);
+    if (file)
+       fclose (file);
+
+    return digest;
+}
diff --git a/lib/string-list.c b/lib/string-list.c
new file mode 100644 (file)
index 0000000..9c3ae7e
--- /dev/null
@@ -0,0 +1,101 @@
+/* strings.c - Iterator for a list of strings
+ *
+ * Copyright © 2010 Intel Corporation
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ *         Austin Clements <aclements@csail.mit.edu>
+ */
+
+#include "notmuch-private.h"
+
+/* Create a new notmuch_string_list_t object, with 'ctx' as its
+ * talloc owner.
+ *
+ * This function can return NULL in case of out-of-memory.
+ */
+notmuch_string_list_t *
+_notmuch_string_list_create (const void *ctx)
+{
+    notmuch_string_list_t *list;
+
+    list = talloc (ctx, notmuch_string_list_t);
+    if (unlikely (list == NULL))
+       return NULL;
+
+    list->length = 0;
+    list->head = NULL;
+    list->tail = &list->head;
+
+    return list;
+}
+
+int
+_notmuch_string_list_length (notmuch_string_list_t *list)
+{
+    return list->length;
+}
+
+void
+_notmuch_string_list_append (notmuch_string_list_t *list,
+                            const char *string)
+{
+    /* Create and initialize new node. */
+    notmuch_string_node_t *node = talloc (list, notmuch_string_node_t);
+
+    node->string = talloc_strdup (node, string);
+    node->next = NULL;
+
+    /* Append the node to the list. */
+    *(list->tail) = node;
+    list->tail = &node->next;
+    list->length++;
+}
+
+static int
+cmpnode (const void *pa, const void *pb)
+{
+    notmuch_string_node_t *a = *(notmuch_string_node_t * const *)pa;
+    notmuch_string_node_t *b = *(notmuch_string_node_t * const *)pb;
+
+    return strcmp (a->string, b->string);
+}
+
+void
+_notmuch_string_list_sort (notmuch_string_list_t *list)
+{
+    notmuch_string_node_t **nodes, *node;
+    int i;
+
+    if (list->length == 0)
+       return;
+
+    nodes = talloc_array (list, notmuch_string_node_t *, list->length);
+    if (unlikely (nodes == NULL))
+       INTERNAL_ERROR ("Could not allocate memory for list sort");
+
+    for (i = 0, node = list->head; node; i++, node = node->next)
+       nodes[i] = node;
+
+    qsort (nodes, list->length, sizeof (*nodes), cmpnode);
+
+    for (i = 0; i < list->length - 1; ++i)
+       nodes[i]->next = nodes[i+1];
+    nodes[i]->next = NULL;
+    list->head = nodes[0];
+    list->tail = &nodes[i]->next;
+
+    talloc_free (nodes);
+}
diff --git a/lib/string-map.c b/lib/string-map.c
new file mode 100644 (file)
index 0000000..ad81820
--- /dev/null
@@ -0,0 +1,228 @@
+/* string-map.c - associative arrays of strings
+ *
+ *
+ * Copyright © 2016 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch-private.h"
+
+/* Create a new notmuch_string_map_t object, with 'ctx' as its
+ * talloc owner.
+ *
+ * This function can return NULL in case of out-of-memory.
+ */
+
+typedef struct _notmuch_string_pair_t {
+    char *key;
+    char *value;
+} notmuch_string_pair_t;
+
+struct _notmuch_string_map {
+    bool sorted;
+    size_t length;
+    notmuch_string_pair_t *pairs;
+};
+
+struct _notmuch_string_map_iterator {
+    notmuch_string_pair_t *current;
+    bool exact;
+    const char *key;
+};
+
+notmuch_string_map_t *
+_notmuch_string_map_create (const void *ctx)
+{
+    notmuch_string_map_t *map;
+
+    map = talloc (ctx, notmuch_string_map_t);
+    if (unlikely (map == NULL))
+       return NULL;
+
+    map->length = 0;
+    map->pairs = NULL;
+    map->sorted = true;
+
+    return map;
+}
+
+void
+_notmuch_string_map_append (notmuch_string_map_t *map,
+                           const char *key,
+                           const char *value)
+{
+
+    map->length++;
+    map->sorted = false;
+
+    if (map->pairs)
+       map->pairs = talloc_realloc (map, map->pairs, notmuch_string_pair_t, map->length + 1);
+    else
+       map->pairs = talloc_array (map, notmuch_string_pair_t, map->length + 1);
+
+    map->pairs[map->length - 1].key = talloc_strdup (map, key);
+    map->pairs[map->length - 1].value = talloc_strdup (map, value);
+
+    /* Add sentinel */
+    map->pairs[map->length].key = NULL;
+    map->pairs[map->length].value = NULL;
+
+}
+
+static int
+cmppair (const void *pa, const void *pb)
+{
+    notmuch_string_pair_t *a = (notmuch_string_pair_t *) pa;
+    notmuch_string_pair_t *b = (notmuch_string_pair_t *) pb;
+
+    return strcmp (a->key, b->key);
+}
+
+static void
+_notmuch_string_map_sort (notmuch_string_map_t *map)
+{
+    if (map->length == 0)
+       return;
+
+    if (map->sorted)
+       return;
+
+    qsort (map->pairs, map->length, sizeof (notmuch_string_pair_t), cmppair);
+
+    map->sorted = true;
+}
+
+static bool
+string_cmp (const char *a, const char *b, bool exact)
+{
+    if (exact)
+       return (strcmp (a, b));
+    else
+       return (strncmp (a, b, strlen (a)));
+}
+
+static notmuch_string_pair_t *
+bsearch_first (notmuch_string_pair_t *array, size_t len, const char *key, bool exact)
+{
+    size_t first = 0;
+    size_t last = len - 1;
+    size_t mid;
+
+    if (len <= 0)
+       return NULL;
+
+    while (last > first) {
+       mid = (first + last) / 2;
+       int sign = string_cmp (key, array[mid].key, exact);
+
+       if (sign <= 0)
+           last = mid;
+       else
+           first = mid + 1;
+    }
+
+
+    if (string_cmp (key, array[first].key, exact) == 0)
+       return array + first;
+    else
+       return NULL;
+
+}
+
+const char *
+_notmuch_string_map_get (notmuch_string_map_t *map, const char *key)
+{
+    notmuch_string_pair_t *pair;
+
+    /* this means that calling append invalidates iterators */
+    _notmuch_string_map_sort (map);
+
+    pair = bsearch_first (map->pairs, map->length, key, true);
+    if (! pair)
+       return NULL;
+
+    return pair->value;
+}
+
+notmuch_string_map_iterator_t *
+_notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
+                                    bool exact)
+{
+    notmuch_string_map_iterator_t *iter;
+
+    _notmuch_string_map_sort (map);
+
+    iter = talloc (map, notmuch_string_map_iterator_t);
+    if (unlikely (iter == NULL))
+       return NULL;
+
+    if (unlikely (talloc_reference (iter, map) == NULL))
+       return NULL;
+
+    iter->key = talloc_strdup (iter, key);
+    iter->exact = exact;
+    iter->current = bsearch_first (map->pairs, map->length, key, exact);
+    return iter;
+}
+
+bool
+_notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iterator)
+{
+    if (iterator->current == NULL)
+       return false;
+
+    /* sentinel */
+    if (iterator->current->key == NULL)
+       return false;
+
+    return (0 == string_cmp (iterator->key, iterator->current->key, iterator->exact));
+
+}
+
+void
+_notmuch_string_map_iterator_move_to_next (notmuch_string_map_iterator_t *iterator)
+{
+
+    if (! _notmuch_string_map_iterator_valid (iterator))
+       return;
+
+    (iterator->current)++;
+}
+
+const char *
+_notmuch_string_map_iterator_key (notmuch_string_map_iterator_t *iterator)
+{
+    if (! _notmuch_string_map_iterator_valid (iterator))
+       return NULL;
+
+    return iterator->current->key;
+}
+
+const char *
+_notmuch_string_map_iterator_value (notmuch_string_map_iterator_t *iterator)
+{
+    if (! _notmuch_string_map_iterator_valid (iterator))
+       return NULL;
+
+    return iterator->current->value;
+}
+
+void
+_notmuch_string_map_iterator_destroy (notmuch_string_map_iterator_t *iterator)
+{
+    talloc_free (iterator);
+}
diff --git a/lib/tags.c b/lib/tags.c
new file mode 100644 (file)
index 0000000..c7d3f66
--- /dev/null
@@ -0,0 +1,76 @@
+/* tags.c - Iterator for tags returned from message or thread
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+struct _notmuch_tags {
+    notmuch_string_node_t *iterator;
+};
+
+/* Create a new notmuch_tags_t object, with 'ctx' as its talloc owner.
+ * The returned iterator will talloc_steal the 'list', since the list
+ * is almost always transient.
+ *
+ * This function can return NULL in case of out-of-memory.
+ */
+notmuch_tags_t *
+_notmuch_tags_create (const void *ctx, notmuch_string_list_t *list)
+{
+    notmuch_tags_t *tags;
+
+    tags = talloc (ctx, notmuch_tags_t);
+    if (unlikely (tags == NULL))
+       return NULL;
+
+    tags->iterator = list->head;
+    (void) talloc_steal (tags, list);
+
+    return tags;
+}
+
+notmuch_bool_t
+notmuch_tags_valid (notmuch_tags_t *tags)
+{
+    return tags->iterator != NULL;
+}
+
+const char *
+notmuch_tags_get (notmuch_tags_t *tags)
+{
+    if (tags->iterator == NULL)
+       return NULL;
+
+    return (char *) tags->iterator->string;
+}
+
+void
+notmuch_tags_move_to_next (notmuch_tags_t *tags)
+{
+    if (tags->iterator == NULL)
+       return;
+
+    tags->iterator = tags->iterator->next;
+}
+
+void
+notmuch_tags_destroy (notmuch_tags_t *tags)
+{
+    talloc_free (tags);
+}
diff --git a/lib/thread-fp.cc b/lib/thread-fp.cc
new file mode 100644 (file)
index 0000000..7327700
--- /dev/null
@@ -0,0 +1,67 @@
+/* thread-fp.cc - "thread:" field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2018 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "database-private.h"
+#include "thread-fp.h"
+#include <iostream>
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+
+Xapian::Query
+ThreadFieldProcessor::operator() (const std::string & str)
+{
+    notmuch_status_t status;
+    const char *thread_prefix = _find_prefix ("thread");
+
+    if (str.at (0) == '{') {
+       if (str.size () <= 1 || str.at (str.size () - 1) != '}') {
+           throw Xapian::QueryParserError ("missing } in '" + str + "'");
+       } else {
+           std::string subquery_str = str.substr (1, str.size () - 2);
+           notmuch_query_t *subquery = notmuch_query_create (notmuch, subquery_str.c_str ());
+           notmuch_messages_t *messages;
+           std::set<std::string> terms;
+
+           if (! subquery)
+               throw Xapian::QueryParserError ("failed to create subquery for '" + subquery_str + "'");
+
+           status = notmuch_query_search_messages (subquery, &messages);
+           if (status)
+               throw Xapian::QueryParserError ("failed to search messages for '" + subquery_str + "'");
+
+           for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) {
+               std::string term = thread_prefix;
+               notmuch_message_t *message;
+               message = notmuch_messages_get (messages);
+               term += _notmuch_message_get_thread_id_only (message);
+               terms.insert (term);
+           }
+           return Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
+       }
+    } else {
+       /* literal thread id */
+       std::string term = thread_prefix + str;
+       return Xapian::Query (term);
+    }
+
+}
+#endif
diff --git a/lib/thread-fp.h b/lib/thread-fp.h
new file mode 100644 (file)
index 0000000..47c066c
--- /dev/null
@@ -0,0 +1,42 @@
+/* thread-fp.h - thread field processor glue
+ *
+ * This file is part of notmuch.
+ *
+ * Copyright © 2018 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#ifndef NOTMUCH_THREAD_FP_H
+#define NOTMUCH_THREAD_FP_H
+
+#include <xapian.h>
+#include "notmuch.h"
+
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+class ThreadFieldProcessor : public Xapian::FieldProcessor {
+ protected:
+    Xapian::QueryParser &parser;
+    notmuch_database_t *notmuch;
+
+ public:
+    ThreadFieldProcessor (Xapian::QueryParser &parser_, notmuch_database_t *notmuch_)
+       : parser(parser_), notmuch(notmuch_) { };
+
+    Xapian::Query operator()(const std::string & str);
+};
+#endif
+#endif /* NOTMUCH_THREAD_FP_H */
diff --git a/lib/thread.cc b/lib/thread.cc
new file mode 100644 (file)
index 0000000..47c9066
--- /dev/null
@@ -0,0 +1,738 @@
+/* thread.cc - Results of thread-based searches from a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#include <gmime/gmime.h>
+#include <glib.h> /* GHashTable */
+
+#ifdef DEBUG_THREADING
+#define THREAD_DEBUG(format, ...) fprintf(stderr, format " (%s).\n", ##__VA_ARGS__, __location__)
+#else
+#define THREAD_DEBUG(format, ...) do {} while (0) /* ignored */
+#endif
+
+#define EMPTY_STRING(s) ((s)[0] == '\0')
+
+struct _notmuch_thread {
+    notmuch_database_t *notmuch;
+    char *thread_id;
+    char *subject;
+    GHashTable *authors_hash;
+    GPtrArray *authors_array;
+    GHashTable *matched_authors_hash;
+    GPtrArray *matched_authors_array;
+    char *authors;
+    GHashTable *tags;
+
+    /* All messages, oldest first. */
+    notmuch_message_list_t *message_list;
+    /* Top-level messages, oldest first. */
+    notmuch_message_list_t *toplevel_list;
+
+    GHashTable *message_hash;
+    int total_messages;
+    int total_files;
+    int matched_messages;
+    time_t oldest;
+    time_t newest;
+};
+
+static int
+_notmuch_thread_destructor (notmuch_thread_t *thread)
+{
+    g_hash_table_unref (thread->authors_hash);
+    g_hash_table_unref (thread->matched_authors_hash);
+    g_hash_table_unref (thread->tags);
+    g_hash_table_unref (thread->message_hash);
+
+    if (thread->authors_array) {
+       g_ptr_array_free (thread->authors_array, true);
+       thread->authors_array = NULL;
+    }
+
+    if (thread->matched_authors_array) {
+       g_ptr_array_free (thread->matched_authors_array, true);
+       thread->matched_authors_array = NULL;
+    }
+
+    return 0;
+}
+
+/* Add each author of the thread to the thread's authors_hash and to
+ * the thread's authors_array. */
+static void
+_thread_add_author (notmuch_thread_t *thread,
+                   const char *author)
+{
+    char *author_copy;
+
+    if (author == NULL)
+       return;
+
+    if (g_hash_table_lookup_extended (thread->authors_hash,
+                                     author, NULL, NULL))
+       return;
+
+    author_copy = talloc_strdup (thread, author);
+
+    g_hash_table_insert (thread->authors_hash, author_copy, NULL);
+
+    g_ptr_array_add (thread->authors_array, author_copy);
+}
+
+/* Add each matched author of the thread to the thread's
+ * matched_authors_hash and to the thread's matched_authors_array. */
+static void
+_thread_add_matched_author (notmuch_thread_t *thread,
+                           const char *author)
+{
+    char *author_copy;
+
+    if (author == NULL)
+       return;
+
+    if (g_hash_table_lookup_extended (thread->matched_authors_hash,
+                                     author, NULL, NULL))
+       return;
+
+    author_copy = talloc_strdup (thread, author);
+
+    g_hash_table_insert (thread->matched_authors_hash, author_copy, NULL);
+
+    g_ptr_array_add (thread->matched_authors_array, author_copy);
+}
+
+/* Construct an authors string from matched_authors_array and
+ * authors_array. The string contains matched authors first, then
+ * non-matched authors (with the two groups separated by '|'). Within
+ * each group, authors are listed in date order. */
+static void
+_resolve_thread_authors_string (notmuch_thread_t *thread)
+{
+    unsigned int i;
+    char *author;
+    int first_non_matched_author = 1;
+
+    /* First, list all matched authors in date order. */
+    for (i = 0; i < thread->matched_authors_array->len; i++) {
+       author = (char *) g_ptr_array_index (thread->matched_authors_array, i);
+       if (thread->authors)
+           thread->authors = talloc_asprintf (thread, "%s, %s",
+                                              thread->authors,
+                                              author);
+       else
+           thread->authors = author;
+    }
+
+    /* Next, append any non-matched authors that haven't already appeared. */
+    for (i = 0; i < thread->authors_array->len; i++) {
+       author = (char *) g_ptr_array_index (thread->authors_array, i);
+       if (g_hash_table_lookup_extended (thread->matched_authors_hash,
+                                         author, NULL, NULL))
+           continue;
+       if (first_non_matched_author) {
+           thread->authors = talloc_asprintf (thread, "%s| %s",
+                                              thread->authors,
+                                              author);
+       } else {
+           thread->authors = talloc_asprintf (thread, "%s, %s",
+                                              thread->authors,
+                                              author);
+       }
+
+       first_non_matched_author = 0;
+    }
+
+    g_ptr_array_free (thread->authors_array, true);
+    thread->authors_array = NULL;
+    g_ptr_array_free (thread->matched_authors_array, true);
+    thread->matched_authors_array = NULL;
+
+    if (!thread->authors)
+       thread->authors = talloc_strdup(thread, "");
+}
+
+/* clean up the ugly "Lastname, Firstname" format that some mail systems
+ * (most notably, Exchange) are creating to be "Firstname Lastname"
+ * To make sure that we don't change other potential situations where a
+ * comma is in the name, we check that we match one of these patterns
+ * "Last, First" <first.last@company.com>
+ * "Last, First MI" <first.mi.last@company.com>
+ */
+static char *
+_thread_cleanup_author (notmuch_thread_t *thread,
+                       const char *author, const char *from)
+{
+    char *clean_author,*test_author;
+    const char *comma;
+    char *blank;
+    int fname,lname;
+
+    if (author == NULL)
+       return NULL;
+    clean_author = talloc_strdup(thread, author);
+    if (clean_author == NULL)
+       return NULL;
+    /* check if there's a comma in the name and that there's a
+     * component of the name behind it (so the name doesn't end with
+     * the comma - in which case the string that strchr finds is just
+     * one character long ",\0").
+     * Otherwise just return the copy of the original author name that
+     * we just made*/
+    comma = strchr(author,',');
+    if (comma && strlen(comma) > 1) {
+       /* let's assemble what we think is the correct name */
+       lname = comma - author;
+
+       /* Skip all the spaces after the comma */
+       fname = strlen(author) - lname - 1;
+       comma += 1;
+       while (*comma == ' ') {
+           fname -= 1;
+           comma += 1;
+       }
+       strncpy(clean_author, comma, fname);
+
+       *(clean_author+fname) = ' ';
+       strncpy(clean_author + fname + 1, author, lname);
+       *(clean_author+fname+1+lname) = '\0';
+       /* make a temporary copy and see if it matches the email */
+       test_author = talloc_strdup(thread,clean_author);
+
+       blank=strchr(test_author,' ');
+       while (blank != NULL) {
+           *blank = '.';
+           blank=strchr(test_author,' ');
+       }
+       if (strcasestr(from, test_author) == NULL)
+           /* we didn't identify this as part of the email address
+           * so let's punt and return the original author */
+           strcpy (clean_author, author);
+    }
+    return clean_author;
+}
+
+/* Add 'message' as a message that belongs to 'thread'.
+ *
+ * The 'thread' will talloc_steal the 'message' and hold onto a
+ * reference to it.
+ */
+static void
+_thread_add_message (notmuch_thread_t *thread,
+                    notmuch_message_t *message,
+                    notmuch_string_list_t *exclude_terms,
+                    notmuch_exclude_t omit_exclude)
+{
+    notmuch_tags_t *tags;
+    const char *tag;
+    InternetAddressList *list = NULL;
+    InternetAddress *address;
+    const char *from, *author;
+    char *clean_author;
+    bool message_excluded = false;
+
+    if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) {
+       for (tags = notmuch_message_get_tags (message);
+            notmuch_tags_valid (tags);
+            notmuch_tags_move_to_next (tags))
+       {
+           tag = notmuch_tags_get (tags);
+           /* Is message excluded? */
+           for (notmuch_string_node_t *term = exclude_terms->head;
+                term != NULL;
+                term = term->next)
+           {
+               /* Check for an empty string, and then ignore initial 'K'. */
+               if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) {
+                   message_excluded = true;
+                   break;
+               }
+           }
+       }
+    }
+
+    if (message_excluded && omit_exclude == NOTMUCH_EXCLUDE_ALL)
+       return;
+
+    _notmuch_message_list_add_message (thread->message_list,
+                                      talloc_steal (thread, message));
+    thread->total_messages++;
+    thread->total_files += notmuch_message_count_files (message);
+
+    g_hash_table_insert (thread->message_hash,
+                        xstrdup (notmuch_message_get_message_id (message)),
+                        message);
+
+    from = notmuch_message_get_header (message, "from");
+    if (from)
+       list = internet_address_list_parse_string (from);
+
+    if (list) {
+       address = internet_address_list_get_address (list, 0);
+       if (address) {
+           author = internet_address_get_name (address);
+           /* We treat quoted empty names as if they were empty. */
+           if (author == NULL || author[0] == '\0') {
+               InternetAddressMailbox *mailbox;
+               mailbox = INTERNET_ADDRESS_MAILBOX (address);
+               author = internet_address_mailbox_get_addr (mailbox);
+           }
+           clean_author = _thread_cleanup_author (thread, author, from);
+           _thread_add_author (thread, clean_author);
+           _notmuch_message_set_author (message, clean_author);
+       }
+       g_object_unref (G_OBJECT (list));
+    }
+
+    if (! thread->subject) {
+       const char *subject;
+       subject = notmuch_message_get_header (message, "subject");
+       thread->subject = talloc_strdup (thread, subject ? subject : "");
+    }
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+    {
+       tag = notmuch_tags_get (tags);
+       g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
+    }
+
+    /* Mark excluded messages. */
+    if (message_excluded)
+       notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
+}
+
+static void
+_thread_set_subject_from_message (notmuch_thread_t *thread,
+                                 notmuch_message_t *message)
+{
+    const char *subject;
+    const char *cleaned_subject;
+
+    subject = notmuch_message_get_header (message, "subject");
+    if (! subject)
+       return;
+
+    if ((strncasecmp (subject, "Re: ", 4) == 0) ||
+       (strncasecmp (subject, "Aw: ", 4) == 0) ||
+       (strncasecmp (subject, "Vs: ", 4) == 0) ||
+       (strncasecmp (subject, "Sv: ", 4) == 0)) {
+
+       cleaned_subject = talloc_strndup (thread,
+                                         subject + 4,
+                                         strlen(subject) - 4);
+    } else {
+       cleaned_subject = talloc_strdup (thread, subject);
+    }
+
+    if (! EMPTY_STRING(cleaned_subject)) {
+       if (thread->subject)
+           talloc_free (thread->subject);
+
+       thread->subject = talloc_strdup (thread, cleaned_subject);
+    }
+}
+
+/* Add a message to this thread which is known to match the original
+ * search specification. The 'sort' parameter controls whether the
+ * oldest or newest matching subject is applied to the thread as a
+ * whole. */
+static void
+_thread_add_matched_message (notmuch_thread_t *thread,
+                            notmuch_message_t *message,
+                            notmuch_sort_t sort)
+{
+    time_t date;
+    notmuch_message_t *hashed_message;
+
+    date = notmuch_message_get_date (message);
+
+    if (date < thread->oldest || ! thread->matched_messages) {
+       thread->oldest = date;
+       if (sort == NOTMUCH_SORT_OLDEST_FIRST)
+           _thread_set_subject_from_message (thread, message);
+    }
+
+    if (date > thread->newest || ! thread->matched_messages) {
+       thread->newest = date;
+       const char *cur_subject = notmuch_thread_get_subject(thread);
+       if (sort != NOTMUCH_SORT_OLDEST_FIRST || EMPTY_STRING(cur_subject))
+           _thread_set_subject_from_message (thread, message);
+    }
+
+    if (!notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED))
+       thread->matched_messages++;
+
+    if (g_hash_table_lookup_extended (thread->message_hash,
+                           notmuch_message_get_message_id (message), NULL,
+                           (void **) &hashed_message)) {
+       notmuch_message_set_flag (hashed_message,
+                                 NOTMUCH_MESSAGE_FLAG_MATCH, 1);
+    }
+
+    _thread_add_matched_author (thread, _notmuch_message_get_author (hashed_message));
+}
+
+static bool
+_parent_via_in_reply_to (notmuch_thread_t *thread, notmuch_message_t *message) {
+    notmuch_message_t *parent;
+    const char *in_reply_to;
+
+    in_reply_to = _notmuch_message_get_in_reply_to (message);
+    THREAD_DEBUG("checking message = %s in_reply_to=%s\n",
+                notmuch_message_get_message_id (message), in_reply_to);
+
+    if (in_reply_to && (! EMPTY_STRING(in_reply_to)) &&
+       g_hash_table_lookup_extended (thread->message_hash,
+                                     in_reply_to, NULL,
+                                     (void **) &parent)) {
+       _notmuch_message_add_reply (parent, message);
+       return true;
+    } else {
+       return false;
+    }
+}
+
+static void
+_parent_or_toplevel (notmuch_thread_t *thread, notmuch_message_t *message)
+{
+    size_t max_depth = 0;
+    notmuch_message_t *new_parent;
+    notmuch_message_t *parent = NULL;
+    const notmuch_string_list_t *references =
+       _notmuch_message_get_references (message);
+
+    THREAD_DEBUG("trying to reparent via references: %s\n",
+                    notmuch_message_get_message_id (message));
+
+    for (notmuch_string_node_t *ref_node = references->head;
+        ref_node; ref_node = ref_node->next) {
+       THREAD_DEBUG("checking reference=%s\n", ref_node->string);
+       if ((g_hash_table_lookup_extended (thread->message_hash,
+                                          ref_node->string, NULL,
+                                          (void **) &new_parent))) {
+           size_t new_depth = _notmuch_message_get_thread_depth (new_parent);
+           THREAD_DEBUG("got depth %lu\n", new_depth);
+           if (new_depth > max_depth || !parent) {
+               THREAD_DEBUG("adding at depth %lu parent=%s\n", new_depth, ref_node->string);
+               max_depth = new_depth;
+               parent = new_parent;
+           }
+       }
+    }
+    if (parent) {
+       THREAD_DEBUG("adding reply %s to parent=%s\n",
+                notmuch_message_get_message_id (message),
+                notmuch_message_get_message_id (parent));
+       _notmuch_message_add_reply (parent, message);
+    } else {
+       THREAD_DEBUG("adding as toplevel %s\n",
+                notmuch_message_get_message_id (message));
+       _notmuch_message_list_add_message (thread->toplevel_list, message);
+    }
+}
+
+static void
+_resolve_thread_relationships (notmuch_thread_t *thread)
+{
+    notmuch_message_node_t *node, *first_node;
+    notmuch_message_t *message;
+    void *local;
+    notmuch_message_list_t *maybe_toplevel_list;
+
+    first_node = thread->message_list->head;
+    if (! first_node)
+       return;
+
+    local = talloc_new (thread);
+    maybe_toplevel_list = _notmuch_message_list_create (local);
+
+    for (node = first_node->next; node; node = node->next) {
+       message = node->message;
+       if (! _parent_via_in_reply_to (thread, message))
+           _notmuch_message_list_add_message (maybe_toplevel_list, message);
+    }
+
+    /*
+     * if we reach the end of the list without finding a top-level
+     * message, that means the thread is a cycle (or set of cycles)
+     * and any message can be considered top-level.  Choose the oldest
+     * message, which happens to be first in our list.
+     */
+    if (first_node) {
+       message = first_node->message;
+       THREAD_DEBUG("checking first message  %s\n",
+                    notmuch_message_get_message_id (message));
+
+        if (_notmuch_message_list_empty (maybe_toplevel_list) ||
+           ! _parent_via_in_reply_to (thread, message)) {
+
+           THREAD_DEBUG("adding first message as toplevel = %s\n",
+                        notmuch_message_get_message_id (message));
+           _notmuch_message_list_add_message (maybe_toplevel_list, message);
+       }
+    }
+
+    for (notmuch_messages_t *messages = _notmuch_messages_create (maybe_toplevel_list);
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       notmuch_message_t *message = notmuch_messages_get (messages);
+       _notmuch_message_label_depths (message, 0);
+    }
+
+    for (notmuch_messages_t *roots = _notmuch_messages_create (maybe_toplevel_list);
+        notmuch_messages_valid (roots);
+        notmuch_messages_move_to_next (roots)) {
+       notmuch_message_t *message = notmuch_messages_get (roots);
+       if (_notmuch_messages_has_next (roots) || ! _notmuch_message_list_empty (thread->toplevel_list))
+           _parent_or_toplevel (thread, message);
+       else
+           _notmuch_message_list_add_message (thread->toplevel_list, message);
+    }
+
+    /* XXX this could be made conditional on messages being inserted
+     * (out of order) in later passes
+     */
+    thread->toplevel_list = _notmuch_message_sort_subtrees (thread, thread->toplevel_list);
+
+    talloc_free (local);
+}
+
+/* Create a new notmuch_thread_t object by finding the thread
+ * containing the message with the given doc ID, treating any messages
+ * contained in match_set as "matched".  Remove all messages in the
+ * thread from match_set.
+ *
+ * Creating the thread will perform a database search to get all
+ * messages belonging to the thread and will get the first subject
+ * line, the total count of messages, and all authors in the thread.
+ * Each message in the thread is checked against match_set to allow
+ * for a separate count of matched messages, and to allow a viewer to
+ * display these messages differently.
+ *
+ * Here, 'ctx' is talloc context for the resulting thread object.
+ *
+ * This function returns NULL in the case of any error.
+ */
+notmuch_thread_t *
+_notmuch_thread_create (void *ctx,
+                       notmuch_database_t *notmuch,
+                       unsigned int seed_doc_id,
+                       notmuch_doc_id_set_t *match_set,
+                       notmuch_string_list_t *exclude_terms,
+                       notmuch_exclude_t omit_excluded,
+                       notmuch_sort_t sort)
+{
+    void *local = talloc_new (ctx);
+    notmuch_thread_t *thread = NULL;
+    notmuch_message_t *seed_message;
+    const char *thread_id;
+    char *thread_id_query_string;
+    notmuch_query_t *thread_id_query;
+
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    seed_message = _notmuch_message_create (local, notmuch, seed_doc_id, NULL);
+    if (! seed_message)
+       INTERNAL_ERROR ("Thread seed message %u does not exist", seed_doc_id);
+
+    thread_id = notmuch_message_get_thread_id (seed_message);
+    thread_id_query_string = talloc_asprintf (local, "thread:%s", thread_id);
+    if (unlikely (thread_id_query_string == NULL))
+       goto DONE;
+
+    thread_id_query = talloc_steal (
+       local, notmuch_query_create (notmuch, thread_id_query_string));
+    if (unlikely (thread_id_query == NULL))
+       goto DONE;
+
+    thread = talloc (local, notmuch_thread_t);
+    if (unlikely (thread == NULL))
+       goto DONE;
+
+    talloc_set_destructor (thread, _notmuch_thread_destructor);
+
+    thread->notmuch = notmuch;
+    thread->thread_id = talloc_strdup (thread, thread_id);
+    thread->subject = NULL;
+    thread->authors_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                 NULL, NULL);
+    thread->authors_array = g_ptr_array_new ();
+    thread->matched_authors_hash = g_hash_table_new_full (g_str_hash,
+                                                         g_str_equal,
+                                                         NULL, NULL);
+    thread->matched_authors_array = g_ptr_array_new ();
+    thread->authors = NULL;
+    thread->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                         free, NULL);
+
+    thread->message_list = _notmuch_message_list_create (thread);
+    thread->toplevel_list = _notmuch_message_list_create (thread);
+    if (unlikely (thread->message_list == NULL ||
+                 thread->toplevel_list == NULL)) {
+       thread = NULL;
+       goto DONE;
+    }
+
+    thread->message_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                 free, NULL);
+
+    thread->total_messages = 0;
+    thread->total_files = 0;
+    thread->matched_messages = 0;
+    thread->oldest = 0;
+    thread->newest = 0;
+
+    /* We use oldest-first order unconditionally here to obtain the
+     * proper author ordering for the thread. The 'sort' parameter
+     * passed to this function is used only to indicate whether the
+     * oldest or newest subject is desired. */
+    notmuch_query_set_sort (thread_id_query, NOTMUCH_SORT_OLDEST_FIRST);
+
+    status = notmuch_query_search_messages (thread_id_query, &messages);
+    if (status)
+       goto DONE;
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       unsigned int doc_id;
+
+       message = notmuch_messages_get (messages);
+       doc_id = _notmuch_message_get_doc_id (message);
+       if (doc_id == seed_doc_id)
+           message = seed_message;
+
+       _thread_add_message (thread, message, exclude_terms, omit_excluded);
+
+       if ( _notmuch_doc_id_set_contains (match_set, doc_id)) {
+           _notmuch_doc_id_set_remove (match_set, doc_id);
+           _thread_add_matched_message (thread, message, sort);
+       }
+
+       _notmuch_message_close (message);
+    }
+
+    _resolve_thread_authors_string (thread);
+
+    _resolve_thread_relationships (thread);
+
+    /* Commit to returning thread. */
+    (void) talloc_steal (ctx, thread);
+
+  DONE:
+    talloc_free (local);
+    return thread;
+}
+
+notmuch_messages_t *
+notmuch_thread_get_toplevel_messages (notmuch_thread_t *thread)
+{
+    return _notmuch_messages_create (thread->toplevel_list);
+}
+
+notmuch_messages_t *
+notmuch_thread_get_messages (notmuch_thread_t *thread)
+{
+    return _notmuch_messages_create (thread->message_list);
+}
+
+const char *
+notmuch_thread_get_thread_id (notmuch_thread_t *thread)
+{
+    return thread->thread_id;
+}
+
+int
+notmuch_thread_get_total_messages (notmuch_thread_t *thread)
+{
+    return thread->total_messages;
+}
+
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread)
+{
+    return thread->total_files;
+}
+
+int
+notmuch_thread_get_matched_messages (notmuch_thread_t *thread)
+{
+    return thread->matched_messages;
+}
+
+const char *
+notmuch_thread_get_authors (notmuch_thread_t *thread)
+{
+    return thread->authors;
+}
+
+const char *
+notmuch_thread_get_subject (notmuch_thread_t *thread)
+{
+    return thread->subject;
+}
+
+time_t
+notmuch_thread_get_oldest_date (notmuch_thread_t *thread)
+{
+    return thread->oldest;
+}
+
+time_t
+notmuch_thread_get_newest_date (notmuch_thread_t *thread)
+{
+    return thread->newest;
+}
+
+notmuch_tags_t *
+notmuch_thread_get_tags (notmuch_thread_t *thread)
+{
+    notmuch_string_list_t *tags;
+    GList *keys, *l;
+
+    tags = _notmuch_string_list_create (thread);
+    if (unlikely (tags == NULL))
+       return NULL;
+
+    keys = g_hash_table_get_keys (thread->tags);
+
+    for (l = keys; l; l = l->next)
+       _notmuch_string_list_append (tags, (char *) l->data);
+
+    g_list_free (keys);
+
+    _notmuch_string_list_sort (tags);
+
+    return _notmuch_tags_create (thread, tags);
+}
+
+void
+notmuch_thread_destroy (notmuch_thread_t *thread)
+{
+    talloc_free (thread);
+}
diff --git a/libnotmuch-dev.install b/libnotmuch-dev.install
deleted file mode 100644 (file)
index 96bbd63..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-usr/include
-usr/lib/*/libnotmuch.so
diff --git a/libnotmuch5.install b/libnotmuch5.install
deleted file mode 100644 (file)
index a513b47..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/*/libnotmuch.so.*
diff --git a/libnotmuch5.symbols b/libnotmuch5.symbols
deleted file mode 100644 (file)
index 308567b..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-libnotmuch.so.5 libnotmuch5 #MINVER#
- notmuch_built_with@Base 0.23~rc0
- notmuch_config_list_destroy@Base 0.23~rc0
- notmuch_config_list_key@Base 0.23~rc0
- notmuch_config_list_move_to_next@Base 0.23~rc0
- notmuch_config_list_valid@Base 0.23~rc0
- notmuch_config_list_value@Base 0.23~rc0
- notmuch_database_add_message@Base 0.3
- notmuch_database_begin_atomic@Base 0.9~rc1
- notmuch_database_close@Base 0.13~rc1
- notmuch_database_compact@Base 0.17~rc1
- notmuch_database_create@Base 0.3
- notmuch_database_create_verbose@Base 0.20~rc1
- notmuch_database_destroy@Base 0.13~rc1
- notmuch_database_end_atomic@Base 0.9~rc1
- notmuch_database_find_message@Base 0.9~rc2
- notmuch_database_find_message_by_filename@Base 0.9~rc2
- notmuch_database_get_all_tags@Base 0.3
- notmuch_database_get_config@Base 0.23~rc0
- notmuch_database_get_config_list@Base 0.23~rc0
- notmuch_database_get_default_indexopts@Base 0.26~rc0
- notmuch_database_get_directory@Base 0.3
- notmuch_database_get_path@Base 0.3
- notmuch_database_get_revision@Base 0.21~rc1
- notmuch_database_get_version@Base 0.3
- notmuch_database_index_file@Base 0.26~rc0
- notmuch_database_needs_upgrade@Base 0.3
- notmuch_database_open@Base 0.3
- notmuch_database_open_verbose@Base 0.20~rc1
- notmuch_database_remove_message@Base 0.3
- notmuch_database_set_config@Base 0.23~rc0
- notmuch_database_status_string@Base 0.20~rc1
- notmuch_database_upgrade@Base 0.3
- notmuch_directory_delete@Base 0.21~rc1
- notmuch_directory_destroy@Base 0.3
- notmuch_directory_get_child_directories@Base 0.3
- notmuch_directory_get_child_files@Base 0.3
- notmuch_directory_get_mtime@Base 0.3
- notmuch_directory_set_mtime@Base 0.3
- notmuch_filenames_destroy@Base 0.3
- notmuch_filenames_get@Base 0.3
- notmuch_filenames_move_to_next@Base 0.3
- notmuch_filenames_valid@Base 0.3
- notmuch_indexopts_destroy@Base 0.26~rc0
- notmuch_indexopts_get_decrypt_policy@Base 0.26~rc0
- notmuch_indexopts_set_decrypt_policy@Base 0.26~rc0
- notmuch_message_add_property@Base 0.23~rc0
- notmuch_message_add_tag@Base 0.3
- notmuch_message_count_files@Base 0.26~rc0
- notmuch_message_count_properties@Base 0.27~rc0
- notmuch_message_destroy@Base 0.3
- notmuch_message_freeze@Base 0.3
- notmuch_message_get_database@Base 0.27~rc0
- notmuch_message_get_date@Base 0.3
- notmuch_message_get_filename@Base 0.3
- notmuch_message_get_filenames@Base 0.5
- notmuch_message_get_flag@Base 0.3
- notmuch_message_get_header@Base 0.3
- notmuch_message_get_message_id@Base 0.3
- notmuch_message_get_properties@Base 0.23~rc0
- notmuch_message_get_property@Base 0.23~rc0
- notmuch_message_get_replies@Base 0.3
- notmuch_message_get_tags@Base 0.3
- notmuch_message_get_thread_id@Base 0.3
- notmuch_message_has_maildir_flag@Base 0.26~rc0
- notmuch_message_maildir_flags_to_tags@Base 0.5
- notmuch_message_properties_destroy@Base 0.23~rc0
- notmuch_message_properties_key@Base 0.23~rc0
- notmuch_message_properties_move_to_next@Base 0.23~rc0
- notmuch_message_properties_valid@Base 0.23~rc0
- notmuch_message_properties_value@Base 0.23~rc0
- notmuch_message_reindex@Base 0.26~rc0
- notmuch_message_remove_all_properties@Base 0.23~rc0
- notmuch_message_remove_all_properties_with_prefix@Base 0.26~rc0
- notmuch_message_remove_all_tags@Base 0.3
- notmuch_message_remove_property@Base 0.23~rc0
- notmuch_message_remove_tag@Base 0.3
- notmuch_message_set_flag@Base 0.3
- notmuch_message_tags_to_maildir_flags@Base 0.5
- notmuch_message_thaw@Base 0.3
- notmuch_messages_collect_tags@Base 0.3
- notmuch_messages_destroy@Base 0.3
- notmuch_messages_get@Base 0.3
- notmuch_messages_move_to_next@Base 0.3
- notmuch_messages_valid@Base 0.3
- notmuch_query_add_tag_exclude@Base 0.12~rc1
- notmuch_query_count_messages@Base 0.3
- notmuch_query_count_messages_st@Base 0.21~rc1
- notmuch_query_count_threads@Base 0.10~rc1
- notmuch_query_count_threads_st@Base 0.21~rc1
- notmuch_query_create@Base 0.3
- notmuch_query_destroy@Base 0.3
- notmuch_query_get_database@Base 0.21~rc1
- notmuch_query_get_query_string@Base 0.4
- notmuch_query_get_sort@Base 0.4
- notmuch_query_search_messages@Base 0.3
- notmuch_query_search_messages_st@Base 0.20~rc1
- notmuch_query_search_threads@Base 0.3
- notmuch_query_search_threads_st@Base 0.20~rc1
- notmuch_query_set_omit_excluded@Base 0.13~rc1
- notmuch_query_set_sort@Base 0.3
- notmuch_status_to_string@Base 0.3
- notmuch_tags_destroy@Base 0.3
- notmuch_tags_get@Base 0.3
- notmuch_tags_move_to_next@Base 0.3
- notmuch_tags_valid@Base 0.3
- notmuch_thread_destroy@Base 0.3
- notmuch_thread_get_authors@Base 0.3
- notmuch_thread_get_matched_messages@Base 0.3
- notmuch_thread_get_messages@Base 0.16
- notmuch_thread_get_newest_date@Base 0.3
- notmuch_thread_get_oldest_date@Base 0.3
- notmuch_thread_get_subject@Base 0.3
- notmuch_thread_get_tags@Base 0.3
- notmuch_thread_get_thread_id@Base 0.3
- notmuch_thread_get_toplevel_messages@Base 0.3
- notmuch_thread_get_total_files@Base 0.26~rc0
- notmuch_thread_get_total_messages@Base 0.3
- notmuch_threads_destroy@Base 0.3
- notmuch_threads_get@Base 0.3
- notmuch_threads_move_to_next@Base 0.3
- notmuch_threads_valid@Base 0.3
- (c++)"typeinfo for Xapian::LogicError@Base" 0.6.1
- (c++)"typeinfo for Xapian::RuntimeError@Base" 0.6.1
- (c++)"typeinfo for Xapian::DocNotFoundError@Base" 0.6.1
- (c++)"typeinfo for Xapian::InvalidArgumentError@Base" 0.6.1
- (c++)"typeinfo for Xapian::Error@Base" 0.6.1
- (c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
- (c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
- (c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
- (c++)"typeinfo for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
- (c++)"typeinfo name for Xapian::QueryParser::add_valuerangeprocessor(Xapian::ValueRangeProcessor*)::ShimRangeProcessor@Base" 0.25~rc0
- (c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::DocNotFoundError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::InvalidArgumentError@Base" 0.6.1
- (c++)"typeinfo name for Xapian::Error@Base" 0.6.1
- (c++)"typeinfo name for Xapian::DatabaseError@Base" 0.24~rc0
- (c++)"typeinfo name for Xapian::DatabaseModifiedError@Base" 0.24~rc0
- (c++|optional=present with Xapian 1.4)"typeinfo name for Xapian::QueryParserError@Base" 0.23~rc0
diff --git a/mime-node.c b/mime-node.c
new file mode 100644 (file)
index 0000000..2a24e53
--- /dev/null
@@ -0,0 +1,401 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * 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: Carl Worth <cworth@cworth.org>
+ *          Keith Packard <keithp@keithp.com>
+ *          Austin Clements <aclements@csail.mit.edu>
+ */
+
+#include "notmuch-client.h"
+
+/* Context that gets inherited from the root node. */
+typedef struct mime_node_context {
+    /* Per-message resources.  These are allocated internally and must
+     * be destroyed. */
+    FILE *file;
+    GMimeStream *stream;
+    GMimeParser *parser;
+    GMimeMessage *mime_message;
+
+    /* Context provided by the caller. */
+    _notmuch_crypto_t *crypto;
+} mime_node_context_t;
+
+static int
+_mime_node_context_free (mime_node_context_t *res)
+{
+    if (res->mime_message)
+       g_object_unref (res->mime_message);
+
+    if (res->parser)
+       g_object_unref (res->parser);
+
+    if (res->stream)
+       g_object_unref (res->stream);
+
+    if (res->file)
+       fclose (res->file);
+
+    return 0;
+}
+
+notmuch_status_t
+mime_node_open (const void *ctx, notmuch_message_t *message,
+               _notmuch_crypto_t *crypto, mime_node_t **root_out)
+{
+    const char *filename = notmuch_message_get_filename (message);
+    mime_node_context_t *mctx;
+    mime_node_t *root;
+    notmuch_status_t status;
+
+    root = talloc_zero (ctx, mime_node_t);
+    if (root == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    /* Create the tree-wide context */
+    mctx = talloc_zero (root, mime_node_context_t);
+    if (mctx == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+    talloc_set_destructor (mctx, _mime_node_context_free);
+
+    /* Fast path */
+    mctx->file = fopen (filename, "r");
+    if (! mctx->file) {
+       /* Slow path - for some reason the first file in the list is
+        * not available anymore. This is clearly a problem in the
+        * database, but we are not going to let this problem be a
+        * show stopper */
+       notmuch_filenames_t *filenames;
+       for (filenames = notmuch_message_get_filenames (message);
+            notmuch_filenames_valid (filenames);
+            notmuch_filenames_move_to_next (filenames))
+       {
+           filename = notmuch_filenames_get (filenames);
+           mctx->file = fopen (filename, "r");
+           if (mctx->file)
+               break;
+       }
+
+       talloc_free (filenames);
+       if (! mctx->file) {
+           /* Give up */
+           fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+               status = NOTMUCH_STATUS_FILE_ERROR;
+               goto DONE;
+           }
+       }
+
+    mctx->stream = g_mime_stream_file_new (mctx->file);
+    if (!mctx->stream) {
+       fprintf (stderr, "Out of memory.\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), false);
+
+    mctx->parser = g_mime_parser_new_with_stream (mctx->stream);
+    if (!mctx->parser) {
+       fprintf (stderr, "Out of memory.\n");
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    mctx->mime_message = g_mime_parser_construct_message (mctx->parser);
+    if (!mctx->mime_message) {
+       fprintf (stderr, "Failed to parse %s\n", filename);
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    mctx->crypto = crypto;
+
+    /* Create the root node */
+    root->part = GMIME_OBJECT (mctx->mime_message);
+    root->envelope_file = message;
+    root->nchildren = 1;
+    root->ctx = mctx;
+
+    root->parent = NULL;
+    root->part_num = 0;
+    root->next_child = 0;
+    root->next_part_num = 1;
+
+    *root_out = root;
+    return NOTMUCH_STATUS_SUCCESS;
+
+DONE:
+    talloc_free (root);
+    return status;
+}
+
+/* Signature list destructor (GMime 2.6) */
+static int
+_signature_list_free (GMimeSignatureList **proxy)
+{
+    g_object_unref (*proxy);
+    return 0;
+}
+
+/* Set up signature list destructor (GMime 2.6) */
+static void
+set_signature_list_destructor (mime_node_t *node)
+{
+    GMimeSignatureList **proxy = talloc (node, GMimeSignatureList *);
+    if (proxy) {
+       *proxy = node->sig_list;
+       talloc_set_destructor (proxy, _signature_list_free);
+    }
+}
+
+/* Verify a signed mime node (GMime 2.6) */
+static void
+node_verify (mime_node_t *node, GMimeObject *part,
+            g_mime_3_unused(GMimeCryptoContext *cryptoctx))
+{
+    GError *err = NULL;
+
+    node->verify_attempted = true;
+    node->sig_list = g_mime_multipart_signed_verify
+       (GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
+
+    if (node->sig_list)
+       set_signature_list_destructor (node);
+    else
+       fprintf (stderr, "Failed to verify signed part: %s\n",
+                err ? err->message : "no error explanation given");
+
+    if (err)
+       g_error_free (err);
+}
+
+/* Decrypt and optionally verify an encrypted mime node (GMime 2.6) */
+static void
+node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
+                        g_mime_3_unused(GMimeCryptoContext *cryptoctx))
+{
+    GError *err = NULL;
+    GMimeDecryptResult *decrypt_result = NULL;
+    GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
+    notmuch_message_t *message = NULL;
+
+    if (! node->decrypted_child) {
+       for (mime_node_t *parent = node; parent; parent = parent->parent)
+           if (parent->envelope_file) {
+               message = parent->envelope_file;
+               break;
+           }
+
+       node->decrypted_child = _notmuch_crypto_decrypt (&node->decrypt_attempted,
+                                                        node->ctx->crypto->decrypt,
+                                                        message,
+                                                        cryptoctx, encrypteddata, &decrypt_result, &err);
+    }
+    if (! node->decrypted_child) {
+       fprintf (stderr, "Failed to decrypt part: %s\n",
+                err ? err->message : "no error explanation given");
+       goto DONE;
+    }
+
+    node->decrypt_success = true;
+    node->verify_attempted = true;
+
+    if (decrypt_result) {
+       /* This may be NULL if the part is not signed. */
+       node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
+       if (node->sig_list) {
+           g_object_ref (node->sig_list);
+           set_signature_list_destructor (node);
+       }
+
+#if HAVE_GMIME_SESSION_KEYS
+       if (node->ctx->crypto->decrypt == NOTMUCH_DECRYPT_TRUE && message) {
+           notmuch_database_t *db = notmuch_message_get_database (message);
+           const char *session_key = g_mime_decrypt_result_get_session_key (decrypt_result);
+           if (db && session_key)
+               print_status_message ("Failed to stash session key in the database",
+                                     message,
+                                     notmuch_message_add_property (message, "session-key",
+                                                                   session_key));
+       }
+#endif
+       g_object_unref (decrypt_result);
+    }
+
+ DONE:
+    if (err)
+       g_error_free (err);
+}
+
+static mime_node_t *
+_mime_node_create (mime_node_t *parent, GMimeObject *part)
+{
+    mime_node_t *node = talloc_zero (parent, mime_node_t);
+    GMimeCryptoContext *cryptoctx = NULL;
+
+    /* Set basic node properties */
+    node->part = part;
+    node->ctx = parent->ctx;
+    if (!talloc_reference (node, node->ctx)) {
+       fprintf (stderr, "Out of memory.\n");
+       talloc_free (node);
+       return NULL;
+    }
+    node->parent = parent;
+    node->part_num = node->next_part_num = -1;
+    node->next_child = 0;
+
+    /* Deal with the different types of parts */
+    if (GMIME_IS_PART (part)) {
+       node->nchildren = 0;
+    } else if (GMIME_IS_MULTIPART (part)) {
+       node->nchildren = g_mime_multipart_get_count (GMIME_MULTIPART (part));
+    } else if (GMIME_IS_MESSAGE_PART (part)) {
+       /* Promote part to an envelope and open it */
+       GMimeMessagePart *message_part = GMIME_MESSAGE_PART (part);
+       GMimeMessage *message = g_mime_message_part_get_message (message_part);
+       node->envelope_part = message_part;
+       node->part = GMIME_OBJECT (message);
+       node->nchildren = 1;
+    } else {
+       fprintf (stderr, "Warning: Unknown mime part type: %s.\n",
+                g_type_name (G_OBJECT_TYPE (part)));
+       talloc_free (node);
+       return NULL;
+    }
+
+#if (GMIME_MAJOR_VERSION < 3)
+    if ((GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE))
+       || (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) {
+       GMimeContentType *content_type = g_mime_object_get_content_type (part);
+       const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol");
+       notmuch_status_t status;
+       status = _notmuch_crypto_get_gmime_ctx_for_protocol (node->ctx->crypto,
+                                                            protocol, &cryptoctx);
+       if (status) /* this is a warning, not an error */
+           fprintf (stderr, "Warning: %s (%s).\n", notmuch_status_to_string (status),
+                    protocol ? protocol : "NULL");
+       if (!cryptoctx)
+           return node;
+    }
+#endif
+
+    /* Handle PGP/MIME parts */
+    if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
+       if (node->nchildren != 2) {
+           /* this violates RFC 3156 section 4, so we won't bother with it. */
+           fprintf (stderr, "Error: %d part(s) for a multipart/encrypted "
+                    "message (must be exactly 2)\n",
+                    node->nchildren);
+       } else {
+           node_decrypt_and_verify (node, part, cryptoctx);
+       }
+    } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify) {
+       if (node->nchildren != 2) {
+           /* this violates RFC 3156 section 5, so we won't bother with it. */
+           fprintf (stderr, "Error: %d part(s) for a multipart/signed message "
+                    "(must be exactly 2)\n",
+                    node->nchildren);
+       } else {
+           node_verify (node, part, cryptoctx);
+       }
+    }
+
+    return node;
+}
+
+mime_node_t *
+mime_node_child (mime_node_t *parent, int child)
+{
+    GMimeObject *sub;
+    mime_node_t *node;
+
+    if (!parent || !parent->part || child < 0 || child >= parent->nchildren)
+       return NULL;
+
+    if (GMIME_IS_MULTIPART (parent->part)) {
+       if (child == 1 && parent->decrypted_child)
+           sub = parent->decrypted_child;
+       else
+           sub = g_mime_multipart_get_part
+               (GMIME_MULTIPART (parent->part), child);
+    } else if (GMIME_IS_MESSAGE (parent->part)) {
+       sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part));
+    } else {
+       /* This should have been caught by message_part_create */
+       INTERNAL_ERROR ("Unexpected GMimeObject type: %s",
+                       g_type_name (G_OBJECT_TYPE (parent->part)));
+    }
+    node = _mime_node_create (parent, sub);
+
+    if (child == parent->next_child && parent->next_part_num != -1) {
+       /* We're traversing in depth-first order.  Record the child's
+        * depth-first numbering. */
+       node->part_num = parent->next_part_num;
+       node->next_part_num = node->part_num + 1;
+
+       /* Prepare the parent for its next depth-first child. */
+       parent->next_child++;
+       parent->next_part_num = -1;
+
+       if (node->nchildren == 0) {
+           /* We've reached a leaf, so find the parent that has more
+            * children and set it up to number its next child. */
+           mime_node_t *iter = node->parent;
+           while (iter && iter->next_child == iter->nchildren)
+               iter = iter->parent;
+           if (iter)
+               iter->next_part_num = node->part_num + 1;
+       }
+    }
+
+    return node;
+}
+
+static mime_node_t *
+_mime_node_seek_dfs_walk (mime_node_t *node, int *n)
+{
+    int i;
+
+    if (*n == 0)
+       return node;
+
+    *n -= 1;
+    for (i = 0; i < node->nchildren; i++) {
+       mime_node_t *child = mime_node_child (node, i);
+       mime_node_t *ret = _mime_node_seek_dfs_walk (child, n);
+       if (ret)
+           return ret;
+
+       talloc_free (child);
+    }
+    return NULL;
+}
+
+mime_node_t *
+mime_node_seek_dfs (mime_node_t *node, int n)
+{
+    if (n < 0)
+       return NULL;
+    return _mime_node_seek_dfs_walk (node, &n);
+}
diff --git a/notmuch-client.h b/notmuch-client.h
new file mode 100644 (file)
index 0000000..6c84ecc
--- /dev/null
@@ -0,0 +1,520 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_CLIENT_H
+#define NOTMUCH_CLIENT_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* for getline */
+#endif
+#include <stdbool.h>
+#include <stdio.h>
+#include <sysexits.h>
+
+#include "compat.h"
+
+#include "gmime-extra.h"
+
+#include "notmuch.h"
+
+/* This is separate from notmuch-private.h because we're trying to
+ * keep notmuch.c from looking into any internals, (which helps us
+ * develop notmuch.h into a plausible library interface).
+ */
+#include "xutil.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include "talloc-extra.h"
+#include "crypto.h"
+
+#define unused(x) x __attribute__ ((unused))
+
+#define STRINGIFY(s) STRINGIFY_(s)
+#define STRINGIFY_(s) #s
+
+typedef struct mime_node mime_node_t;
+struct sprinter;
+struct notmuch_show_params;
+
+typedef struct notmuch_show_format {
+    struct sprinter *(*new_sprinter) (const void *ctx, FILE *stream);
+    notmuch_status_t (*part) (const void *ctx, struct sprinter *sprinter,
+                             struct mime_node *node, int indent,
+                             const struct notmuch_show_params *params);
+} notmuch_show_format_t;
+
+typedef struct notmuch_show_params {
+    bool entire_thread;
+    bool omit_excluded;
+    bool output_body;
+    int part;
+    _notmuch_crypto_t crypto;
+    bool include_html;
+    GMimeStream *out_stream;
+} notmuch_show_params_t;
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that __location__ comes from talloc.h.
+ */
+#define INTERNAL_ERROR(format, ...)                    \
+    do {                                               \
+       fprintf(stderr,                                 \
+               "Internal error: " format " (%s)\n",    \
+               ##__VA_ARGS__, __location__);           \
+       exit (1);                                       \
+    } while (0)
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+#define STRNCMP_LITERAL(var, literal) \
+    strncmp ((var), (literal), sizeof (literal) - 1)
+
+static inline void
+chomp_newline (char *str)
+{
+    if (str && str[strlen(str)-1] == '\n')
+       str[strlen(str)-1] = '\0';
+}
+
+/* Exit status code indicating temporary failure; user is invited to
+ * retry.
+ *
+ * For example, file(s) in the mail store were removed or renamed
+ * after notmuch new scanned the directories but before indexing the
+ * file(s). If the file was renamed, the indexing might not be
+ * complete, and the user is advised to re-run notmuch new.
+ */
+#define NOTMUCH_EXIT_TEMPFAIL EX_TEMPFAIL
+
+/* Exit status code indicating the requested format version is too old
+ * (support for that version has been dropped).  CLI code should use
+ * notmuch_exit_if_unsupported_format rather than directly exiting
+ * with this code.
+ */
+#define NOTMUCH_EXIT_FORMAT_TOO_OLD 20
+/* Exit status code indicating the requested format version is newer
+ * than the version supported by the CLI.  CLI code should use
+ * notmuch_exit_if_unsupported_format rather than directly exiting
+ * with this code.
+ */
+#define NOTMUCH_EXIT_FORMAT_TOO_NEW 21
+
+/* The current structured output format version.  Requests for format
+ * versions above this will return an error.  Backwards-incompatible
+ * changes such as removing map fields, changing the meaning of map
+ * fields, or changing the meanings of list elements should increase
+ * this.  New (required) map fields can be added without increasing
+ * this.
+ */
+#define NOTMUCH_FORMAT_CUR 4
+/* The minimum supported structured output format version.  Requests
+ * for format versions below this will return an error. */
+#define NOTMUCH_FORMAT_MIN 1
+/* The minimum non-deprecated structured output format version.
+ * Requests for format versions below this will print a stern warning.
+ * Must be between NOTMUCH_FORMAT_MIN and NOTMUCH_FORMAT_CUR,
+ * inclusive.
+ */
+#define NOTMUCH_FORMAT_MIN_ACTIVE 1
+
+/* The output format version requested by the caller on the command
+ * line.  If no format version is requested, this will be set to
+ * NOTMUCH_FORMAT_CUR.  Even though the command-line option is
+ * per-command, this is global because commands can share structured
+ * output code.
+ */
+extern int notmuch_format_version;
+
+typedef struct _notmuch_config notmuch_config_t;
+
+/* Commands that support structured output should support the
+ * following argument
+ *  { NOTMUCH_OPT_INT, &notmuch_format_version, "format-version", 0, 0 }
+ * and should invoke notmuch_exit_if_unsupported_format to check the
+ * requested version.  If notmuch_format_version is outside the
+ * supported range, this will print a detailed diagnostic message for
+ * the user and exit with NOTMUCH_EXIT_FORMAT_TOO_{OLD,NEW} to inform
+ * the invoking program of the problem.
+ */
+void
+notmuch_exit_if_unsupported_format (void);
+
+int
+notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_address_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_setup_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_config_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
+notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[]);
+
+const char *
+notmuch_time_relative_date (const void *ctx, time_t then);
+
+void
+notmuch_time_print_formatted_seconds (double seconds);
+
+double
+notmuch_time_elapsed (struct timeval start, struct timeval end);
+
+char *
+query_string_from_args (void *ctx, int argc, char *argv[]);
+
+notmuch_status_t
+show_one_part (const char *filename, int part);
+
+void
+format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
+                     bool output_body,
+                     bool include_html);
+
+void
+format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
+                        bool reply);
+
+typedef enum {
+    NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
+} notmuch_show_text_part_flags;
+
+void
+show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
+                       notmuch_show_text_part_flags flags);
+
+char *
+json_quote_chararray (const void *ctx, const char *str, const size_t len);
+
+char *
+json_quote_str (const void *ctx, const char *str);
+
+/* notmuch-config.c */
+
+typedef enum {
+    NOTMUCH_CONFIG_OPEN        = 1 << 0,
+    NOTMUCH_CONFIG_CREATE = 1 << 1,
+} notmuch_config_mode_t;
+
+notmuch_config_t *
+notmuch_config_open (void *ctx,
+                    const char *filename,
+                    notmuch_config_mode_t config_mode);
+
+void
+notmuch_config_close (notmuch_config_t *config);
+
+int
+notmuch_config_save (notmuch_config_t *config);
+
+bool
+notmuch_config_is_new (notmuch_config_t *config);
+
+const char *
+notmuch_config_get_database_path (notmuch_config_t *config);
+
+void
+notmuch_config_set_database_path (notmuch_config_t *config,
+                                 const char *database_path);
+
+#if (GMIME_MAJOR_VERSION < 3)
+const char *
+notmuch_config_get_crypto_gpg_path (notmuch_config_t *config);
+
+void
+notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
+                                 const char *gpg_path);
+#endif
+
+const char *
+notmuch_config_get_user_name (notmuch_config_t *config);
+
+void
+notmuch_config_set_user_name (notmuch_config_t *config,
+                             const char *user_name);
+
+const char *
+notmuch_config_get_user_primary_email (notmuch_config_t *config);
+
+void
+notmuch_config_set_user_primary_email (notmuch_config_t *config,
+                                      const char *primary_email);
+
+const char **
+notmuch_config_get_user_other_email (notmuch_config_t *config,
+                                    size_t *length);
+
+void
+notmuch_config_set_user_other_email (notmuch_config_t *config,
+                                    const char *other_email[],
+                                    size_t length);
+
+const char **
+notmuch_config_get_new_tags (notmuch_config_t *config,
+                            size_t *length);
+void
+notmuch_config_set_new_tags (notmuch_config_t *config,
+                            const char *new_tags[],
+                            size_t length);
+
+const char **
+notmuch_config_get_new_ignore (notmuch_config_t *config,
+                              size_t *length);
+
+void
+notmuch_config_set_new_ignore (notmuch_config_t *config,
+                              const char *new_ignore[],
+                              size_t length);
+
+bool
+notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config);
+
+void
+notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
+                                             bool synchronize_flags);
+
+const char **
+notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length);
+
+void
+notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
+                                     const char *list[],
+                                     size_t length);
+
+int
+notmuch_run_hook (const char *db_path, const char *hook);
+
+bool
+debugger_is_active (void);
+
+/* mime-node.c */
+
+/* mime_node_t represents a single node in a MIME tree.  A MIME tree
+ * abstracts the different ways of traversing different types of MIME
+ * parts, allowing a MIME message to be viewed as a generic tree of
+ * parts.  Message-type parts have one child, multipart-type parts
+ * have multiple children, and leaf parts have zero children.
+ */
+struct mime_node {
+    /* The MIME object of this part.  This will be a GMimeMessage,
+     * GMimePart, GMimeMultipart, or a subclass of one of these.
+     *
+     * This will never be a GMimeMessagePart because GMimeMessagePart
+     * is structurally redundant with GMimeMessage.  If this part is a
+     * message (that is, 'part' is a GMimeMessage), then either
+     * envelope_file will be set to a notmuch_message_t (for top-level
+     * messages) or envelope_part will be set to a GMimeMessagePart
+     * (for embedded message parts).
+     */
+    GMimeObject *part;
+
+    /* If part is a GMimeMessage, these record the envelope of the
+     * message: either a notmuch_message_t representing a top-level
+     * message, or a GMimeMessagePart representing a MIME part
+     * containing a message.
+     */
+    notmuch_message_t *envelope_file;
+    GMimeMessagePart *envelope_part;
+
+    /* The number of children of this part. */
+    int nchildren;
+
+    /* The parent of this node or NULL if this is the root node. */
+    struct mime_node *parent;
+
+    /* The depth-first part number of this child if the MIME tree is
+     * being traversed in depth-first order, or -1 otherwise. */
+    int part_num;
+
+    /* True if decryption of this part was attempted. */
+    bool decrypt_attempted;
+    /* True if decryption of this part's child succeeded.  In this
+     * case, the decrypted part is substituted for the second child of
+     * this part (which would usually be the encrypted data). */
+    bool decrypt_success;
+
+    /* True if signature verification on this part was attempted. */
+    bool verify_attempted;
+
+    /* The list of signatures for signed or encrypted containers. If
+     * there are no signatures, this will be NULL. */
+    GMimeSignatureList* sig_list;
+
+    /* Internal: Context inherited from the root iterator. */
+    struct mime_node_context *ctx;
+
+    /* Internal: For successfully decrypted multipart parts, the
+     * decrypted part to substitute for the second child. */
+    GMimeObject *decrypted_child;
+
+    /* Internal: The next child for depth-first traversal and the part
+     * number to assign it (or -1 if unknown). */
+    int next_child;
+    int next_part_num;
+};
+
+/* Construct a new MIME node pointing to the root message part of
+ * message. If crypto->verify is true, signed child parts will be
+ * verified. If crypto->decrypt is NOTMUCH_DECRYPT_TRUE, encrypted
+ * child parts will be decrypted using either stored session keys or
+ * asymmetric crypto.  If crypto->decrypt is NOTMUCH_DECRYPT_AUTO,
+ * only session keys will be tried.  If the crypto contexts
+ * (crypto->gpgctx or crypto->pkcs7) are NULL, they will be lazily
+ * initialized.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Root node is returned in *node_out.
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: Failed to open message file.
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ */
+notmuch_status_t
+mime_node_open (const void *ctx, notmuch_message_t *message,
+               _notmuch_crypto_t *crypto, mime_node_t **node_out);
+
+/* Return a new MIME node for the requested child part of parent.
+ * parent will be used as the talloc context for the returned child
+ * node.
+ *
+ * In case of any failure, this function returns NULL, (after printing
+ * an error message on stderr).
+ */
+mime_node_t *
+mime_node_child (mime_node_t *parent, int child);
+
+/* Return the nth child of node in a depth-first traversal.  If n is
+ * 0, returns node itself.  Returns NULL if there is no such part. */
+mime_node_t *
+mime_node_seek_dfs (mime_node_t *node, int n);
+
+typedef enum dump_formats {
+    DUMP_FORMAT_AUTO,
+    DUMP_FORMAT_BATCH_TAG,
+    DUMP_FORMAT_SUP
+} dump_format_t;
+
+typedef enum dump_includes {
+    DUMP_INCLUDE_TAGS = 1,
+    DUMP_INCLUDE_CONFIG = 2,
+    DUMP_INCLUDE_PROPERTIES = 4
+} dump_include_t;
+
+#define DUMP_INCLUDE_DEFAULT (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES)
+
+#define NOTMUCH_DUMP_VERSION 3
+
+int
+notmuch_database_dump (notmuch_database_t *notmuch,
+                      const char *output_file_name,
+                      const char *query_str,
+                      dump_format_t output_format,
+                      dump_include_t include,
+                      bool gzip_output);
+
+/* If status is non-zero (i.e. error) print appropriate
+   messages to stderr.
+*/
+
+notmuch_status_t
+print_status_query (const char *loc,
+                   const notmuch_query_t *query,
+                   notmuch_status_t status);
+
+notmuch_status_t
+print_status_message (const char *loc,
+                     const notmuch_message_t *message,
+                     notmuch_status_t status);
+
+notmuch_status_t
+print_status_database (const char *loc,
+                      const notmuch_database_t *database,
+                      notmuch_status_t status);
+
+int
+status_to_exit (notmuch_status_t status);
+
+#include "command-line-arguments.h"
+
+extern const char *notmuch_requested_db_uuid;
+extern const notmuch_opt_desc_t  notmuch_shared_options [];
+void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
+
+void notmuch_process_shared_options (const char* subcommand_name);
+int notmuch_minimal_options (const char* subcommand_name,
+                            int argc, char **argv);
+
+
+/* the state chosen by the user invoking one of the notmuch
+ * subcommands that does indexing */
+struct _notmuch_client_indexing_cli_choices {
+    int decrypt_policy;
+    bool decrypt_policy_set;
+    notmuch_indexopts_t * opts;
+};
+extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
+extern const notmuch_opt_desc_t  notmuch_shared_indexing_options [];
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, notmuch_config_t *config);
+
+#endif
diff --git a/notmuch-compact.c b/notmuch-compact.c
new file mode 100644 (file)
index 0000000..f8996cf
--- /dev/null
@@ -0,0 +1,73 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2013 Ben Gamari
+ *
+ * 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/ .
+ *
+ * Author: Ben Gamari <bgamari.foss@gmail.com>
+ */
+
+#include "notmuch-client.h"
+
+static void
+status_update_cb (const char *msg, unused (void *closure))
+{
+    printf ("%s\n", msg);
+}
+
+int
+notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    const char *path = notmuch_config_get_database_path (config);
+    const char *backup_path = NULL;
+    notmuch_status_t ret;
+    bool quiet = false;
+    int opt_index;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_string = &backup_path, .name = "backup" },
+       { .opt_bool =  &quiet, .name = "quiet" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    if (notmuch_requested_db_uuid) {
+       fprintf (stderr, "Error: --uuid not implemented for compact\n");
+       return EXIT_FAILURE;
+    }
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (! quiet)
+       printf ("Compacting database...\n");
+    ret = notmuch_database_compact (path, backup_path,
+                                   quiet ? NULL : status_update_cb, NULL);
+    if (ret) {
+       fprintf (stderr, "Compaction failed: %s\n", notmuch_status_to_string (ret));
+       return EXIT_FAILURE;
+    }
+
+    if (! quiet) {
+       if (backup_path)
+           printf ("The old database has been moved to %s.\n", backup_path);
+
+       printf ("Done.\n");
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/notmuch-config.c b/notmuch-config.c
new file mode 100644 (file)
index 0000000..bf77cc9
--- /dev/null
@@ -0,0 +1,1127 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+#include <pwd.h>
+#include <netdb.h>
+#include <assert.h>
+
+static const char toplevel_config_comment[] =
+    " .notmuch-config - Configuration file for the notmuch mail system\n"
+    "\n"
+    " For more information about notmuch, see https://notmuchmail.org";
+
+static const char database_config_comment[] =
+    " Database configuration\n"
+    "\n"
+    " The only value supported here is 'path' which should be the top-level\n"
+    " directory where your mail currently exists and to where mail will be\n"
+    " delivered in the future. Files should be individual email messages.\n"
+    " Notmuch will store its database within a sub-directory of the path\n"
+    " configured here named \".notmuch\".\n";
+
+static const char new_config_comment[] =
+    " Configuration for \"notmuch new\"\n"
+    "\n"
+    " The following options are supported here:\n"
+    "\n"
+    "\ttags    A list (separated by ';') of the tags that will be\n"
+    "\t        added to all messages incorporated by \"notmuch new\".\n"
+    "\n"
+    "\tignore  A list (separated by ';') of file and directory names\n"
+    "\t        that will not be searched for messages by \"notmuch new\".\n"
+    "\n"
+    "\t        NOTE: *Every* file/directory that goes by one of those\n"
+    "\t        names will be ignored, independent of its depth/location\n"
+    "\t        in the mail store.\n";
+
+static const char user_config_comment[] =
+    " User configuration\n"
+    "\n"
+    " Here is where you can let notmuch know how you would like to be\n"
+    " addressed. Valid settings are\n"
+    "\n"
+    "\tname            Your full name.\n"
+    "\tprimary_email   Your primary email address.\n"
+    "\tother_email     A list (separated by ';') of other email addresses\n"
+    "\t                at which you receive email.\n"
+    "\n"
+    " Notmuch will use the various email addresses configured here when\n"
+    " formatting replies. It will avoid including your own addresses in the\n"
+    " recipient list of replies, and will set the From address based on the\n"
+    " address to which the original email was addressed.\n";
+
+static const char maildir_config_comment[] =
+    " Maildir compatibility configuration\n"
+    "\n"
+    " The following option is supported here:\n"
+    "\n"
+    "\tsynchronize_flags      Valid values are true and false.\n"
+    "\n"
+    "\tIf true, then the following maildir flags (in message filenames)\n"
+    "\twill be synchronized with the corresponding notmuch tags:\n"
+    "\n"
+    "\t\tFlag  Tag\n"
+    "\t\t----  -------\n"
+    "\t\tD     draft\n"
+    "\t\tF     flagged\n"
+    "\t\tP     passed\n"
+    "\t\tR     replied\n"
+    "\t\tS     unread (added when 'S' flag is not present)\n"
+    "\n"
+    "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
+    "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
+    "\tcommands will notice tag changes and update flags in filenames\n";
+
+static const char search_config_comment[] =
+    " Search configuration\n"
+    "\n"
+    " The following option is supported here:\n"
+    "\n"
+    "\texclude_tags\n"
+    "\t\tA ;-separated list of tags that will be excluded from\n"
+    "\t\tsearch results by default.  Using an excluded tag in a\n"
+    "\t\tquery will override that exclusion.\n";
+
+static const char crypto_config_comment[] =
+    " Cryptography related configuration\n"
+    "\n"
+#if (GMIME_MAJOR_VERSION < 3)
+    " The following *deprecated* option is currently supported:\n"
+    "\n"
+    "\tgpg_path\n"
+    "\t\tbinary name or full path to invoke gpg.\n"
+    "\t\tNOTE: In a future build, this option will be ignored.\n"
+#else
+    " The following old option is now ignored:\n"
+    "\n"
+    "\tgpgpath\n"
+    "\t\tThis option was used by older builds of notmuch to choose\n"
+    "\t\tthe version of gpg to use.\n"
+#endif
+    "\t\tSetting $PATH is a better approach.\n";
+
+struct _notmuch_config {
+    char *filename;
+    GKeyFile *key_file;
+    bool is_new;
+
+    char *database_path;
+    char *crypto_gpg_path;
+    char *user_name;
+    char *user_primary_email;
+    const char **user_other_email;
+    size_t user_other_email_length;
+    const char **new_tags;
+    size_t new_tags_length;
+    const char **new_ignore;
+    size_t new_ignore_length;
+    bool maildir_synchronize_flags;
+    const char **search_exclude_tags;
+    size_t search_exclude_tags_length;
+};
+
+static int
+notmuch_config_destructor (notmuch_config_t *config)
+{
+    if (config->key_file)
+       g_key_file_free (config->key_file);
+
+    return 0;
+}
+
+static char *
+get_name_from_passwd_file (void *ctx)
+{
+    long pw_buf_size;
+    char *pw_buf;
+    struct passwd passwd, *ignored;
+    char *name;
+    int e;
+
+    pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (pw_buf_size == -1) pw_buf_size = 64;
+    pw_buf = talloc_size (ctx, pw_buf_size);
+
+    while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
+                            pw_buf_size, &ignored)) == ERANGE) {
+        pw_buf_size = pw_buf_size * 2;
+        pw_buf = talloc_zero_size(ctx, pw_buf_size);
+    }
+
+    if (e == 0) {
+       char *comma = strchr (passwd.pw_gecos, ',');
+       if (comma)
+           name = talloc_strndup (ctx, passwd.pw_gecos,
+                                  comma - passwd.pw_gecos);
+       else
+           name = talloc_strdup (ctx, passwd.pw_gecos);
+    } else {
+       name = talloc_strdup (ctx, "");
+    }
+
+    talloc_free (pw_buf);
+
+    return name;
+}
+
+static char *
+get_username_from_passwd_file (void *ctx)
+{
+    long pw_buf_size;
+    char *pw_buf;
+    struct passwd passwd, *ignored;
+    char *name;
+    int e;
+
+    pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (pw_buf_size == -1) pw_buf_size = 64;
+    pw_buf = talloc_zero_size (ctx, pw_buf_size);
+
+    while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
+                            pw_buf_size, &ignored)) == ERANGE) {
+        pw_buf_size = pw_buf_size * 2;
+        pw_buf = talloc_zero_size(ctx, pw_buf_size);
+    }
+
+    if (e == 0)
+       name = talloc_strdup (ctx, passwd.pw_name);
+    else
+       name = talloc_strdup (ctx, "");
+
+    talloc_free (pw_buf);
+
+    return name;
+}
+
+static bool
+get_config_from_file (notmuch_config_t *config, bool 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;
+    bool ret = false;
+
+    FILE *fp = fopen(config->filename, "r");
+    if (fp == NULL) {
+       if (errno == ENOENT) {
+           /* 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;
+           } else {
+               fprintf (stderr, "Configuration file %s not found.\n"
+                        "Try running 'notmuch setup' to create a configuration.\n",
+                        config->filename);
+           }
+       } else {
+           fprintf (stderr, "Error opening config file '%s': %s\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
+ * ($HOME/.notmuch-config) will be used.
+ *
+ * If any error occurs, (out of memory, or a permission-denied error,
+ * etc.), this function will print a message to stderr and return
+ * NULL.
+ *
+ * FILE NOT FOUND: When the specified configuration file (whether from
+ * 'filename' or the $NOTMUCH_CONFIG environment variable) does not
+ * exist, the behavior of this function depends on the 'is_new_ret'
+ * variable.
+ *
+ *     If is_new_ret is NULL, then a "file not found" message will be
+ *     printed to stderr and NULL will be returned.
+
+ *     If is_new_ret is non-NULL then a default configuration will be
+ *     returned and *is_new_ret will be set to 1 on return so that
+ *     the caller can recognize this case.
+ *
+ *     These default configuration settings are determined as
+ *     follows:
+ *
+ *             database_path:          $MAILDIR, otherwise $HOME/mail
+ *
+ *             user_name:              $NAME variable if set, otherwise
+ *                                     read from /etc/passwd
+ *
+ *             user_primary_mail:      $EMAIL variable if set, otherwise
+ *                                     constructed from the username and
+ *                                     hostname of the current machine.
+ *
+ *             user_other_email:       Not set.
+ *
+ *     The default configuration also contains comments to guide the
+ *     user in editing the file directly.
+ */
+notmuch_config_t *
+notmuch_config_open (void *ctx,
+                    const char *filename,
+                    notmuch_config_mode_t config_mode)
+{
+    GError *error = NULL;
+    size_t tmp;
+    char *notmuch_config_env = NULL;
+    int file_had_database_group;
+    int file_had_new_group;
+    int file_had_user_group;
+    int file_had_maildir_group;
+    int file_had_search_group;
+    int file_had_crypto_group;
+
+    notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
+    if (config == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return NULL;
+    }
+    
+    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"))) {
+       config->filename = talloc_strdup (config, notmuch_config_env);
+    } else {
+       config->filename = talloc_asprintf (config, "%s/.notmuch-config",
+                                           getenv ("HOME"));
+    }
+
+    config->key_file = g_key_file_new ();
+
+    if (config_mode & NOTMUCH_CONFIG_OPEN) {
+       bool create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
+
+       if (! get_config_from_file (config, create_new)) {
+           talloc_free (config);
+           return NULL;
+       }
+    }
+
+    /* Whenever we know of configuration sections that don't appear in
+     * the configuration file, we add some comments to help the user
+     * understand what can be done.
+     *
+     * It would be convenient to just add those comments now, but
+     * apparently g_key_file will clear any comments when keys are
+     * added later that create the groups. So we have to check for the
+     * groups now, but add the comments only after setting all of our
+     * values.
+     */
+    file_had_database_group = g_key_file_has_group (config->key_file,
+                                                   "database");
+    file_had_new_group = g_key_file_has_group (config->key_file, "new");
+    file_had_user_group = g_key_file_has_group (config->key_file, "user");
+    file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
+    file_had_search_group = g_key_file_has_group (config->key_file, "search");
+    file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
+
+    if (notmuch_config_get_database_path (config) == NULL) {
+       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 = 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);
+    }
+
+    if (notmuch_config_get_user_primary_email (config) == NULL) {
+       char *email = getenv ("EMAIL");
+       if (email) {
+           notmuch_config_set_user_primary_email (config, email);
+       } else {
+           char hostname[256];
+           struct hostent *hostent;
+           const char *domainname;
+
+           char *username = get_username_from_passwd_file (config);
+
+           gethostname (hostname, 256);
+           hostname[255] = '\0';
+
+           hostent = gethostbyname (hostname);
+           if (hostent && (domainname = strchr (hostent->h_name, '.')))
+               domainname += 1;
+           else
+               domainname = "(none)";
+
+           email = talloc_asprintf (config, "%s@%s.%s",
+                                    username, hostname, domainname);
+
+           notmuch_config_set_user_primary_email (config, email);
+
+           talloc_free (username);
+           talloc_free (email);
+       }
+    }
+
+    if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
+        const char *tags[] = { "unread", "inbox" };
+       notmuch_config_set_new_tags (config, tags, 2);
+    }
+
+    if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
+       notmuch_config_set_new_ignore (config, NULL, 0);
+    }
+
+    if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
+       if (config->is_new) {
+           const char *tags[] = { "deleted", "spam" };
+           notmuch_config_set_search_exclude_tags (config, tags, 2);
+       } else {
+           notmuch_config_set_search_exclude_tags (config, NULL, 0);
+       }
+    }
+
+    error = NULL;
+    config->maildir_synchronize_flags =
+       g_key_file_get_boolean (config->key_file,
+                               "maildir", "synchronize_flags", &error);
+    if (error) {
+       notmuch_config_set_maildir_synchronize_flags (config, true);
+       g_error_free (error);
+    }
+
+#if (GMIME_MAJOR_VERSION < 3)
+    if (notmuch_config_get_crypto_gpg_path (config) == NULL) {
+       notmuch_config_set_crypto_gpg_path (config, "gpg");
+    }
+#endif
+
+    /* Whenever we know of configuration sections that don't appear in
+     * the configuration file, we add some comments to help the user
+     * understand what can be done. */
+    if (config->is_new)
+       g_key_file_set_comment (config->key_file, NULL, NULL,
+                               toplevel_config_comment, NULL);
+
+    if (! file_had_database_group)
+       g_key_file_set_comment (config->key_file, "database", NULL,
+                               database_config_comment, NULL);
+
+    if (! file_had_new_group)
+       g_key_file_set_comment (config->key_file, "new", NULL,
+                               new_config_comment, NULL);
+
+    if (! file_had_user_group)
+       g_key_file_set_comment (config->key_file, "user", NULL,
+                               user_config_comment, NULL);
+
+    if (! file_had_maildir_group)
+       g_key_file_set_comment (config->key_file, "maildir", NULL,
+                               maildir_config_comment, NULL);
+
+    if (! file_had_search_group)
+       g_key_file_set_comment (config->key_file, "search", NULL,
+                               search_config_comment, NULL);
+
+    if (! file_had_crypto_group)
+       g_key_file_set_comment (config->key_file, "crypto", NULL,
+                               crypto_config_comment, NULL);
+
+    return config;
+}
+
+/* Close the given notmuch_config_t object, freeing all resources.
+ * 
+ * Note: Any changes made to the configuration are *not* saved by this
+ * function. To save changes, call notmuch_config_save before
+ * notmuch_config_close.
+*/
+void
+notmuch_config_close (notmuch_config_t *config)
+{
+    talloc_free (config);
+}
+
+/* Save any changes made to the notmuch configuration.
+ *
+ * Any comments originally in the file will be preserved.
+ *
+ * Returns 0 if successful, and 1 in case of any error, (after
+ * printing a description of the error to stderr).
+ */
+int
+notmuch_config_save (notmuch_config_t *config)
+{
+    size_t length;
+    char *data, *filename;
+    GError *error = NULL;
+
+    data = g_key_file_to_data (config->key_file, &length, NULL);
+    if (data == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return 1;
+    }
+
+    /* Try not to overwrite symlinks. */
+    filename = canonicalize_file_name (config->filename);
+    if (! filename) {
+       if (errno == ENOENT) {
+           filename = strdup (config->filename);
+           if (! filename) {
+               fprintf (stderr, "Out of memory.\n");
+               g_free (data);
+               return 1;
+           }
+       } else {
+           fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
+                    strerror (errno));
+           g_free (data);
+           return 1;
+       }
+    }
+
+    if (! g_file_set_contents (filename, data, length, &error)) {
+       if (strcmp (filename, config->filename) != 0) {
+           fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
+                    config->filename, filename, error->message);
+       } else {
+           fprintf (stderr, "Error saving configuration to %s: %s\n",
+                    filename, error->message);
+       }
+       g_error_free (error);
+       free (filename);
+       g_free (data);
+       return 1;
+    }
+
+    free (filename);
+    g_free (data);
+    return 0;
+}
+
+bool
+notmuch_config_is_new (notmuch_config_t *config)
+{
+    return config->is_new;
+}
+
+static const char *
+_config_get (notmuch_config_t *config, char **field,
+            const char *group, const char *key)
+{
+    /* read from config file and cache value, if not cached already */
+    if (*field == NULL) {
+       char *value;
+       value = g_key_file_get_string (config->key_file, group, key, NULL);
+       if (value) {
+           *field = talloc_strdup (config, value);
+           free (value);
+       }
+    }
+    return *field;
+}
+
+static void
+_config_set (notmuch_config_t *config, char **field,
+            const char *group, const char *key, const char *value)
+{
+    g_key_file_set_string (config->key_file, group, key, value);
+
+    /* drop the cached value */
+    talloc_free (*field);
+    *field = NULL;
+}
+
+static const char **
+_config_get_list (notmuch_config_t *config,
+                 const char *section, const char *key,
+                 const char ***outlist, size_t *list_length, size_t *ret_length)
+{
+    assert(outlist);
+
+    /* read from config file and cache value, if not cached already */
+    if (*outlist == NULL) {
+
+       char **inlist = g_key_file_get_string_list (config->key_file,
+                                            section, key, list_length, NULL);
+       if (inlist) {
+           unsigned int i;
+
+           *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
+
+           for (i = 0; i < *list_length; i++)
+               (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
+
+           (*outlist)[i] = NULL;
+
+           g_strfreev (inlist);
+       }
+    }
+
+    if (ret_length)
+       *ret_length = *list_length;
+
+    return *outlist;
+}
+
+static void
+_config_set_list (notmuch_config_t *config,
+                 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, key, list, length);
+
+    /* drop the cached value */
+    talloc_free (*config_var);
+    *config_var = NULL;
+}
+
+const char *
+notmuch_config_get_database_path (notmuch_config_t *config)
+{
+    char *db_path = (char *)_config_get (config, &config->database_path, "database", "path");
+
+    if (db_path && *db_path != '/') {
+       /* If the path in the configuration file begins with any
+        * character other than /, presume that it is relative to
+        * $HOME and update as appropriate.
+        */
+       char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
+       talloc_free (db_path);
+       db_path = config->database_path = abs_path;
+    }
+
+    return db_path;
+}
+
+void
+notmuch_config_set_database_path (notmuch_config_t *config,
+                                 const char *database_path)
+{
+    _config_set (config, &config->database_path, "database", "path", database_path);
+}
+
+const char *
+notmuch_config_get_user_name (notmuch_config_t *config)
+{
+    return _config_get (config, &config->user_name, "user", "name");
+}
+
+void
+notmuch_config_set_user_name (notmuch_config_t *config,
+                             const char *user_name)
+{
+    _config_set (config, &config->user_name, "user", "name", user_name);
+}
+
+const char *
+notmuch_config_get_user_primary_email (notmuch_config_t *config)
+{
+    return _config_get (config, &config->user_primary_email, "user", "primary_email");
+}
+
+void
+notmuch_config_set_user_primary_email (notmuch_config_t *config,
+                                      const char *primary_email)
+{
+    _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
+}
+
+const char **
+notmuch_config_get_user_other_email (notmuch_config_t *config,   size_t *length)
+{
+    return _config_get_list (config, "user", "other_email",
+                            &(config->user_other_email),
+                            &(config->user_other_email_length), length);
+}
+
+const char **
+notmuch_config_get_new_tags (notmuch_config_t *config,   size_t *length)
+{
+    return _config_get_list (config, "new", "tags",
+                            &(config->new_tags),
+                            &(config->new_tags_length), length);
+}
+
+const char **
+notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
+{
+    return _config_get_list (config, "new", "ignore",
+                            &(config->new_ignore),
+                            &(config->new_ignore_length), length);
+}
+
+void
+notmuch_config_set_user_other_email (notmuch_config_t *config,
+                                    const char *list[],
+                                    size_t length)
+{
+    _config_set_list (config, "user", "other_email", list, length,
+                    &(config->user_other_email));
+}
+
+void
+notmuch_config_set_new_tags (notmuch_config_t *config,
+                                    const char *list[],
+                                    size_t length)
+{
+    _config_set_list (config, "new", "tags", list, length,
+                    &(config->new_tags));
+}
+
+void
+notmuch_config_set_new_ignore (notmuch_config_t *config,
+                              const char *list[],
+                              size_t length)
+{
+    _config_set_list (config, "new", "ignore", list, length,
+                    &(config->new_ignore));
+}
+
+const char **
+notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
+{
+    return _config_get_list (config, "search", "exclude_tags",
+                            &(config->search_exclude_tags),
+                            &(config->search_exclude_tags_length), length);
+}
+
+void
+notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
+                                     const char *list[],
+                                     size_t length)
+{
+    _config_set_list (config, "search", "exclude_tags", list, length,
+                     &(config->search_exclude_tags));
+}
+
+#if (GMIME_MAJOR_VERSION < 3)
+const char *
+notmuch_config_get_crypto_gpg_path (notmuch_config_t *config)
+{
+    return _config_get (config, &config->crypto_gpg_path, "crypto", "gpg_path");
+}
+
+void
+notmuch_config_set_crypto_gpg_path (notmuch_config_t *config,
+                             const char *gpg_path)
+{
+    _config_set (config, &config->crypto_gpg_path, "crypto", "gpg_path", gpg_path);
+}
+#endif
+
+
+/* Given a configuration item of the form <group>.<key> return the
+ * component group and key. If any error occurs, print a message on
+ * stderr and return 1. Otherwise, return 0.
+ *
+ * Note: This function modifies the original 'item' string.
+ */
+static int
+_item_split (char *item, char **group, char **key)
+{
+    char *period;
+
+    *group = item;
+
+    period = strchr (item, '.');
+    if (period == NULL || *(period+1) == '\0') {
+       fprintf (stderr,
+                "Invalid configuration name: %s\n"
+                "(Should be of the form <section>.<item>)\n", item);
+       return 1;
+    }
+
+    *period = '\0';
+    *key = period + 1;
+
+    return 0;
+}
+
+#define BUILT_WITH_PREFIX "built_with."
+
+static bool
+_stored_in_db (const char *item)
+{
+    const char * db_configs[] = {
+       "index.decrypt",
+    };
+    if (STRNCMP_LITERAL (item, "query.") == 0)
+       return true;
+    for (size_t i = 0; i < ARRAY_SIZE (db_configs); i++)
+       if (strcmp (item, db_configs[i]) == 0)
+           return true;
+    return false;
+}
+
+static int
+_print_db_config(notmuch_config_t *config, const char *name)
+{
+    notmuch_database_t *notmuch;
+    char *val;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_get_config (notmuch, name, &val)))
+       return EXIT_FAILURE;
+
+     puts (val);
+
+    return EXIT_SUCCESS;
+}
+
+static int
+notmuch_config_command_get (notmuch_config_t *config, char *item)
+{
+    if (strcmp(item, "database.path") == 0) {
+       printf ("%s\n", notmuch_config_get_database_path (config));
+    } else if (strcmp(item, "user.name") == 0) {
+       printf ("%s\n", notmuch_config_get_user_name (config));
+    } else if (strcmp(item, "user.primary_email") == 0) {
+       printf ("%s\n", notmuch_config_get_user_primary_email (config));
+    } else if (strcmp(item, "user.other_email") == 0) {
+       const char **other_email;
+       size_t i, length;
+       
+       other_email = notmuch_config_get_user_other_email (config, &length);
+       for (i = 0; i < length; i++)
+           printf ("%s\n", other_email[i]);
+    } else if (strcmp(item, "new.tags") == 0) {
+       const char **tags;
+       size_t i, length;
+
+       tags = notmuch_config_get_new_tags (config, &length);
+       for (i = 0; i < length; i++)
+           printf ("%s\n", tags[i]);
+    } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+       printf ("%s\n",
+               notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
+    } else if (_stored_in_db (item)) {
+       return _print_db_config (config, item);
+    } else {
+       char **value;
+       size_t i, length;
+       char *group, *key;
+
+       if (_item_split (item, &group, &key))
+           return 1;
+
+       value = g_key_file_get_string_list (config->key_file,
+                                           group, key,
+                                           &length, NULL);
+       if (value == NULL) {
+           fprintf (stderr, "Unknown configuration item: %s.%s\n",
+                    group, key);
+           return 1;
+       }
+
+       for (i = 0; i < length; i++)
+           printf ("%s\n", value[i]);
+
+       g_strfreev (value);
+    }
+
+    return 0;
+}
+
+static int
+_set_db_config(notmuch_config_t *config, const char *key, int argc, char **argv)
+{
+    notmuch_database_t *notmuch;
+    const char *val = "";
+
+    if (argc > 1) {
+       /* XXX handle lists? */
+       fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
+       return EXIT_FAILURE;
+    }
+
+    if (argc > 0) {
+       val = argv[0];
+    }
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_set_config (notmuch, key, val)))
+       return EXIT_FAILURE;
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_close (notmuch)))
+       return EXIT_FAILURE;
+
+    return EXIT_SUCCESS;
+}
+
+static int
+notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
+{
+    char *group, *key;
+
+    if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+       fprintf (stderr, "Error: read only option: %s\n", item);
+       return 1;
+    }
+
+    if (_stored_in_db (item)) {
+       return _set_db_config (config, item, argc, argv);
+    }
+
+    if (_item_split (item, &group, &key))
+       return 1;
+
+    /* With only the name of an item, we clear it from the
+     * configuration file.
+     *
+     * With a single value, we set it as a string.
+     *
+     * With multiple values, we set them as a string list.
+     */
+    switch (argc) {
+    case 0:
+       g_key_file_remove_key (config->key_file, group, key, NULL);
+       break;
+    case 1:
+       g_key_file_set_string (config->key_file, group, key, argv[0]);
+       break;
+    default:
+       g_key_file_set_string_list (config->key_file, group, key,
+                                   (const gchar **) argv, argc);
+       break;
+    }
+
+    return notmuch_config_save (config);
+}
+
+static
+void
+_notmuch_config_list_built_with ()
+{
+    printf("%scompact=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("compact") ? "true" : "false");
+    printf("%sfield_processor=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("field_processor") ? "true" : "false");
+    printf("%sretry_lock=%s\n",
+          BUILT_WITH_PREFIX,
+          notmuch_built_with ("retry_lock") ? "true" : "false");
+}
+
+static int
+_list_db_config (notmuch_config_t *config)
+{
+    notmuch_database_t *notmuch;
+    notmuch_config_list_t *list;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    /* XXX Handle UUID mismatch? */
+
+
+    if (print_status_database ("notmuch config", notmuch,
+                              notmuch_database_get_config_list (notmuch, "", &list)))
+       return EXIT_FAILURE;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+       printf("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+    }
+    notmuch_config_list_destroy (list);
+
+   return EXIT_SUCCESS;
+}
+
+static int
+notmuch_config_command_list (notmuch_config_t *config)
+{
+    char **groups;
+    size_t g, groups_length;
+
+    groups = g_key_file_get_groups (config->key_file, &groups_length);
+    if (groups == NULL)
+       return 1;
+
+    for (g = 0; g < groups_length; g++) {
+       char **keys;
+       size_t k, keys_length;
+
+       keys = g_key_file_get_keys (config->key_file,
+                                   groups[g], &keys_length, NULL);
+       if (keys == NULL)
+           continue;
+
+       for (k = 0; k < keys_length; k++) {
+           char *value;
+
+           value = g_key_file_get_string (config->key_file,
+                                          groups[g], keys[k], NULL);
+           if (value != NULL) {
+               printf ("%s.%s=%s\n", groups[g], keys[k], value);
+               free (value);
+           }
+       }
+
+       g_strfreev (keys);
+    }
+
+    g_strfreev (groups);
+
+    _notmuch_config_list_built_with ();
+    return _list_db_config (config);
+}
+
+int
+notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    int ret;
+    int opt_index;
+
+    opt_index = notmuch_minimal_options ("config", argc, argv);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    if (notmuch_requested_db_uuid)
+       fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+                notmuch_requested_db_uuid);
+
+    /* skip at least subcommand argument */
+    argc-= opt_index;
+    argv+= opt_index;
+
+    if (argc < 1) {
+       fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
+       return EXIT_FAILURE;
+    }
+
+    if (strcmp (argv[0], "get") == 0) {
+       if (argc != 2) {
+           fprintf (stderr, "Error: notmuch config get requires exactly "
+                    "one argument.\n");
+           return EXIT_FAILURE;
+       }
+       ret = notmuch_config_command_get (config, argv[1]);
+    } else if (strcmp (argv[0], "set") == 0) {
+       if (argc < 2) {
+           fprintf (stderr, "Error: notmuch config set requires at least "
+                    "one argument.\n");
+           return EXIT_FAILURE;
+       }
+       ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
+    } else if (strcmp (argv[0], "list") == 0) {
+       ret = notmuch_config_command_list (config);
+    } else {
+       fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
+                argv[0]);
+       return EXIT_FAILURE;
+    }
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+
+}
+
+bool
+notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
+{
+    return config->maildir_synchronize_flags;
+}
+
+void
+notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
+                                             bool synchronize_flags)
+{
+    g_key_file_set_boolean (config->key_file,
+                           "maildir", "synchronize_flags", synchronize_flags);
+    config->maildir_synchronize_flags = synchronize_flags;
+}
diff --git a/notmuch-count.c b/notmuch-count.c
new file mode 100644 (file)
index 0000000..ca05c97
--- /dev/null
@@ -0,0 +1,233 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * 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/ .
+ *
+ * Author: Keith Packard <keithp@keithp.com>
+ */
+
+#include "notmuch-client.h"
+
+enum {
+    OUTPUT_THREADS,
+    OUTPUT_MESSAGES,
+    OUTPUT_FILES,
+};
+
+/* Return the number of files matching the query, or -1 for an error */
+static int
+count_files (notmuch_query_t *query)
+{
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_filenames_t *filenames;
+    notmuch_status_t status;
+    int count = 0;
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch count", query, status))
+       return -1;
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+       filenames = notmuch_message_get_filenames (message);
+
+       for (;
+            notmuch_filenames_valid (filenames);
+            notmuch_filenames_move_to_next (filenames))
+           count++;
+
+       notmuch_filenames_destroy (filenames);
+       notmuch_message_destroy (message);
+    }
+
+    notmuch_messages_destroy (messages);
+
+    return count;
+}
+
+/* return 0 on success, -1 on failure */
+static int
+print_count (notmuch_database_t *notmuch, const char *query_str,
+            const char **exclude_tags, size_t exclude_tags_length, int output, int print_lastmod)
+{
+    notmuch_query_t *query;
+    size_t i;
+    int count;
+    unsigned int ucount;
+    unsigned long revision;
+    const char *uuid;
+    int ret = 0;
+    notmuch_status_t status;
+
+    query = notmuch_query_create (notmuch, query_str);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return -1;
+    }
+
+    for (i = 0; i < exclude_tags_length; i++) {
+       status = notmuch_query_add_tag_exclude (query, exclude_tags[i]);
+       if (status && status != NOTMUCH_STATUS_IGNORED) {
+           print_status_query ("notmuch count", query, status);
+           return -1;
+       }
+    }
+
+    switch (output) {
+    case OUTPUT_MESSAGES:
+       status = notmuch_query_count_messages (query, &ucount);
+       if (print_status_query ("notmuch count", query, status))
+           return -1;
+       printf ("%u", ucount);
+       break;
+    case OUTPUT_THREADS:
+       status = notmuch_query_count_threads (query, &ucount);
+       if (print_status_query ("notmuch count", query, status))
+           return -1;
+       printf ("%u", ucount);
+       break;
+    case OUTPUT_FILES:
+       count = count_files (query);
+       if (count >= 0) {
+           printf ("%d", count);
+       } else {
+           ret = -1;
+           goto DONE;
+       }
+       break;
+    }
+
+    if (print_lastmod) {
+       revision = notmuch_database_get_revision (notmuch, &uuid);
+       printf ("\t%s\t%lu\n", uuid, revision);
+    } else {
+       fputs ("\n", stdout);
+    }
+
+  DONE:
+    notmuch_query_destroy (query);
+
+    return ret;
+}
+
+static int
+count_file (notmuch_database_t *notmuch, FILE *input, const char **exclude_tags,
+           size_t exclude_tags_length, int output, int print_lastmod)
+{
+    char *line = NULL;
+    ssize_t line_len;
+    size_t line_size;
+    int ret = 0;
+
+    while (! ret && (line_len = getline (&line, &line_size, input)) != -1) {
+       chomp_newline (line);
+       ret = print_count (notmuch, line, exclude_tags, exclude_tags_length,
+                          output, print_lastmod);
+    }
+
+    if (line)
+       free (line);
+
+    return ret;
+}
+
+int
+notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    char *query_str;
+    int opt_index;
+    int output = OUTPUT_MESSAGES;
+    bool exclude = true;
+    const char **search_exclude_tags = NULL;
+    size_t search_exclude_tags_length = 0;
+    bool batch = false;
+    bool print_lastmod = false;
+    FILE *input = stdin;
+    const char *input_file_name = NULL;
+    int ret;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &output, .name = "output", .keywords =
+         (notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
+                                 { "messages", OUTPUT_MESSAGES },
+                                 { "files", OUTPUT_FILES },
+                                 { 0, 0 } } },
+       { .opt_bool = &exclude, .name = "exclude" },
+       { .opt_bool = &print_lastmod, .name = "lastmod" },
+       { .opt_bool = &batch, .name = "batch" },
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (input_file_name) {
+       batch = true;
+       input = fopen (input_file_name, "r");
+       if (input == NULL) {
+           fprintf (stderr, "Error opening %s for reading: %s\n",
+                    input_file_name, strerror (errno));
+           return EXIT_FAILURE;
+       }
+    }
+
+    if (batch && opt_index != argc) {
+       fprintf (stderr, "--batch and query string are not compatible\n");
+       if (input)
+           fclose (input);
+       return EXIT_FAILURE;
+    }
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    query_str = query_string_from_args (config, argc - opt_index, argv + opt_index);
+    if (query_str == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return EXIT_FAILURE;
+    }
+
+    if (exclude) {
+       search_exclude_tags = notmuch_config_get_search_exclude_tags
+           (config, &search_exclude_tags_length);
+    }
+
+    if (batch)
+       ret = count_file (notmuch, input, search_exclude_tags,
+                         search_exclude_tags_length, output, print_lastmod);
+    else
+       ret = print_count (notmuch, query_str, search_exclude_tags,
+                          search_exclude_tags_length, output, print_lastmod);
+
+    notmuch_database_destroy (notmuch);
+
+    if (input != stdin)
+       fclose (input);
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-dump.c b/notmuch-dump.c
new file mode 100644 (file)
index 0000000..ef2f02d
--- /dev/null
@@ -0,0 +1,417 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "hex-escape.h"
+#include "string-util.h"
+#include <zlib.h>
+
+static int
+database_dump_config (notmuch_database_t *notmuch, gzFile output)
+{
+    notmuch_config_list_t *list;
+    int ret = EXIT_FAILURE;
+    char *buffer = NULL;
+    size_t buffer_size = 0;
+
+    if (print_status_database ("notmuch dump", notmuch,
+                              notmuch_database_get_config_list (notmuch, NULL, &list)))
+       goto DONE;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+       if (hex_encode (notmuch, notmuch_config_list_key (list),
+                       &buffer, &buffer_size) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode config key %s\n",
+                    notmuch_config_list_key (list));
+           goto DONE;
+       }
+       gzprintf (output, "#@ %s", buffer);
+
+       if (hex_encode (notmuch, notmuch_config_list_value (list),
+                       &buffer, &buffer_size) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode config value %s\n",
+                    notmuch_config_list_value (list) );
+           goto DONE;
+       }
+
+       gzprintf (output, " %s\n", buffer);
+    }
+
+    ret = EXIT_SUCCESS;
+
+ DONE:
+    if (list)
+       notmuch_config_list_destroy (list);
+
+    if (buffer)
+       talloc_free (buffer);
+
+    return ret;
+}
+
+static void
+print_dump_header (gzFile output, int output_format, int include)
+{
+    const char *sep = "";
+
+    gzprintf (output, "#notmuch-dump %s:%d ",
+             (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag",
+             NOTMUCH_DUMP_VERSION);
+
+    if (include & DUMP_INCLUDE_CONFIG) {
+       gzputs (output, "config");
+       sep = ",";
+    }
+    if (include & DUMP_INCLUDE_PROPERTIES) {
+       gzprintf (output, "%sproperties", sep);
+       sep = ",";
+    }
+    if (include & DUMP_INCLUDE_TAGS) {
+       gzprintf (output, "%stags", sep);
+    }
+    gzputs (output, "\n");
+}
+
+static int
+dump_properties_message (void *ctx,
+                        notmuch_message_t *message,
+                        gzFile output,
+                        char **buffer_p, size_t *size_p)
+{
+    const char *message_id;
+    notmuch_message_properties_t *list;
+    bool first = true;
+
+    message_id = notmuch_message_get_message_id (message);
+
+    if (strchr (message_id, '\n')) {
+       fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id);
+       return 0;
+    }
+
+    for (list = notmuch_message_get_properties (message, "", false);
+        notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
+       const char *key, *val;
+
+       if (first) {
+           if (hex_encode (ctx, message_id, buffer_p, size_p) != HEX_SUCCESS) {
+               fprintf (stderr, "Error: failed to hex-encode message-id %s\n", message_id);
+               return 1;
+           }
+           gzprintf (output, "#= %s", *buffer_p);
+           first = false;
+       }
+
+       key = notmuch_message_properties_key (list);
+       val = notmuch_message_properties_value (list);
+
+       if (hex_encode (ctx, key, buffer_p, size_p) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode key %s\n", key);
+           return 1;
+       }
+       gzprintf (output, " %s", *buffer_p);
+
+       if (hex_encode (ctx, val, buffer_p, size_p) != HEX_SUCCESS) {
+           fprintf (stderr, "Error: failed to hex-encode value %s\n", val);
+           return 1;
+       }
+       gzprintf (output, "=%s", *buffer_p);
+    }
+    notmuch_message_properties_destroy (list);
+
+    if (! first)
+       gzprintf (output, "\n", *buffer_p);
+
+    return 0;
+}
+
+static int
+dump_tags_message (void *ctx,
+                  notmuch_message_t *message, int output_format,
+                  gzFile output,
+                  char **buffer_p, size_t *size_p)
+{
+    int first = 1;
+    const char *message_id;
+
+    message_id = notmuch_message_get_message_id (message);
+
+    if (output_format == DUMP_FORMAT_BATCH_TAG &&
+       strchr (message_id, '\n')) {
+       /* This will produce a line break in the output, which
+        * would be difficult to handle in tools.  However, it's
+        * also impossible to produce an email containing a line
+        * break in a message ID because of unfolding, so we can
+        * safely disallow it. */
+       fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id);
+       return EXIT_SUCCESS;
+    }
+
+    if (output_format == DUMP_FORMAT_SUP) {
+       gzprintf (output, "%s (", message_id);
+    }
+
+    for (notmuch_tags_t *tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags)) {
+       const char *tag_str = notmuch_tags_get (tags);
+
+       if (! first)
+           gzputs (output, " ");
+
+       first = 0;
+
+       if (output_format == DUMP_FORMAT_SUP) {
+           gzputs (output, tag_str);
+       } else {
+           if (hex_encode (ctx, tag_str,
+                           buffer_p, size_p) != HEX_SUCCESS) {
+               fprintf (stderr, "Error: failed to hex-encode tag %s\n",
+                        tag_str);
+               return EXIT_FAILURE;
+           }
+           gzprintf (output, "+%s", *buffer_p);
+       }
+    }
+
+    if (output_format == DUMP_FORMAT_SUP) {
+       gzputs (output, ")\n");
+    } else {
+       if (make_boolean_term (ctx, "id", message_id,
+                              buffer_p, size_p)) {
+           fprintf (stderr, "Error quoting message id %s: %s\n",
+                    message_id, strerror (errno));
+           return EXIT_FAILURE;
+       }
+       gzprintf (output, " -- %s\n", *buffer_p);
+    }
+    return EXIT_SUCCESS;
+}
+
+static int
+database_dump_file (notmuch_database_t *notmuch, gzFile output,
+                   const char *query_str, int output_format, int include)
+{
+    notmuch_query_t *query;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+    char *buffer = NULL;
+    size_t buffer_size = 0;
+
+    print_dump_header (output, output_format, include);
+
+    if (include & DUMP_INCLUDE_CONFIG) {
+       if (print_status_database ("notmuch dump", notmuch,
+                                  database_dump_config(notmuch,output)))
+           return EXIT_FAILURE;
+    }
+
+    if (! (include & (DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES)))
+       return EXIT_SUCCESS;
+
+    if (! query_str)
+       query_str = "";
+
+    query = notmuch_query_create (notmuch, query_str);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+    /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
+     * first results quickly at the expense of total time.
+     */
+    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch dump", query, status))
+       return EXIT_FAILURE;
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages)) {
+
+       message = notmuch_messages_get (messages);
+
+       if ((include & DUMP_INCLUDE_TAGS) &&
+           dump_tags_message (notmuch, message, output_format, output,
+                              &buffer, &buffer_size))
+           return EXIT_FAILURE;
+
+       if ((include & DUMP_INCLUDE_PROPERTIES) &&
+           dump_properties_message (notmuch, message, output,
+                                    &buffer, &buffer_size))
+           return EXIT_FAILURE;
+
+       notmuch_message_destroy (message);
+    }
+
+    notmuch_query_destroy (query);
+
+    return EXIT_SUCCESS;
+}
+
+/* Dump database into output_file_name if it's non-NULL, stdout
+ * otherwise.
+ */
+int
+notmuch_database_dump (notmuch_database_t *notmuch,
+                      const char *output_file_name,
+                      const char *query_str,
+                      dump_format_t output_format,
+                      dump_include_t include,
+                      bool gzip_output)
+{
+    gzFile output = NULL;
+    const char *mode = gzip_output ? "w9" : "wT";
+    const char *name_for_error = output_file_name ? output_file_name : "stdout";
+
+    char *tempname = NULL;
+    int outfd = -1;
+
+    int ret = -1;
+
+    if (output_file_name) {
+       tempname = talloc_asprintf (notmuch, "%s.XXXXXX", output_file_name);
+       outfd = mkstemp (tempname);
+    } else {
+       outfd = dup (STDOUT_FILENO);
+    }
+
+    if (outfd < 0) {
+       fprintf (stderr, "Bad output file %s\n", name_for_error);
+       goto DONE;
+    }
+
+    output = gzdopen (outfd, mode);
+
+    if (output == NULL) {
+       fprintf (stderr, "Error opening %s for (gzip) writing: %s\n",
+                name_for_error, strerror (errno));
+       if (close (outfd))
+           fprintf (stderr, "Error closing %s during shutdown: %s\n",
+                name_for_error, strerror (errno));
+       goto DONE;
+    }
+
+    ret = database_dump_file (notmuch, output, query_str, output_format, include);
+    if (ret) goto DONE;
+
+    ret = gzflush (output, Z_FINISH);
+    if (ret) {
+       fprintf (stderr, "Error flushing output: %s\n", gzerror (output, NULL));
+       goto DONE;
+    }
+
+    if (output_file_name) {
+       ret = fsync (outfd);
+       if (ret) {
+           fprintf (stderr, "Error syncing %s to disk: %s\n",
+                    name_for_error, strerror (errno));
+           goto DONE;
+       }
+    }
+
+    if (gzclose_w (output) != Z_OK) {
+       fprintf (stderr, "Error closing %s: %s\n", name_for_error,
+                gzerror (output, NULL));
+       ret = EXIT_FAILURE;
+       output = NULL;
+       goto DONE;
+    }
+
+    if (output_file_name) {
+       ret = rename (tempname, output_file_name);
+       if (ret) {
+           fprintf (stderr, "Error renaming %s to %s: %s\n",
+                    tempname, output_file_name, strerror (errno));
+           goto DONE;
+       }
+
+    }
+ DONE:
+    if (ret != EXIT_SUCCESS && output)
+       (void) gzclose_w (output);
+
+    if (ret != EXIT_SUCCESS && output_file_name)
+       (void) unlink (tempname);
+
+    return ret;
+}
+
+int
+notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    const char *query_str = NULL;
+    int ret;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    const char *output_file_name = NULL;
+    int opt_index;
+
+    int output_format = DUMP_FORMAT_BATCH_TAG;
+    int include = 0;
+    bool gzip_output = 0;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &output_format, .name = "format", .keywords =
+         (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
+                                 { "batch-tag", DUMP_FORMAT_BATCH_TAG },
+                                 { 0, 0 } } },
+       { .opt_flags = &include, .name = "include", .keywords =
+         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                 { "properties", DUMP_INCLUDE_PROPERTIES },
+                                 { "tags", DUMP_INCLUDE_TAGS} } },
+       { .opt_string = &output_file_name, .name = "output" },
+       { .opt_bool = &gzip_output, .name = "gzip" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (include == 0)
+       include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS | DUMP_INCLUDE_PROPERTIES;
+
+    if (opt_index < argc) {
+       query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
+       if (query_str == NULL) {
+           fprintf (stderr, "Out of memory.\n");
+           return EXIT_FAILURE;
+       }
+    }
+
+    ret = notmuch_database_dump (notmuch, output_file_name, query_str,
+                                output_format, include, gzip_output);
+
+    notmuch_database_destroy (notmuch);
+
+    return ret;
+}
diff --git a/notmuch-emacs.README.Debian b/notmuch-emacs.README.Debian
deleted file mode 100644 (file)
index 33f2d99..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-* For help in getting started with notmuch and the emacs interface,
-  see /usr/share/doc/notmuch/README.
-
- -- David Bremner <bremner@debian.org>, Wed, 27 Nov 2013 08:17:11 -0400
diff --git a/notmuch-emacs.maintscript b/notmuch-emacs.maintscript
deleted file mode 100644 (file)
index 6f93feb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-rm_conffile /etc/emacs/site-start.d/50notmuch.el
diff --git a/notmuch-insert.c b/notmuch-insert.c
new file mode 100644 (file)
index 0000000..d229c9d
--- /dev/null
@@ -0,0 +1,593 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2013 Peter Wang
+ *
+ * Based in part on notmuch-deliver
+ * Copyright © 2010 Ali Polatel
+ *
+ * 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/ .
+ *
+ * Author: Peter Wang <novalazy@gmail.com>
+ */
+
+#include "notmuch-client.h"
+#include "tag-util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "string-util.h"
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+/* Like gethostname but guarantees that a null-terminated hostname is
+ * returned, even if it has to make one up. Invalid characters are
+ * substituted such that the hostname can be used within a filename.
+ */
+static void
+safe_gethostname (char *hostname, size_t len)
+{
+    char *p;
+
+    if (gethostname (hostname, len) == -1) {
+       strncpy (hostname, "unknown", len);
+    }
+    hostname[len - 1] = '\0';
+
+    for (p = hostname; *p != '\0'; p++) {
+       if (*p == '/' || *p == ':')
+           *p = '_';
+    }
+}
+
+/* Call fsync() on a directory path. */
+static bool
+sync_dir (const char *dir)
+{
+    int fd, r;
+
+    fd = open (dir, O_RDONLY);
+    if (fd == -1) {
+       fprintf (stderr, "Error: open %s: %s\n", dir, strerror (errno));
+       return false;
+    }
+
+    r = fsync (fd);
+    if (r)
+       fprintf (stderr, "Error: fsync %s: %s\n", dir, strerror (errno));
+
+    close (fd);
+
+    return r == 0;
+}
+
+/*
+ * Check the specified folder name does not contain a directory
+ * component ".." to prevent writes outside of the Maildir
+ * hierarchy. Return true on valid folder name, false otherwise.
+ */
+static bool
+is_valid_folder_name (const char *folder)
+{
+    const char *p = folder;
+
+    for (;;) {
+       if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
+           return false;
+       p = strchr (p, '/');
+       if (!p)
+           return true;
+       p++;
+    }
+}
+
+/*
+ * Make the given directory and its parents as necessary, using the
+ * given mode. Return true on success, false otherwise. Partial
+ * results are not cleaned up on errors.
+ */
+static bool
+mkdir_recursive (const void *ctx, const char *path, int mode)
+{
+    struct stat st;
+    int r;
+    char *parent = NULL, *slash;
+
+    /* First check the common case: directory already exists. */
+    r = stat (path, &st);
+    if (r == 0) {
+        if (! S_ISDIR (st.st_mode)) {
+           fprintf (stderr, "Error: '%s' is not a directory: %s\n",
+                    path, strerror (EEXIST));
+           return false;
+       }
+
+       return true;
+    } else if (errno != ENOENT) {
+       fprintf (stderr, "Error: stat '%s': %s\n", path, strerror (errno));
+       return false;
+    }
+
+    /* mkdir parents, if any */
+    slash = strrchr (path, '/');
+    if (slash && slash != path) {
+       parent = talloc_strndup (ctx, path, slash - path);
+       if (! parent) {
+           fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+           return false;
+       }
+
+       if (! mkdir_recursive (ctx, parent, mode))
+           return false;
+    }
+
+    if (mkdir (path, mode)) {
+       fprintf (stderr, "Error: mkdir '%s': %s\n", path, strerror (errno));
+       return false;
+    }
+
+    return parent ? sync_dir (parent) : true;
+}
+
+/*
+ * Create the given maildir folder, i.e. maildir and its
+ * subdirectories cur/new/tmp. Return true on success, false
+ * otherwise. Partial results are not cleaned up on errors.
+ */
+static bool
+maildir_create_folder (const void *ctx, const char *maildir, bool world_readable)
+{
+    const char *subdirs[] = { "cur", "new", "tmp" };
+    const int mode = (world_readable ? 0755 : 0700);
+    char *subdir;
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (subdirs); i++) {
+       subdir = talloc_asprintf (ctx, "%s/%s", maildir, subdirs[i]);
+       if (! subdir) {
+           fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+           return false;
+       }
+
+       if (! mkdir_recursive (ctx, subdir, mode))
+           return false;
+    }
+
+    return true;
+}
+
+/*
+ * Generate a temporary file basename, no path, do not create an
+ * actual file. Return the basename, or NULL on errors.
+ */
+static char *
+tempfilename (const void *ctx)
+{
+    char *filename;
+    char hostname[256];
+    struct timeval tv;
+    pid_t pid;
+
+    /* We follow the Dovecot file name generation algorithm. */
+    pid = getpid ();
+    safe_gethostname (hostname, sizeof (hostname));
+    gettimeofday (&tv, NULL);
+
+    filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
+                               (long) tv.tv_sec, (long) tv.tv_usec, pid, hostname);
+    if (! filename)
+       fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+
+    return filename;
+}
+
+/*
+ * Create a unique temporary file in maildir/tmp, return fd and full
+ * path to file in *path_out, or -1 on errors (in which case *path_out
+ * is not touched).
+ */
+static int
+maildir_mktemp (const void *ctx, const char *maildir, bool world_readable, char **path_out)
+{
+    char *filename, *path;
+    int fd;
+    const int mode = (world_readable ? 0644 : 0600);
+
+    do {
+       filename = tempfilename (ctx);
+       if (! filename)
+           return -1;
+
+       path = talloc_asprintf (ctx, "%s/tmp/%s", maildir, filename);
+       if (! path) {
+           fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+           return -1;
+       }
+
+       fd = open (path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
+    } while (fd == -1 && errno == EEXIST);
+
+    if (fd == -1) {
+       fprintf (stderr, "Error: open '%s': %s\n", path, strerror (errno));
+       return -1;
+    }
+
+    *path_out = path;
+
+    return fd;
+}
+
+/*
+ * Copy fdin to fdout, return true on success, and false on errors and
+ * empty input.
+ */
+static bool
+copy_fd (int fdout, int fdin)
+{
+    bool empty = true;
+
+    while (! interrupted) {
+       ssize_t remain;
+       char buf[4096];
+       char *p;
+
+       remain = read (fdin, buf, sizeof (buf));
+       if (remain == 0)
+           break;
+       if (remain < 0) {
+           if (errno == EINTR)
+               continue;
+           fprintf (stderr, "Error: reading from standard input: %s\n",
+                    strerror (errno));
+           return false;
+       }
+
+       p = buf;
+       do {
+           ssize_t written = write (fdout, p, remain);
+           if (written < 0 && errno == EINTR)
+               continue;
+           if (written <= 0) {
+               fprintf (stderr, "Error: writing to temporary file: %s",
+                        strerror (errno));
+               return false;
+           }
+           p += written;
+           remain -= written;
+           empty = false;
+       } while (remain > 0);
+    }
+
+    return (!interrupted && !empty);
+}
+
+/*
+ * Write fdin to a new temp file in maildir/tmp, return full path to
+ * the file, or NULL on errors.
+ */
+static char *
+maildir_write_tmp (const void *ctx, int fdin, const char *maildir, bool world_readable)
+{
+    char *path;
+    int fdout;
+
+    fdout = maildir_mktemp (ctx, maildir, world_readable, &path);
+    if (fdout < 0)
+       return NULL;
+
+    if (! copy_fd (fdout, fdin))
+       goto FAIL;
+
+    if (fsync (fdout)) {
+       fprintf (stderr, "Error: fsync '%s': %s\n", path, strerror (errno));
+       goto FAIL;
+    }
+
+    close (fdout);
+
+    return path;
+
+FAIL:
+    close (fdout);
+    unlink (path);
+
+    return NULL;
+}
+
+/*
+ * Write fdin to a new file in maildir/new, using an intermediate temp
+ * file in maildir/tmp, return full path to the new file, or NULL on
+ * errors.
+ */
+static char *
+maildir_write_new (const void *ctx, int fdin, const char *maildir, bool world_readable)
+{
+    char *cleanpath, *tmppath, *newpath, *newdir;
+
+    tmppath = maildir_write_tmp (ctx, fdin, maildir, world_readable);
+    if (! tmppath)
+       return NULL;
+    cleanpath = tmppath;
+
+    newpath = talloc_strdup (ctx, tmppath);
+    if (! newpath) {
+       fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+       goto FAIL;
+    }
+
+    /* sanity checks needed? */
+    memcpy (newpath + strlen (maildir) + 1, "new", 3);
+
+    if (rename (tmppath, newpath)) {
+       fprintf (stderr, "Error: rename '%s' '%s': %s\n",
+                tmppath, newpath, strerror (errno));
+       goto FAIL;
+    }
+    cleanpath = newpath;
+
+    newdir = talloc_asprintf (ctx, "%s/%s", maildir, "new");
+    if (! newdir) {
+       fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
+       goto FAIL;
+    }
+
+    if (! sync_dir (newdir))
+       goto FAIL;
+
+    return newpath;
+
+FAIL:
+    unlink (cleanpath);
+
+    return NULL;
+}
+
+/*
+ * Add the specified message file to the notmuch database, applying
+ * tags in tag_ops. If synchronize_flags is true, the tags are
+ * synchronized to maildir flags (which may result in message file
+ * rename).
+ *
+ * Return NOTMUCH_STATUS_SUCCESS on success, errors otherwise. If keep
+ * is true, errors in tag changes and flag syncing are ignored and
+ * success status is returned; otherwise such errors cause the message
+ * to be removed from the database. Failure to add the message to the
+ * database results in error status regardless of keep.
+ */
+static notmuch_status_t
+add_file (notmuch_database_t *notmuch, const char *path, tag_op_list_t *tag_ops,
+         bool synchronize_flags, bool keep,
+         notmuch_indexopts_t *indexopts)
+{
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    status = notmuch_database_index_file (notmuch, path, indexopts, &message);
+    if (status == NOTMUCH_STATUS_SUCCESS) {
+       status = tag_op_list_apply (message, tag_ops, 0);
+       if (status) {
+           fprintf (stderr, "%s: failed to apply tags to file '%s': %s\n",
+                    keep ? "Warning" : "Error",
+                    path, notmuch_status_to_string (status));
+           goto DONE;
+       }
+    } else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+       status = NOTMUCH_STATUS_SUCCESS;
+    } else if (status == NOTMUCH_STATUS_FILE_NOT_EMAIL) {
+       fprintf (stderr, "Error: delivery of non-mail file: '%s'\n", path);
+       goto FAIL;
+    } else {
+       fprintf (stderr, "Error: failed to add '%s' to notmuch database: %s\n",
+                path, notmuch_status_to_string (status));
+       goto FAIL;
+    }
+
+    if (synchronize_flags) {
+       status = notmuch_message_tags_to_maildir_flags (message);
+       if (status != NOTMUCH_STATUS_SUCCESS)
+           fprintf (stderr, "%s: failed to sync tags to maildir flags for '%s': %s\n",
+                    keep ? "Warning" : "Error",
+                    path, notmuch_status_to_string (status));
+
+       /*
+        * Note: Unfortunately a failed maildir flag sync might
+        * already have renamed the file, in which case the cleanup
+        * path may fail.
+        */
+    }
+
+  DONE:
+    notmuch_message_destroy (message);
+
+    if (status) {
+       if (keep) {
+           status = NOTMUCH_STATUS_SUCCESS;
+       } else {
+           notmuch_status_t cleanup_status;
+
+           cleanup_status = notmuch_database_remove_message (notmuch, path);
+           if (cleanup_status != NOTMUCH_STATUS_SUCCESS &&
+               cleanup_status != NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+               fprintf (stderr, "Warning: failed to remove '%s' from database "
+                        "after errors: %s. Please run 'notmuch new' to fix.\n",
+                        path, notmuch_status_to_string (cleanup_status));
+           }
+       }
+    }
+
+  FAIL:
+    return status;
+}
+
+int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_status_t status, close_status;
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    const char *db_path;
+    const char **new_tags;
+    size_t new_tags_length;
+    tag_op_list_t *tag_ops;
+    char *query_string = NULL;
+    const char *folder = "";
+    bool create_folder = false;
+    bool keep = false;
+    bool hooks = true;
+    bool world_readable = false;
+    bool synchronize_flags;
+    char *maildir;
+    char *newpath;
+    int opt_index;
+    unsigned int i;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_string = &folder, .name = "folder", .allow_empty = true },
+       { .opt_bool = &create_folder, .name = "create-folder" },
+       { .opt_bool = &keep, .name = "keep" },
+       { .opt_bool = &hooks, .name = "hooks" },
+       { .opt_bool = &world_readable, .name = "world-readable" },
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    db_path = notmuch_config_get_database_path (config);
+    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
+    synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
+
+    tag_ops = tag_op_list_create (config);
+    if (tag_ops == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return EXIT_FAILURE;
+    }
+    for (i = 0; i < new_tags_length; i++) {
+       const char *error_msg;
+
+       error_msg = illegal_tag (new_tags[i], false);
+       if (error_msg) {
+           fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
+                    new_tags[i],  error_msg);
+           return EXIT_FAILURE;
+       }
+
+       if (tag_op_list_append (tag_ops, new_tags[i], false))
+           return EXIT_FAILURE;
+    }
+
+    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+                               &query_string, tag_ops))
+       return EXIT_FAILURE;
+
+    if (*query_string != '\0') {
+       fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
+       return EXIT_FAILURE;
+    }
+
+    if (! is_valid_folder_name (folder)) {
+       fprintf (stderr, "Error: invalid folder name: '%s'\n", folder);
+       return EXIT_FAILURE;
+    }
+
+    maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
+    if (! maildir) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    strip_trailing (maildir, '/');
+    if (create_folder && ! maildir_create_folder (config, maildir, world_readable))
+       return EXIT_FAILURE;
+
+    /* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
+     * from standard input may be interrupted. */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = 0;
+    sigaction (SIGINT, &action, NULL);
+
+    /* Write the message to the Maildir new directory. */
+    newpath = maildir_write_new (config, STDIN_FILENO, maildir, world_readable);
+    if (! newpath) {
+       return EXIT_FAILURE;
+    }
+
+    status = notmuch_database_open (notmuch_config_get_database_path (config),
+                                   NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch);
+    if (status)
+       return keep ? NOTMUCH_STATUS_SUCCESS : status_to_exit (status);
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
+
+    /* Index the message. */
+    status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts);
+
+    /* Commit changes. */
+    close_status = notmuch_database_destroy (notmuch);
+    if (close_status) {
+       /* Hold on to the first error, if any. */
+       if (! status)
+           status = close_status;
+       fprintf (stderr, "%s: failed to commit database changes: %s\n",
+                keep ? "Warning" : "Error",
+                notmuch_status_to_string (close_status));
+    }
+
+    if (status) {
+       if (keep) {
+           status = NOTMUCH_STATUS_SUCCESS;
+       } else {
+           /* If maildir flag sync failed, this might fail. */
+           if (unlink (newpath)) {
+               fprintf (stderr, "Warning: failed to remove '%s' from maildir "
+                        "after errors: %s. Please run 'notmuch new' to fix.\n",
+                        newpath, strerror (errno));
+           }
+       }
+    }
+
+    if (hooks && status == NOTMUCH_STATUS_SUCCESS) {
+       /* Ignore hook failures. */
+       notmuch_run_hook (db_path, "post-insert");
+    }
+
+    return status_to_exit (status);
+}
diff --git a/notmuch-mutt.docs b/notmuch-mutt.docs
deleted file mode 100644 (file)
index f3d25cd..0000000
+++ /dev/null
@@ -1 +0,0 @@
-contrib/notmuch-mutt/README
diff --git a/notmuch-mutt.install b/notmuch-mutt.install
deleted file mode 100644 (file)
index 9b468bd..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-usr/bin/notmuch-mutt
-etc/Muttrc.d/notmuch-mutt.rc
diff --git a/notmuch-mutt.manpages b/notmuch-mutt.manpages
deleted file mode 100644 (file)
index a14ed69..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/share/man/man1/notmuch-mutt.1
diff --git a/notmuch-new.c b/notmuch-new.c
new file mode 100644 (file)
index 0000000..6a54a1a
--- /dev/null
@@ -0,0 +1,1298 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "tag-util.h"
+
+#include <unistd.h>
+
+typedef struct _filename_node {
+    char *filename;
+    time_t mtime;
+    struct _filename_node *next;
+} _filename_node_t;
+
+typedef struct _filename_list {
+    unsigned count;
+    _filename_node_t *head;
+    _filename_node_t **tail;
+} _filename_list_t;
+
+enum verbosity {
+    VERBOSITY_QUIET,
+    VERBOSITY_NORMAL,
+    VERBOSITY_VERBOSE,
+};
+
+typedef struct {
+    const char *db_path;
+
+    int output_is_a_tty;
+    enum verbosity verbosity;
+    bool debug;
+    bool full_scan;
+    const char **new_tags;
+    size_t new_tags_length;
+    const char **ignore_verbatim;
+    size_t ignore_verbatim_length;
+    regex_t *ignore_regex;
+    size_t ignore_regex_length;
+
+    int total_files;
+    int processed_files;
+    int added_messages, removed_messages, renamed_messages;
+    int vanished_files;
+    struct timeval tv_start;
+
+    _filename_list_t *removed_files;
+    _filename_list_t *removed_directories;
+    _filename_list_t *directory_mtimes;
+
+    bool synchronize_flags;
+} add_files_state_t;
+
+static volatile sig_atomic_t do_print_progress = 0;
+
+static void
+handle_sigalrm (unused (int signal))
+{
+    do_print_progress = 1;
+}
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof(msg)-1));
+    interrupted = 1;
+}
+
+static _filename_list_t *
+_filename_list_create (const void *ctx)
+{
+    _filename_list_t *list;
+
+    list = talloc (ctx, _filename_list_t);
+    if (list == NULL)
+       return NULL;
+
+    list->head = NULL;
+    list->tail = &list->head;
+    list->count = 0;
+
+    return list;
+}
+
+static _filename_node_t *
+_filename_list_add (_filename_list_t *list,
+                   const char *filename)
+{
+    _filename_node_t *node = talloc (list, _filename_node_t);
+
+    list->count++;
+
+    node->filename = talloc_strdup (list, filename);
+    node->next = NULL;
+
+    *(list->tail) = node;
+    list->tail = &node->next;
+
+    return node;
+}
+
+static void
+generic_print_progress (const char *action, const char *object,
+                       struct timeval tv_start, unsigned processed, unsigned total)
+{
+    struct timeval tv_now;
+    double elapsed_overall, rate_overall;
+
+    gettimeofday (&tv_now, NULL);
+
+    elapsed_overall = notmuch_time_elapsed (tv_start, tv_now);
+    rate_overall = processed / elapsed_overall;
+
+    printf ("%s %u ", action, processed);
+
+    if (total) {
+       printf ("of %u %s", total, object);
+       if (processed > 0 && elapsed_overall > 0.5) {
+           double time_remaining = ((total - processed) / rate_overall);
+           printf (" (");
+           notmuch_time_print_formatted_seconds (time_remaining);
+           printf (" remaining)");
+       }
+    } else {
+       printf ("%s", object);
+       if (elapsed_overall > 0.5)
+           printf (" (%d %s/sec.)", (int) rate_overall, object);
+    }
+    printf (".\033[K\r");
+
+    fflush (stdout);
+}
+
+static int
+dirent_sort_inode (const struct dirent **a, const struct dirent **b)
+{
+    return ((*a)->d_ino < (*b)->d_ino) ? -1 : 1;
+}
+
+static int
+dirent_sort_strcmp_name (const struct dirent **a, const struct dirent **b)
+{
+    return strcmp ((*a)->d_name, (*b)->d_name);
+}
+
+/* Return the type of a directory entry relative to path as a stat(2)
+ * mode.  Like stat, this follows symlinks.  Returns -1 and sets errno
+ * if the file's type cannot be determined (which includes dangling
+ * symlinks).
+ */
+static int
+dirent_type (const char *path, const struct dirent *entry)
+{
+    struct stat statbuf;
+    char *abspath;
+    int err, saved_errno;
+
+#if HAVE_D_TYPE
+    /* Mapping from d_type to stat mode_t.  We omit DT_LNK so that
+     * we'll fall through to stat and get the real file type. */
+    static const mode_t modes[] = {
+       [DT_BLK]  = S_IFBLK,
+       [DT_CHR]  = S_IFCHR,
+       [DT_DIR]  = S_IFDIR,
+       [DT_FIFO] = S_IFIFO,
+       [DT_REG]  = S_IFREG,
+       [DT_SOCK] = S_IFSOCK
+    };
+    if (entry->d_type < ARRAY_SIZE(modes) && modes[entry->d_type])
+       return modes[entry->d_type];
+#endif
+
+    abspath = talloc_asprintf (NULL, "%s/%s", path, entry->d_name);
+    if (!abspath) {
+       errno = ENOMEM;
+       return -1;
+    }
+    err = stat(abspath, &statbuf);
+    saved_errno = errno;
+    talloc_free (abspath);
+    if (err < 0) {
+       errno = saved_errno;
+       return -1;
+    }
+    return statbuf.st_mode & S_IFMT;
+}
+
+/* Test if the directory looks like a Maildir directory.
+ *
+ * Search through the array of directory entries to see if we can find all
+ * three subdirectories typical for Maildir, that is "new", "cur", and "tmp".
+ *
+ * Return 1 if the directory looks like a Maildir and 0 otherwise.
+ */
+static int
+_entries_resemble_maildir (const char *path, struct dirent **entries, int count)
+{
+    int i, found = 0;
+
+    for (i = 0; i < count; i++) {
+       if (dirent_type (path, entries[i]) != S_IFDIR)
+           continue;
+
+       if (strcmp(entries[i]->d_name, "new") == 0 ||
+           strcmp(entries[i]->d_name, "cur") == 0 ||
+           strcmp(entries[i]->d_name, "tmp") == 0)
+       {
+           found++;
+           if (found == 3)
+               return 1;
+       }
+    }
+
+    return 0;
+}
+
+static bool
+_special_directory (const char *entry)
+{
+    return strcmp (entry, ".") == 0 || strcmp (entry, "..") == 0;
+}
+
+static bool
+_setup_ignore (notmuch_config_t *config, add_files_state_t *state)
+{
+    const char **ignore_list, **ignore;
+    int nregex = 0, nverbatim = 0;
+    const char **verbatim = NULL;
+    regex_t *regex = NULL;
+
+    ignore_list = notmuch_config_get_new_ignore (config, NULL);
+    if (! ignore_list)
+       return true;
+
+    for (ignore = ignore_list; *ignore; ignore++) {
+       const char *s = *ignore;
+       size_t len = strlen (s);
+
+       if (len == 0) {
+           fprintf (stderr, "Error: Empty string in new.ignore list\n");
+           return false;
+       }
+
+       if (s[0] == '/') {
+           regex_t *preg;
+           char *r;
+           int rerr;
+
+           if (len < 3 || s[len - 1] != '/') {
+               fprintf (stderr, "Error: Malformed pattern '%s' in new.ignore\n",
+                        s);
+               return false;
+           }
+
+           r = talloc_strndup (config, s + 1, len - 2);
+           regex = talloc_realloc (config, regex, regex_t, nregex + 1);
+           preg = &regex[nregex];
+
+           rerr = regcomp (preg, r, REG_EXTENDED | REG_NOSUB);
+           if (rerr) {
+               size_t error_size = regerror (rerr, preg, NULL, 0);
+               char *error = talloc_size (r, error_size);
+
+               regerror (rerr, preg, error, error_size);
+
+               fprintf (stderr, "Error: Invalid regex '%s' in new.ignore: %s\n",
+                        r, error);
+               return false;
+           }
+           nregex++;
+
+           talloc_free (r);
+       } else {
+           verbatim = talloc_realloc (config, verbatim, const char *,
+                                      nverbatim + 1);
+           verbatim[nverbatim++] = s;
+       }
+    }
+
+    state->ignore_regex = regex;
+    state->ignore_regex_length = nregex;
+    state->ignore_verbatim = verbatim;
+    state->ignore_verbatim_length = nverbatim;
+
+    return true;
+}
+
+static char *
+_get_relative_path (const char *db_path, const char *dirpath, const char *entry)
+{
+    size_t db_path_len = strlen (db_path);
+
+    /* paranoia? */
+    if (strncmp (dirpath, db_path, db_path_len) != 0) {
+       fprintf (stderr, "Warning: '%s' is not a subdirectory of '%s'\n",
+                dirpath, db_path);
+       return NULL;
+    }
+
+    dirpath += db_path_len;
+    while (*dirpath == '/')
+       dirpath++;
+
+    if (*dirpath)
+       return talloc_asprintf (NULL, "%s/%s", dirpath, entry);
+    else
+       return talloc_strdup (NULL, entry);
+}
+
+/* Test if the file/directory is to be ignored.
+ */
+static bool
+_entry_in_ignore_list (add_files_state_t *state, const char *dirpath,
+                      const char *entry)
+{
+    bool ret = false;
+    size_t i;
+    char *path;
+
+    for (i = 0; i < state->ignore_verbatim_length; i++) {
+       if (strcmp (entry, state->ignore_verbatim[i]) == 0)
+           return true;
+    }
+
+    if (state->ignore_regex_length == 0)
+       return false;
+
+    path = _get_relative_path (state->db_path, dirpath, entry);
+    if (! path)
+       return false;
+
+    for (i = 0; i < state->ignore_regex_length; i++) {
+       if (regexec (&state->ignore_regex[i], path, 0, NULL, 0) == 0) {
+           ret = true;
+           break;
+       }
+    }
+
+    talloc_free (path);
+
+    return ret;
+}
+
+/* Add a single file to the database. */
+static notmuch_status_t
+add_file (notmuch_database_t *notmuch, const char *filename,
+         add_files_state_t *state)
+{
+    notmuch_message_t *message = NULL;
+    const char **tag;
+    notmuch_status_t status;
+
+    status = notmuch_database_begin_atomic (notmuch);
+    if (status)
+       goto DONE;
+
+    status = notmuch_database_index_file (notmuch, filename, indexing_cli_choices.opts, &message);
+    switch (status) {
+    /* Success. */
+    case NOTMUCH_STATUS_SUCCESS:
+       state->added_messages++;
+       notmuch_message_freeze (message);
+       if (state->synchronize_flags)
+           notmuch_message_maildir_flags_to_tags (message);
+
+       for (tag = state->new_tags; *tag != NULL; tag++) {
+           if (strcmp ("unread", *tag) !=0 ||
+               !notmuch_message_has_maildir_flag (message, 'S')) {
+               notmuch_message_add_tag (message, *tag);
+           }
+       }
+
+       notmuch_message_thaw (message);
+       break;
+    /* Non-fatal issues (go on to next file). */
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       if (state->synchronize_flags)
+           notmuch_message_maildir_flags_to_tags (message);
+       break;
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+       fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
+       break;
+    case NOTMUCH_STATUS_FILE_ERROR:
+       /* Someone renamed/removed the file between scandir and now. */
+       state->vanished_files++;
+       fprintf (stderr, "Unexpected error with file %s\n", filename);
+       (void) print_status_database ("add_file", notmuch, status);
+       break;
+    /* Fatal issues. Don't process anymore. */
+    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+       (void) print_status_database("add_file", notmuch, status);
+       goto DONE;
+    default:
+       INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
+       goto DONE;
+    }
+
+    status = notmuch_database_end_atomic (notmuch);
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return status;
+}
+
+/* Examine 'path' recursively as follows:
+ *
+ *   o Ask the filesystem for the mtime of 'path' (fs_mtime)
+ *   o Ask the database for its timestamp of 'path' (db_mtime)
+ *
+ *   o Ask the filesystem for files and directories within 'path'
+ *     (via scandir and stored in fs_entries)
+ *
+ *   o Pass 1: For each directory in fs_entries, recursively call into
+ *     this same function.
+ *
+ *   o Compare fs_mtime to db_mtime. If they are equivalent, terminate
+ *     the algorithm at this point, (this directory has not been
+ *     updated in the filesystem since the last database scan of PASS
+ *     2).
+ *
+ *   o Ask the database for files and directories within 'path'
+ *     (db_files and db_subdirs)
+ *
+ *   o Pass 2: Walk fs_entries simultaneously with db_files and
+ *     db_subdirs. Look for one of three interesting cases:
+ *
+ *        1. Regular file in fs_entries and not in db_files
+ *            This is a new file to add_message into the database.
+ *
+ *         2. Filename in db_files not in fs_entries.
+ *            This is a file that has been removed from the mail store.
+ *
+ *         3. Directory in db_subdirs not in fs_entries
+ *            This is a directory that has been removed from the mail store.
+ *
+ *     Note that the addition of a directory is not interesting here,
+ *     since that will have been taken care of in pass 1. Also, we
+ *     don't immediately act on file/directory removal since we must
+ *     ensure that in the case of a rename that the new filename is
+ *     added before the old filename is removed, (so that no
+ *     information is lost from the database).
+ *
+ *   o Tell the database to update its time of 'path' to 'fs_mtime'
+ *     if fs_mtime isn't the current wall-clock time.
+ */
+static notmuch_status_t
+add_files (notmuch_database_t *notmuch,
+          const char *path,
+          add_files_state_t *state)
+{
+    struct dirent *entry = NULL;
+    char *next = NULL;
+    time_t fs_mtime, db_mtime;
+    notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
+    struct dirent **fs_entries = NULL;
+    int i, num_fs_entries = 0, entry_type;
+    notmuch_directory_t *directory;
+    notmuch_filenames_t *db_files = NULL;
+    notmuch_filenames_t *db_subdirs = NULL;
+    time_t stat_time;
+    struct stat st;
+    bool is_maildir;
+
+    if (stat (path, &st)) {
+       fprintf (stderr, "Error reading directory %s: %s\n",
+                path, strerror (errno));
+       return NOTMUCH_STATUS_FILE_ERROR;
+    }
+    stat_time = time (NULL);
+
+    if (! S_ISDIR (st.st_mode)) {
+       fprintf (stderr, "Error: %s is not a directory.\n", path);
+       return NOTMUCH_STATUS_FILE_ERROR;
+    }
+
+    fs_mtime = st.st_mtime;
+
+    status = notmuch_database_get_directory (notmuch, path, &directory);
+    if (status) {
+       ret = status;
+       goto DONE;
+    }
+    db_mtime = directory ? notmuch_directory_get_mtime (directory) : 0;
+
+    /* If the directory is unchanged from our last scan and has no
+     * sub-directories, then return without scanning it at all.  In
+     * some situations, skipping the scan can substantially reduce the
+     * cost of notmuch new, especially since the huge numbers of files
+     * in Maildirs make scans expensive, but all files live in leaf
+     * directories.
+     *
+     * To check for sub-directories, we borrow a trick from find,
+     * kpathsea, and many other UNIX tools: since a directory's link
+     * count is the number of sub-directories (specifically, their
+     * '..' entries) plus 2 (the link from the parent and the link for
+     * '.').  This check is safe even on weird file systems, since
+     * file systems that can't compute this will return 0 or 1.  This
+     * is safe even on *really* weird file systems like HFS+ that
+     * mistakenly return the total number of directory entries, since
+     * that only inflates the count beyond 2.
+     */
+    if (directory && (! state->full_scan) && fs_mtime == db_mtime && st.st_nlink == 2) {
+       /* There's one catch: pass 1 below considers symlinks to
+        * directories to be directories, but these don't increase the
+        * file system link count.  So, only bail early if the
+        * database agrees that there are no sub-directories. */
+       db_subdirs = notmuch_directory_get_child_directories (directory);
+       if (!notmuch_filenames_valid (db_subdirs))
+           goto DONE;
+       notmuch_filenames_destroy (db_subdirs);
+       db_subdirs = NULL;
+    }
+
+    /* If the database knows about this directory, then we sort based
+     * on strcmp to match the database sorting. Otherwise, we can do
+     * inode-based sorting for faster filesystem operation. */
+    num_fs_entries = scandir (path, &fs_entries, 0,
+                             directory ?
+                             dirent_sort_strcmp_name : dirent_sort_inode);
+
+    if (num_fs_entries == -1) {
+       fprintf (stderr, "Error opening directory %s: %s\n",
+                path, strerror (errno));
+       /* We consider this a fatal error because, if a user moved a
+        * message from another directory that we were able to scan
+        * into this directory, skipping this directory will cause
+        * that message to be lost. */
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    /* Pass 1: Recurse into all sub-directories. */
+    is_maildir = _entries_resemble_maildir (path, fs_entries, num_fs_entries);
+
+    for (i = 0; i < num_fs_entries && ! interrupted; i++) {
+       entry = fs_entries[i];
+
+       /* Ignore special directories to avoid infinite recursion. */
+       if (_special_directory (entry->d_name))
+           continue;
+
+       /* Ignore any files/directories the user has configured to
+        * ignore.  We do this before dirent_type both for performance
+        * and because we don't care if dirent_type fails on entries
+        * that are explicitly ignored.
+        */
+       if (_entry_in_ignore_list (state, path, entry->d_name)) {
+           if (state->debug)
+               printf ("(D) add_files, pass 1: explicitly ignoring %s/%s\n",
+                       path, entry->d_name);
+           continue;
+       }
+
+       /* We only want to descend into directories (and symlinks to
+        * directories). */
+       entry_type = dirent_type (path, entry);
+       if (entry_type == -1) {
+           /* Be pessimistic, e.g. so we don't lose lots of mail just
+            * because a user broke a symlink. */
+           fprintf (stderr, "Error reading file %s/%s: %s\n",
+                    path, entry->d_name, strerror (errno));
+           return NOTMUCH_STATUS_FILE_ERROR;
+       } else if (entry_type != S_IFDIR) {
+           continue;
+       }
+
+       /* Ignore the .notmuch directory and any "tmp" directory
+        * that appears within a maildir.
+        */
+       if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
+           strcmp (entry->d_name, ".notmuch") == 0)
+           continue;
+
+       next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
+       status = add_files (notmuch, next, state);
+       if (status) {
+           ret = status;
+           goto DONE;
+       }
+       talloc_free (next);
+       next = NULL;
+    }
+
+    /* If the directory's modification time in the filesystem is the
+     * same as what we recorded in the database the last time we
+     * scanned it, then we can skip the second pass entirely.
+     *
+     * We test for strict equality here to avoid a bug that can happen
+     * if the system clock jumps backward, (preventing new mail from
+     * being discovered until the clock catches up and the directory
+     * is modified again).
+     */
+    if (directory && (! state->full_scan) && fs_mtime == db_mtime)
+       goto DONE;
+
+    /* If the database has never seen this directory before, we can
+     * simply leave db_files and db_subdirs NULL. */
+    if (directory) {
+       db_files = notmuch_directory_get_child_files (directory);
+       db_subdirs = notmuch_directory_get_child_directories (directory);
+    }
+
+    /* Pass 2: Scan for new files, removed files, and removed directories. */
+    for (i = 0; i < num_fs_entries && ! interrupted; i++) {
+        entry = fs_entries[i];
+
+       /* Ignore special directories early. */
+       if (_special_directory (entry->d_name))
+           continue;
+
+       /* Ignore files & directories user has configured to be ignored */
+       if (_entry_in_ignore_list (state, path, entry->d_name)) {
+           if (state->debug)
+               printf ("(D) add_files, pass 2: explicitly ignoring %s/%s\n",
+                       path, entry->d_name);
+           continue;
+       }
+
+       /* Check if we've walked past any names in db_files or
+        * db_subdirs. If so, these have been deleted. */
+       while (notmuch_filenames_valid (db_files) &&
+              strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0)
+       {
+           char *absolute = talloc_asprintf (state->removed_files,
+                                             "%s/%s", path,
+                                             notmuch_filenames_get (db_files));
+
+           if (state->debug)
+               printf ("(D) add_files, pass 2: queuing passed file %s for deletion from database\n",
+                       absolute);
+
+           _filename_list_add (state->removed_files, absolute);
+
+           notmuch_filenames_move_to_next (db_files);
+       }
+
+       while (notmuch_filenames_valid (db_subdirs) &&
+              strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0)
+       {
+           const char *filename = notmuch_filenames_get (db_subdirs);
+
+           if (strcmp (filename, entry->d_name) < 0)
+           {
+               char *absolute = talloc_asprintf (state->removed_directories,
+                                                 "%s/%s", path, filename);
+               if (state->debug)
+                   printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
+                       absolute);
+
+               _filename_list_add (state->removed_directories, absolute);
+           }
+
+           notmuch_filenames_move_to_next (db_subdirs);
+       }
+
+       /* Only add regular files (and symlinks to regular files). */
+       entry_type = dirent_type (path, entry);
+       if (entry_type == -1) {
+           fprintf (stderr, "Error reading file %s/%s: %s\n",
+                    path, entry->d_name, strerror (errno));
+           return NOTMUCH_STATUS_FILE_ERROR;
+       } else if (entry_type != S_IFREG) {
+           continue;
+       }
+
+       /* Don't add a file that we've added before. */
+       if (notmuch_filenames_valid (db_files) &&
+           strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0)
+       {
+           notmuch_filenames_move_to_next (db_files);
+           continue;
+       }
+
+       /* We're now looking at a regular file that doesn't yet exist
+        * in the database, so add it. */
+       next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
+
+       state->processed_files++;
+
+       if (state->verbosity >= VERBOSITY_VERBOSE) {
+           if (state->output_is_a_tty)
+               printf("\r\033[K");
+
+           printf ("%i/%i: %s", state->processed_files, state->total_files,
+                   next);
+
+           putchar((state->output_is_a_tty) ? '\r' : '\n');
+           fflush (stdout);
+       }
+
+       status = add_file (notmuch, next, state);
+       if (status) {
+           ret = status;
+           goto DONE;
+       }
+
+       if (do_print_progress) {
+           do_print_progress = 0;
+           generic_print_progress ("Processed", "files", state->tv_start,
+                                   state->processed_files, state->total_files);
+       }
+
+       talloc_free (next);
+       next = NULL;
+    }
+
+    if (interrupted)
+       goto DONE;
+
+    /* Now that we've walked the whole filesystem list, anything left
+     * over in the database lists has been deleted. */
+    while (notmuch_filenames_valid (db_files))
+    {
+       char *absolute = talloc_asprintf (state->removed_files,
+                                         "%s/%s", path,
+                                         notmuch_filenames_get (db_files));
+       if (state->debug)
+           printf ("(D) add_files, pass 3: queuing leftover file %s for deletion from database\n",
+                   absolute);
+
+       _filename_list_add (state->removed_files, absolute);
+
+       notmuch_filenames_move_to_next (db_files);
+    }
+
+    while (notmuch_filenames_valid (db_subdirs))
+    {
+       char *absolute = talloc_asprintf (state->removed_directories,
+                                         "%s/%s", path,
+                                         notmuch_filenames_get (db_subdirs));
+
+       if (state->debug)
+           printf ("(D) add_files, pass 3: queuing leftover directory %s for deletion from database\n",
+                   absolute);
+
+       _filename_list_add (state->removed_directories, absolute);
+
+       notmuch_filenames_move_to_next (db_subdirs);
+    }
+
+    /* If the directory's mtime is the same as the wall-clock time
+     * when we stat'ed the directory, we skip updating the mtime in
+     * the database because a message could be delivered later in this
+     * same second.  This may lead to unnecessary re-scans, but it
+     * avoids overlooking messages. */
+    if (fs_mtime != stat_time)
+       _filename_list_add (state->directory_mtimes, path)->mtime = fs_mtime;
+
+  DONE:
+    if (next)
+       talloc_free (next);
+    if (fs_entries) {
+       for (i = 0; i < num_fs_entries; i++)
+           free (fs_entries[i]);
+
+       free (fs_entries);
+    }
+    if (db_subdirs)
+       notmuch_filenames_destroy (db_subdirs);
+    if (db_files)
+       notmuch_filenames_destroy (db_files);
+    if (directory)
+       notmuch_directory_destroy (directory);
+
+    return ret;
+}
+
+static void
+setup_progress_printing_timer (void)
+{
+    struct sigaction action;
+    struct itimerval timerval;
+
+    /* Set up our handler for SIGALRM */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigalrm;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = SA_RESTART;
+    sigaction (SIGALRM, &action, NULL);
+
+    /* Then start a timer to send SIGALRM once per second. */
+    timerval.it_interval.tv_sec = 1;
+    timerval.it_interval.tv_usec = 0;
+    timerval.it_value.tv_sec = 1;
+    timerval.it_value.tv_usec = 0;
+    setitimer (ITIMER_REAL, &timerval, NULL);
+}
+
+static void
+stop_progress_printing_timer (void)
+{
+    struct sigaction action;
+    struct itimerval timerval;
+
+    /* Now stop the timer. */
+    timerval.it_interval.tv_sec = 0;
+    timerval.it_interval.tv_usec = 0;
+    timerval.it_value.tv_sec = 0;
+    timerval.it_value.tv_usec = 0;
+    setitimer (ITIMER_REAL, &timerval, NULL);
+
+    /* And disable the signal handler. */
+    action.sa_handler = SIG_IGN;
+    sigaction (SIGALRM, &action, NULL);
+}
+
+
+/* XXX: This should be merged with the add_files function since it
+ * shares a lot of logic with it. */
+/* Recursively count all regular files in path and all sub-directories
+ * of path.  The result is added to *count (which should be
+ * initialized to zero by the top-level caller before calling
+ * count_files). */
+static void
+count_files (const char *path, int *count, add_files_state_t *state)
+{
+    struct dirent *entry = NULL;
+    char *next;
+    struct dirent **fs_entries = NULL;
+    int num_fs_entries = scandir (path, &fs_entries, 0, dirent_sort_inode);
+    int entry_type, i;
+
+    if (num_fs_entries == -1) {
+       fprintf (stderr, "Warning: failed to open directory %s: %s\n",
+                path, strerror (errno));
+       goto DONE;
+    }
+
+    for (i = 0; i < num_fs_entries && ! interrupted; i++) {
+        entry = fs_entries[i];
+
+       /* Ignore special directories to avoid infinite recursion.
+        * Also ignore the .notmuch directory.
+        */
+       if (_special_directory (entry->d_name) ||
+           strcmp (entry->d_name, ".notmuch") == 0)
+           continue;
+
+       /* Ignore any files/directories the user has configured to be
+        * ignored
+        */
+       if (_entry_in_ignore_list (state, path, entry->d_name)) {
+           if (state->debug)
+               printf ("(D) count_files: explicitly ignoring %s/%s\n",
+                       path, entry->d_name);
+           continue;
+       }
+
+       if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
+           next = NULL;
+           fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
+                    path, entry->d_name);
+           continue;
+       }
+
+       entry_type = dirent_type (path, entry);
+       if (entry_type == S_IFREG) {
+           *count = *count + 1;
+           if (*count % 1000 == 0 && state->verbosity >= VERBOSITY_NORMAL) {
+               printf ("Found %d files so far.\r", *count);
+               fflush (stdout);
+           }
+       } else if (entry_type == S_IFDIR) {
+           count_files (next, count, state);
+       }
+
+       free (next);
+    }
+
+  DONE:
+    if (fs_entries) {
+       for (i = 0; i < num_fs_entries; i++)
+           free (fs_entries[i]);
+
+        free (fs_entries);
+    }
+}
+
+static void
+upgrade_print_progress (void *closure,
+                       double progress)
+{
+    add_files_state_t *state = closure;
+
+    printf ("Upgrading database: %.2f%% complete", progress * 100.0);
+
+    if (progress > 0) {
+       struct timeval tv_now;
+       double elapsed, time_remaining;
+
+       gettimeofday (&tv_now, NULL);
+
+       elapsed = notmuch_time_elapsed (state->tv_start, tv_now);
+       time_remaining = (elapsed / progress) * (1.0 - progress);
+       printf (" (");
+       notmuch_time_print_formatted_seconds (time_remaining);
+       printf (" remaining)");
+    }
+
+    printf (".      \r");
+
+    fflush (stdout);
+}
+
+/* Remove one message filename from the database. */
+static notmuch_status_t
+remove_filename (notmuch_database_t *notmuch,
+                const char *path,
+                add_files_state_t *add_files_state)
+{
+    notmuch_status_t status;
+    notmuch_message_t *message;
+    status = notmuch_database_begin_atomic (notmuch);
+    if (status)
+       return status;
+    status = notmuch_database_find_message_by_filename (notmuch, path, &message);
+    if (status || message == NULL)
+       goto DONE;
+
+    status = notmuch_database_remove_message (notmuch, path);
+    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+       add_files_state->renamed_messages++;
+       if (add_files_state->synchronize_flags == true)
+           notmuch_message_maildir_flags_to_tags (message);
+       status = NOTMUCH_STATUS_SUCCESS;
+    } else if (status == NOTMUCH_STATUS_SUCCESS) {
+       add_files_state->removed_messages++;
+    }
+    notmuch_message_destroy (message);
+
+  DONE:
+    notmuch_database_end_atomic (notmuch);
+    return status;
+}
+
+/* Recursively remove all filenames from the database referring to
+ * 'path' (or to any of its children). */
+static notmuch_status_t
+_remove_directory (void *ctx,
+                  notmuch_database_t *notmuch,
+                  const char *path,
+                  add_files_state_t *add_files_state)
+{
+    notmuch_status_t status;
+    notmuch_directory_t *directory;
+    notmuch_filenames_t *files, *subdirs;
+    char *absolute;
+
+    status = notmuch_database_get_directory (notmuch, path, &directory);
+    if (status || !directory)
+       return status;
+
+    for (files = notmuch_directory_get_child_files (directory);
+        notmuch_filenames_valid (files);
+        notmuch_filenames_move_to_next (files))
+    {
+       absolute = talloc_asprintf (ctx, "%s/%s", path,
+                                   notmuch_filenames_get (files));
+       status = remove_filename (notmuch, absolute, add_files_state);
+       talloc_free (absolute);
+       if (status)
+           goto DONE;
+    }
+
+    for (subdirs = notmuch_directory_get_child_directories (directory);
+        notmuch_filenames_valid (subdirs);
+        notmuch_filenames_move_to_next (subdirs))
+    {
+       absolute = talloc_asprintf (ctx, "%s/%s", path,
+                                   notmuch_filenames_get (subdirs));
+       status = _remove_directory (ctx, notmuch, absolute, add_files_state);
+       talloc_free (absolute);
+       if (status)
+           goto DONE;
+    }
+
+    status = notmuch_directory_delete (directory);
+
+  DONE:
+    if (status)
+       notmuch_directory_destroy (directory);
+    return status;
+}
+
+static void
+print_results (const add_files_state_t *state)
+{
+    double elapsed;
+    struct timeval tv_now;
+
+    gettimeofday (&tv_now, NULL);
+    elapsed = notmuch_time_elapsed (state->tv_start, tv_now);
+
+    if (state->processed_files) {
+       printf ("Processed %d %s in ", state->processed_files,
+               state->processed_files == 1 ? "file" : "total files");
+       notmuch_time_print_formatted_seconds (elapsed);
+       if (elapsed > 1)
+           printf (" (%d files/sec.)",
+                   (int) (state->processed_files / elapsed));
+       printf (".%s\n", (state->output_is_a_tty) ? "\033[K" : "");
+    }
+
+    if (state->added_messages)
+       printf ("Added %d new %s to the database.", state->added_messages,
+               state->added_messages == 1 ? "message" : "messages");
+    else
+       printf ("No new mail.");
+
+    if (state->removed_messages)
+       printf (" Removed %d %s.", state->removed_messages,
+               state->removed_messages == 1 ? "message" : "messages");
+
+    if (state->renamed_messages)
+       printf (" Detected %d file %s.", state->renamed_messages,
+               state->renamed_messages == 1 ? "rename" : "renames");
+
+    printf ("\n");
+}
+
+int
+notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    add_files_state_t add_files_state = {
+       .verbosity = VERBOSITY_NORMAL,
+       .debug = false,
+       .full_scan = false,
+       .output_is_a_tty = isatty (fileno (stdout)),
+    };
+    struct timeval tv_start;
+    int ret = 0;
+    struct stat st;
+    const char *db_path;
+    char *dot_notmuch_path;
+    struct sigaction action;
+    _filename_node_t *f;
+    int opt_index;
+    unsigned int i;
+    bool timer_is_active = false;
+    bool hooks = true;
+    bool quiet = false, verbose = false;
+    notmuch_status_t status;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_bool = &quiet, .name = "quiet" },
+       { .opt_bool = &verbose, .name = "verbose" },
+       { .opt_bool = &add_files_state.debug, .name = "debug" },
+       { .opt_bool = &add_files_state.full_scan, .name = "full-scan" },
+       { .opt_bool = &hooks, .name = "hooks" },
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    /* quiet trumps verbose */
+    if (quiet)
+       add_files_state.verbosity = VERBOSITY_QUIET;
+    else if (verbose)
+       add_files_state.verbosity = VERBOSITY_VERBOSE;
+
+    add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length);
+    add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
+    db_path = notmuch_config_get_database_path (config);
+    add_files_state.db_path = db_path;
+
+    if (! _setup_ignore (config, &add_files_state))
+       return EXIT_FAILURE;
+
+    for (i = 0; i < add_files_state.new_tags_length; i++) {
+       const char *error_msg;
+
+       error_msg = illegal_tag (add_files_state.new_tags[i], false);
+       if (error_msg) {
+           fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
+                    add_files_state.new_tags[i], error_msg);
+           return EXIT_FAILURE;
+       }
+    }
+
+    if (hooks) {
+       ret = notmuch_run_hook (db_path, "pre-new");
+       if (ret)
+           return EXIT_FAILURE;
+    }
+
+    dot_notmuch_path = talloc_asprintf (config, "%s/%s", db_path, ".notmuch");
+
+    if (stat (dot_notmuch_path, &st)) {
+       int count;
+
+       count = 0;
+       count_files (db_path, &count, &add_files_state);
+       if (interrupted)
+           return EXIT_FAILURE;
+
+       if (add_files_state.verbosity >= VERBOSITY_NORMAL)
+           printf ("Found %d total files (that's not much mail).\n", count);
+       if (notmuch_database_create (db_path, &notmuch))
+           return EXIT_FAILURE;
+       add_files_state.total_files = count;
+    } else {
+       char *status_string = NULL;
+       if (notmuch_database_open_verbose (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                          &notmuch, &status_string)) {
+           if (status_string) {
+               fputs (status_string, stderr);
+               free (status_string);
+           }
+           return EXIT_FAILURE;
+       }
+
+       notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+       if (notmuch_database_needs_upgrade (notmuch)) {
+           time_t now = time (NULL);
+           struct tm *gm_time = gmtime (&now);
+
+           /* since dump files are written atomically, the amount of
+            * harm from overwriting one within a second seems
+            * relatively small. */
+
+           const char *backup_name =
+               talloc_asprintf (notmuch, "%s/dump-%04d%02d%02dT%02d%02d%02d.gz",
+                                dot_notmuch_path,
+                                gm_time->tm_year + 1900,
+                                gm_time->tm_mon + 1,
+                                gm_time->tm_mday,
+                                gm_time->tm_hour,
+                                gm_time->tm_min,
+                                gm_time->tm_sec);
+
+           if (add_files_state.verbosity >= VERBOSITY_NORMAL) {
+               printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
+               printf ("This process is safe to interrupt.\n");
+               printf ("Backing up tags to %s...\n", backup_name);
+           }
+
+           if (notmuch_database_dump (notmuch, backup_name, "",
+                                      DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, true)) {
+               fprintf (stderr, "Backup failed. Aborting upgrade.");
+               return EXIT_FAILURE;
+           }
+
+           gettimeofday (&add_files_state.tv_start, NULL);
+           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.\n");
+       }
+
+       add_files_state.total_files = 0;
+    }
+
+    if (notmuch == NULL)
+       return EXIT_FAILURE;
+
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
+
+    /* Set up our handler for SIGINT. We do this after having
+     * potentially done a database upgrade we this interrupt handler
+     * won't support. */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = SA_RESTART;
+    sigaction (SIGINT, &action, NULL);
+
+    talloc_free (dot_notmuch_path);
+    dot_notmuch_path = NULL;
+
+    gettimeofday (&add_files_state.tv_start, NULL);
+
+    add_files_state.removed_files = _filename_list_create (config);
+    add_files_state.removed_directories = _filename_list_create (config);
+    add_files_state.directory_mtimes = _filename_list_create (config);
+
+    if (add_files_state.verbosity == VERBOSITY_NORMAL &&
+       add_files_state.output_is_a_tty && ! debugger_is_active ()) {
+       setup_progress_printing_timer ();
+       timer_is_active = true;
+    }
+
+    ret = add_files (notmuch, db_path, &add_files_state);
+    if (ret)
+       goto DONE;
+
+    gettimeofday (&tv_start, NULL);
+    for (f = add_files_state.removed_files->head; f && !interrupted; f = f->next) {
+       ret = remove_filename (notmuch, f->filename, &add_files_state);
+       if (ret)
+           goto DONE;
+       if (do_print_progress) {
+           do_print_progress = 0;
+           generic_print_progress ("Cleaned up", "messages",
+               tv_start, add_files_state.removed_messages + add_files_state.renamed_messages,
+               add_files_state.removed_files->count);
+       }
+    }
+
+    gettimeofday (&tv_start, NULL);
+    for (f = add_files_state.removed_directories->head, i = 0; f && !interrupted; f = f->next, i++) {
+       ret = _remove_directory (config, notmuch, f->filename, &add_files_state);
+       if (ret)
+           goto DONE;
+       if (do_print_progress) {
+           do_print_progress = 0;
+           generic_print_progress ("Cleaned up", "directories",
+               tv_start, i,
+               add_files_state.removed_directories->count);
+       }
+    }
+
+    for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) {
+       notmuch_directory_t *directory;
+       status = notmuch_database_get_directory (notmuch, f->filename, &directory);
+       if (status == NOTMUCH_STATUS_SUCCESS && directory) {
+           notmuch_directory_set_mtime (directory, f->mtime);
+           notmuch_directory_destroy (directory);
+       }
+    }
+
+  DONE:
+    talloc_free (add_files_state.removed_files);
+    talloc_free (add_files_state.removed_directories);
+    talloc_free (add_files_state.directory_mtimes);
+
+    if (timer_is_active)
+       stop_progress_printing_timer ();
+
+    if (add_files_state.verbosity >= VERBOSITY_NORMAL)
+       print_results (&add_files_state);
+
+    if (ret)
+       fprintf (stderr, "Note: A fatal error was encountered: %s\n",
+                notmuch_status_to_string (ret));
+
+    notmuch_database_destroy (notmuch);
+
+    if (hooks && !ret && !interrupted)
+       ret = notmuch_run_hook (db_path, "post-new");
+
+    if (ret || interrupted)
+       return EXIT_FAILURE;
+
+    if (add_files_state.vanished_files)
+       return NOTMUCH_EXIT_TEMPFAIL;
+
+    return EXIT_SUCCESS;
+}
diff --git a/notmuch-reindex.c b/notmuch-reindex.c
new file mode 100644 (file)
index 0000000..d858912
--- /dev/null
@@ -0,0 +1,142 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2016 Daniel Kahn Gillmor
+ *
+ * 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/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-client.h"
+#include "string-util.h"
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+/* reindex all messages matching 'query_string' using the passed-in indexopts
+ */
+static int
+reindex_query (notmuch_database_t *notmuch, const char *query_string,
+              notmuch_indexopts_t *indexopts)
+{
+    notmuch_query_t *query;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return 1;
+    }
+
+    /* reindexing is not interested in any special sort order */
+    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch reindex", query, status))
+       return status;
+
+    ret = notmuch_database_begin_atomic (notmuch);
+    for (;
+        notmuch_messages_valid (messages) && ! interrupted;
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+
+       ret = notmuch_message_reindex(message, indexopts);
+       if (ret != NOTMUCH_STATUS_SUCCESS)
+           break;
+    }
+
+    if (!ret)
+       ret = notmuch_database_end_atomic (notmuch);
+
+    notmuch_query_destroy (query);
+
+    return ret || interrupted;
+}
+
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    char *query_string = NULL;
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    int opt_index;
+    int ret;
+    notmuch_status_t status;
+
+    /* Set up our handler for SIGINT */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = SA_RESTART;
+    sigaction (SIGINT, &action, NULL);
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_inherit = notmuch_shared_indexing_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    status = notmuch_process_shared_indexing_options (notmuch, config);
+    if (status != NOTMUCH_STATUS_SUCCESS) {
+       fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+                notmuch_status_to_string (status));
+       return EXIT_FAILURE;
+    }
+
+    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    if (query_string == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    if (*query_string == '\0') {
+       fprintf (stderr, "Error: notmuch reindex requires at least one search term.\n");
+       return EXIT_FAILURE;
+    }
+    
+    ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts);
+
+    notmuch_database_destroy (notmuch);
+
+    return ret || interrupted ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-reply.c b/notmuch-reply.c
new file mode 100644 (file)
index 0000000..75cf7ec
--- /dev/null
@@ -0,0 +1,773 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * 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: Carl Worth <cworth@cworth.org>
+ *         Keith Packard <keithp@keithp.com>
+ */
+
+#include "notmuch-client.h"
+#include "string-util.h"
+#include "sprinter.h"
+
+static void
+show_reply_headers (GMimeStream *stream, GMimeMessage *message)
+{
+    /* Output RFC 2822 formatted (and RFC 2047 encoded) headers. */
+    if (g_mime_object_write_to_stream (GMIME_OBJECT(message), stream) < 0) {
+       INTERNAL_ERROR("failed to write headers to stdout\n");
+    }
+}
+
+static void
+format_part_reply (GMimeStream *stream, mime_node_t *node)
+{
+    int i;
+
+    if (node->envelope_file) {
+       g_mime_stream_printf (stream, "On %s, %s wrote:\n",
+                             notmuch_message_get_header (node->envelope_file, "date"),
+                             notmuch_message_get_header (node->envelope_file, "from"));
+    } else if (GMIME_IS_MESSAGE (node->part)) {
+       GMimeMessage *message = GMIME_MESSAGE (node->part);
+       char *recipients_string;
+
+       g_mime_stream_printf (stream, "> From: %s\n", g_mime_message_get_from_string (message));
+       recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO);
+       if (recipients_string)
+           g_mime_stream_printf (stream, "> To: %s\n",
+                                 recipients_string);
+       g_free (recipients_string);
+       recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC);
+       if (recipients_string)
+           g_mime_stream_printf (stream, "> Cc: %s\n",
+                                 recipients_string);
+       g_free (recipients_string);
+       g_mime_stream_printf (stream, "> Subject: %s\n", g_mime_message_get_subject (message));
+       g_mime_stream_printf (stream, "> Date: %s\n", g_mime_message_get_date_string (node, message));
+       g_mime_stream_printf (stream, ">\n");
+    } else if (GMIME_IS_PART (node->part)) {
+       GMimeContentType *content_type = g_mime_object_get_content_type (node->part);
+       GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (node->part);
+
+       if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted") ||
+           g_mime_content_type_is_type (content_type, "application", "pgp-signature")) {
+           /* Ignore PGP/MIME cruft parts */
+       } else if (g_mime_content_type_is_type (content_type, "text", "*") &&
+                  !g_mime_content_type_is_type (content_type, "text", "html")) {
+           show_text_part_content (node->part, stream, NOTMUCH_SHOW_TEXT_PART_REPLY);
+       } else if (disposition &&
+                  strcasecmp (g_mime_content_disposition_get_disposition (disposition),
+                              GMIME_DISPOSITION_ATTACHMENT) == 0) {
+           const char *filename = g_mime_part_get_filename (GMIME_PART (node->part));
+           g_mime_stream_printf (stream, "Attachment: %s (%s)\n", filename,
+                                 g_mime_content_type_to_string (content_type));
+       } else {
+           g_mime_stream_printf (stream, "Non-text part: %s\n",
+                                 g_mime_content_type_to_string (content_type));
+       }
+    }
+
+    for (i = 0; i < node->nchildren; i++)
+       format_part_reply (stream, mime_node_child (node, i));
+}
+
+typedef enum {
+    USER_ADDRESS_IN_STRING,
+    STRING_IN_USER_ADDRESS,
+    STRING_IS_USER_ADDRESS,
+} address_match_t;
+
+/* Match given string against given address according to mode. */
+static bool
+match_address (const char *str, const char *address, address_match_t mode)
+{
+    switch (mode) {
+    case USER_ADDRESS_IN_STRING:
+       return strcasestr (str, address) != NULL;
+    case STRING_IN_USER_ADDRESS:
+       return strcasestr (address, str) != NULL;
+    case STRING_IS_USER_ADDRESS:
+       return strcasecmp (address, str) == 0;
+    }
+
+    return false;
+}
+
+/* Match given string against user's configured "primary" and "other"
+ * addresses according to mode. */
+static const char *
+address_match (const char *str, notmuch_config_t *config, address_match_t mode)
+{
+    const char *primary;
+    const char **other;
+    size_t i, other_len;
+
+    if (!str || *str == '\0')
+       return NULL;
+
+    primary = notmuch_config_get_user_primary_email (config);
+    if (match_address (str, primary, mode))
+       return primary;
+
+    other = notmuch_config_get_user_other_email (config, &other_len);
+    for (i = 0; i < other_len; i++) {
+       if (match_address (str, other[i], mode))
+           return other[i];
+    }
+
+    return NULL;
+}
+
+/* Does the given string contain an address configured as one of the
+ * user's "primary" or "other" addresses. If so, return the matching
+ * address, NULL otherwise. */
+static const char *
+user_address_in_string (const char *str, notmuch_config_t *config)
+{
+    return address_match (str, config, USER_ADDRESS_IN_STRING);
+}
+
+/* Do any of the addresses configured as one of the user's "primary"
+ * or "other" addresses contain the given string. If so, return the
+ * matching address, NULL otherwise. */
+static const char *
+string_in_user_address (const char *str, notmuch_config_t *config)
+{
+    return address_match (str, config, STRING_IN_USER_ADDRESS);
+}
+
+/* Is the given address configured as one of the user's "primary" or
+ * "other" addresses. */
+static bool
+address_is_users (const char *address, notmuch_config_t *config)
+{
+    return address_match (address, config, STRING_IS_USER_ADDRESS) != NULL;
+}
+
+/* Scan addresses in 'list'.
+ *
+ * If 'message' is non-NULL, then for each address in 'list' that is
+ * not configured as one of the user's addresses in 'config', add that
+ * address to 'message' as an address of 'type'.
+ *
+ * If 'user_from' is non-NULL and *user_from is NULL, *user_from will
+ * be set to the first address encountered in 'list' that is the
+ * user's address.
+ *
+ * Return the number of addresses added to 'message'. (If 'message' is
+ * NULL, the function returns 0 by definition.)
+ */
+static unsigned int
+scan_address_list (InternetAddressList *list,
+                  notmuch_config_t *config,
+                  GMimeMessage *message,
+                  GMimeRecipientType type,
+                  const char **user_from)
+{
+    InternetAddress *address;
+    int i;
+    unsigned int n = 0;
+
+    if (list == NULL)
+       return 0;
+
+    for (i = 0; i < internet_address_list_length (list); i++) {
+       address = internet_address_list_get_address (list, i);
+       if (INTERNET_ADDRESS_IS_GROUP (address)) {
+           InternetAddressGroup *group;
+           InternetAddressList *group_list;
+
+           group = INTERNET_ADDRESS_GROUP (address);
+           group_list = internet_address_group_get_members (group);
+           n += scan_address_list (group_list, config, message, type, user_from);
+       } else {
+           InternetAddressMailbox *mailbox;
+           const char *name;
+           const char *addr;
+
+           mailbox = INTERNET_ADDRESS_MAILBOX (address);
+
+           name = internet_address_get_name (address);
+           addr = internet_address_mailbox_get_addr (mailbox);
+
+           if (address_is_users (addr, config)) {
+               if (user_from && *user_from == NULL)
+                   *user_from = addr;
+           } else if (message) {
+               g_mime_message_add_recipient (message, type, name, addr);
+               n++;
+           }
+       }
+    }
+
+    return n;
+}
+
+/* Does the address in the Reply-To header of 'message' already appear
+ * in either the 'To' or 'Cc' header of the message?
+ */
+static bool
+reply_to_header_is_redundant (GMimeMessage *message,
+                             InternetAddressList *reply_to_list)
+{
+    const char *addr, *reply_to;
+    InternetAddress *address;
+    InternetAddressMailbox *mailbox;
+    InternetAddressList *recipients;
+    bool ret = false;
+    int i;
+
+    if (reply_to_list == NULL ||
+       internet_address_list_length (reply_to_list) != 1)
+       return 0;
+
+    address = internet_address_list_get_address (reply_to_list, 0);
+    if (INTERNET_ADDRESS_IS_GROUP (address))
+       return 0;
+
+    mailbox = INTERNET_ADDRESS_MAILBOX (address);
+    reply_to = internet_address_mailbox_get_addr (mailbox);
+
+    recipients = g_mime_message_get_all_recipients (message);
+
+    for (i = 0; i < internet_address_list_length (recipients); i++) {
+       address = internet_address_list_get_address (recipients, i);
+       if (INTERNET_ADDRESS_IS_GROUP (address))
+           continue;
+
+       mailbox = INTERNET_ADDRESS_MAILBOX (address);
+       addr = internet_address_mailbox_get_addr (mailbox);
+       if (strcmp (addr, reply_to) == 0) {
+           ret = true;
+           break;
+       }
+    }
+
+    g_object_unref (G_OBJECT (recipients));
+
+    return ret;
+}
+
+static InternetAddressList *get_sender(GMimeMessage *message)
+{
+    InternetAddressList *reply_to_list;
+
+    reply_to_list = g_mime_message_get_reply_to_list (message);
+    if (reply_to_list &&
+       internet_address_list_length (reply_to_list) > 0) {
+        /*
+        * Some mailing lists munge the Reply-To header despite it
+        * being A Bad Thing, see
+        * http://marc.merlins.org/netrants/reply-to-harmful.html
+        *
+        * The munging is easy to detect, because it results in a
+        * redundant reply-to header, (with an address that already
+        * exists in either To or Cc). So in this case, we ignore the
+        * Reply-To field and use the From header. This ensures the
+        * original sender will get the reply even if not subscribed
+        * to the list. Note that the address in the Reply-To header
+        * will always appear in the reply if reply_all is true.
+        */
+       if (! reply_to_header_is_redundant (message, reply_to_list))
+           return reply_to_list;
+
+       g_mime_2_6_unref (G_OBJECT (reply_to_list));
+    }
+
+    return g_mime_message_get_from (message);
+}
+
+static InternetAddressList *get_to(GMimeMessage *message)
+{
+    return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_TO);
+}
+
+static InternetAddressList *get_cc(GMimeMessage *message)
+{
+    return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_CC);
+}
+
+static InternetAddressList *get_bcc(GMimeMessage *message)
+{
+    return g_mime_message_get_addresses (message, GMIME_ADDRESS_TYPE_BCC);
+}
+
+/* Augment the recipients of 'reply' from the "Reply-to:", "From:",
+ * "To:", "Cc:", and "Bcc:" headers of 'message'.
+ *
+ * If 'reply_all' is true, use sender and all recipients, otherwise
+ * scan the headers for the first that contains something other than
+ * the user's addresses and add the recipients from this header
+ * (typically this would be reply-to-sender, but also handles reply to
+ * user's own message in a sensible way).
+ *
+ * If any of the user's addresses were found in these headers, the
+ * first of these returned, otherwise NULL is returned.
+ */
+static const char *
+add_recipients_from_message (GMimeMessage *reply,
+                            notmuch_config_t *config,
+                            GMimeMessage *message,
+                            bool reply_all)
+{
+
+    /* There is a memory leak here with gmime-2.6 because get_sender
+     * returns a newly allocated list, while the others return
+     * references to libgmime owned data. This leak will be fixed with
+     * the transition to gmime-3.0.
+     */
+    struct {
+       InternetAddressList * (*get_header)(GMimeMessage *message);
+       GMimeRecipientType recipient_type;
+    } reply_to_map[] = {
+       { get_sender,   GMIME_ADDRESS_TYPE_TO },
+       { get_to,       GMIME_ADDRESS_TYPE_TO },
+       { get_cc,       GMIME_ADDRESS_TYPE_CC },
+       { get_bcc,      GMIME_ADDRESS_TYPE_BCC },
+    };
+    const char *from_addr = NULL;
+    unsigned int i;
+    unsigned int n = 0;
+
+    for (i = 0; i < ARRAY_SIZE (reply_to_map); i++) {
+       InternetAddressList *recipients;
+
+       recipients = reply_to_map[i].get_header (message);
+
+       n += scan_address_list (recipients, config, reply,
+                               reply_to_map[i].recipient_type, &from_addr);
+
+       if (!reply_all && n) {
+           /* Stop adding new recipients in reply-to-sender mode if
+            * we have added some recipient(s) above.
+            *
+            * This also handles the case of user replying to his own
+            * message, where reply-to/from is not a recipient. In
+            * this case there may be more than one recipient even if
+            * not replying to all.
+            */
+           reply = NULL;
+
+           /* From address and some recipients are enough, bail out. */
+           if (from_addr)
+               break;
+       }
+    }
+
+    return from_addr;
+}
+
+/*
+ * Look for the user's address in " for <email@add.res>" in the
+ * received headers.
+ *
+ * Return the address that was found, if any, and NULL otherwise.
+ */
+static const char *
+guess_from_in_received_for (notmuch_config_t *config, const char *received)
+{
+    const char *ptr;
+
+    ptr = strstr (received, " for ");
+    if (! ptr)
+       return NULL;
+
+    return user_address_in_string (ptr, config);
+}
+
+/*
+ * Parse all the " by MTA ..." parts in received headers to guess the
+ * email address that this was originally delivered to.
+ *
+ * Extract just the MTA here by removing leading whitespace and
+ * assuming that the MTA name ends at the next whitespace. Test for
+ * *(by+4) to be non-'\0' to make sure there's something there at all
+ * - and then assume that the first whitespace delimited token that
+ * follows is the receiving system in this step of the receive chain.
+ *
+ * Return the address that was found, if any, and NULL otherwise.
+ */
+static const char *
+guess_from_in_received_by (notmuch_config_t *config, const char *received)
+{
+    const char *addr;
+    const char *by = received;
+    char *domain, *tld, *mta, *ptr, *token;
+
+    while ((by = strstr (by, " by ")) != NULL) {
+       by += 4;
+       if (*by == '\0')
+           break;
+       mta = xstrdup (by);
+       token = strtok(mta," \t");
+       if (token == NULL) {
+           free (mta);
+           break;
+       }
+       /*
+        * Now extract the last two components of the MTA host name as
+        * domain and tld.
+        */
+       domain = tld = NULL;
+       while ((ptr = strsep (&token, ". \t")) != NULL) {
+           if (*ptr == '\0')
+               continue;
+           domain = tld;
+           tld = ptr;
+       }
+
+       if (domain) {
+           /*
+            * Recombine domain and tld and look for it among the
+            * configured email addresses. This time we have a known
+            * domain name and nothing else - so the test is the other
+            * way around: we check if this is a substring of one of
+            * the email addresses.
+            */
+           *(tld - 1) = '.';
+
+           addr = string_in_user_address (domain, config);
+           if (addr) {
+               free (mta);
+               return addr;
+           }
+       }
+       free (mta);
+    }
+
+    return NULL;
+}
+
+/*
+ * Get the concatenated Received: headers and search from the front
+ * (last Received: header added) and try to extract from them
+ * indications to which email address this message was delivered.
+ *
+ * The Received: header is special in our get_header function and is
+ * always concatenated.
+ *
+ * Return the address that was found, if any, and NULL otherwise.
+ */
+static const char *
+guess_from_in_received_headers (notmuch_config_t *config,
+                               notmuch_message_t *message)
+{
+    const char *received, *addr;
+    char *sanitized;
+
+    received = notmuch_message_get_header (message, "received");
+    if (! received)
+       return NULL;
+
+    sanitized = sanitize_string (NULL, received);
+    if (! sanitized)
+       return NULL;
+
+    addr = guess_from_in_received_for (config, sanitized);
+    if (! addr)
+       addr = guess_from_in_received_by (config, sanitized);
+
+    talloc_free (sanitized);
+
+    return addr;
+}
+
+/*
+ * Try to find user's email address in one of the extra To-like
+ * headers: Envelope-To, X-Original-To, and Delivered-To (searched in
+ * that order).
+ *
+ * Return the address that was found, if any, and NULL otherwise.
+ */
+static const char *
+get_from_in_to_headers (notmuch_config_t *config, notmuch_message_t *message)
+{
+    size_t i;
+    const char *tohdr, *addr;
+    const char *to_headers[] = {
+       "Envelope-to",
+       "X-Original-To",
+       "Delivered-To",
+    };
+
+    for (i = 0; i < ARRAY_SIZE (to_headers); i++) {
+       tohdr = notmuch_message_get_header (message, to_headers[i]);
+
+       /* Note: tohdr potentially contains a list of email addresses. */
+       addr = user_address_in_string (tohdr, config);
+       if (addr)
+           return addr;
+    }
+
+    return NULL;
+}
+
+static GMimeMessage *
+create_reply_message(void *ctx,
+                    notmuch_config_t *config,
+                    notmuch_message_t *message,
+                    GMimeMessage *mime_message,
+                    bool reply_all,
+                    bool limited)
+{
+    const char *subject, *from_addr = NULL;
+    const char *in_reply_to, *orig_references, *references;
+
+    /*
+     * Use the below header order for limited headers, "pretty" order
+     * otherwise.
+     */
+    GMimeMessage *reply = g_mime_message_new (limited ? 0 : 1);
+    if (reply == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return NULL;
+    }
+
+    in_reply_to = talloc_asprintf (ctx, "<%s>",
+                                  notmuch_message_get_message_id (message));
+
+    g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to);
+
+    orig_references = notmuch_message_get_header (message, "references");
+    if (orig_references && *orig_references)
+       references = talloc_asprintf (ctx, "%s %s", orig_references,
+                                     in_reply_to);
+    else
+       references = talloc_strdup (ctx, in_reply_to);
+
+    g_mime_object_set_header (GMIME_OBJECT (reply), "References", references);
+
+    from_addr = add_recipients_from_message (reply, config,
+                                            mime_message, reply_all);
+
+    /* The above is all that is needed for limited headers. */
+    if (limited)
+       return reply;
+
+    /*
+     * Sadly, there is no standard way to find out to which email
+     * address a mail was delivered - what is in the headers depends
+     * on the MTAs used along the way.
+     *
+     * If none of the user's email addresses are in the To: or Cc:
+     * headers, we try a number of heuristics which hopefully will
+     * answer this question.
+     *
+     * First, check for Envelope-To:, X-Original-To:, and
+     * Delivered-To: headers.
+     */
+    if (from_addr == NULL)
+       from_addr = get_from_in_to_headers (config, message);
+
+    /*
+     * Check for a (for <email@add.res>) clause in Received: headers,
+     * and the domain part of known email addresses in the 'by' part
+     * of Received: headers
+     */
+    if (from_addr == NULL)
+       from_addr = guess_from_in_received_headers (config, message);
+
+    /* Default to user's primary address. */
+    if (from_addr == NULL)
+       from_addr = notmuch_config_get_user_primary_email (config);
+
+    from_addr = talloc_asprintf (ctx, "%s <%s>",
+                                notmuch_config_get_user_name (config),
+                                from_addr);
+    g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr);
+
+    subject = notmuch_message_get_header (message, "subject");
+    if (subject) {
+       if (strncasecmp (subject, "Re:", 3))
+           subject = talloc_asprintf (ctx, "Re: %s", subject);
+       g_mime_message_set_subject (reply, subject);
+    }
+
+    return reply;
+}
+
+enum {
+    FORMAT_DEFAULT,
+    FORMAT_JSON,
+    FORMAT_SEXP,
+    FORMAT_HEADERS_ONLY,
+};
+
+static int do_reply(notmuch_config_t *config,
+                   notmuch_query_t *query,
+                   notmuch_show_params_t *params,
+                   int format,
+                   bool reply_all)
+{
+    GMimeMessage *reply;
+    mime_node_t *node;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+    struct sprinter *sp = NULL;
+
+    if (format == FORMAT_JSON || format == FORMAT_SEXP) {
+       unsigned count;
+
+       status = notmuch_query_count_messages (query, &count);
+       if (print_status_query ("notmuch reply", query, status))
+           return 1;
+
+       if (count != 1) {
+           fprintf (stderr, "Error: search term did not match precisely one message (matched %u messages).\n", count);
+           return 1;
+       }
+
+       if (format == FORMAT_JSON)
+           sp = sprinter_json_create (config, stdout);
+       else
+           sp = sprinter_sexp_create (config, stdout);
+    }
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch reply", query, status))
+       return 1;
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       message = notmuch_messages_get (messages);
+
+       if (mime_node_open (config, message, &params->crypto, &node))
+           return 1;
+
+       reply = create_reply_message (config, config, message,
+                                     GMIME_MESSAGE (node->part), reply_all,
+                                     format == FORMAT_HEADERS_ONLY);
+       if (!reply)
+           return 1;
+
+       if (format == FORMAT_JSON || format == FORMAT_SEXP) {
+           sp->begin_map (sp);
+
+           /* The headers of the reply message we've created */
+           sp->map_key (sp, "reply-headers");
+           format_headers_sprinter (sp, reply, true);
+
+           /* Start the original */
+           sp->map_key (sp, "original");
+           format_part_sprinter (config, sp, node, true, false);
+
+           /* End */
+           sp->end (sp);
+       } else {
+           GMimeStream *stream_stdout = stream_stdout = g_mime_stream_stdout_new ();
+           if (stream_stdout) {
+               show_reply_headers (stream_stdout, reply);
+               if (format == FORMAT_DEFAULT)
+                   format_part_reply (stream_stdout, node);
+           }
+           g_mime_stream_flush (stream_stdout);
+           g_object_unref(stream_stdout);
+       }
+
+       g_object_unref (G_OBJECT (reply));
+       talloc_free (node);
+
+       notmuch_message_destroy (message);
+    }
+
+    return 0;
+}
+
+int
+notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    notmuch_query_t *query;
+    char *query_string;
+    int opt_index;
+    notmuch_show_params_t params = {
+       .part = -1,
+       .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
+    };
+    int format = FORMAT_DEFAULT;
+    int reply_all = true;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &format, .name = "format", .keywords =
+         (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
+                                 { "json", FORMAT_JSON },
+                                 { "sexp", FORMAT_SEXP },
+                                 { "headers-only", FORMAT_HEADERS_ONLY },
+                                 { 0, 0 } } },
+       { .opt_int = &notmuch_format_version, .name = "format-version" },
+       { .opt_keyword = &reply_all, .name = "reply-to", .keywords =
+         (notmuch_keyword_t []){ { "all", true },
+                                 { "sender", false },
+                                 { 0, 0 } } },
+       { .opt_keyword = (int*)(&params.crypto.decrypt), .name = "decrypt",
+         .keyword_no_arg_value = "true", .keywords =
+         (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                                 { "auto", NOTMUCH_DECRYPT_AUTO },
+                                 { "true", NOTMUCH_DECRYPT_NOSTASH },
+                                 { 0, 0 } } },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    notmuch_exit_if_unsupported_format ();
+
+    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    if (query_string == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    if (*query_string == '\0') {
+       fprintf (stderr, "Error: notmuch reply requires at least one search term.\n");
+       return EXIT_FAILURE;
+    }
+
+#if (GMIME_MAJOR_VERSION < 3)
+    params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config);
+#endif
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_ONLY, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    if (do_reply (config, query, &params, format, reply_all) != 0)
+       return EXIT_FAILURE;
+
+    _notmuch_crypto_cleanup (&params.crypto);
+    notmuch_query_destroy (query);
+    notmuch_database_destroy (notmuch);
+
+    return EXIT_SUCCESS;
+}
diff --git a/notmuch-restore.c b/notmuch-restore.c
new file mode 100644 (file)
index 0000000..dee19c2
--- /dev/null
@@ -0,0 +1,459 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "hex-escape.h"
+#include "tag-util.h"
+#include "string-util.h"
+#include "zlib-extra.h"
+
+static int
+process_config_line (notmuch_database_t *notmuch, const char* line)
+{
+    const char *key_p, *val_p;
+    char *key, *val;
+    size_t key_len,val_len;
+    const char *delim = " \t\n";
+    int ret = EXIT_FAILURE;
+
+    void *local = talloc_new(NULL);
+
+    key_p = strtok_len_c (line, delim, &key_len);
+    val_p = strtok_len_c (key_p+key_len, delim, &val_len);
+
+    key = talloc_strndup (local, key_p, key_len);
+    val = talloc_strndup (local, val_p, val_len);
+    if (hex_decode_inplace (key) != HEX_SUCCESS ||
+       hex_decode_inplace (val) != HEX_SUCCESS ) {
+       fprintf (stderr, "hex decoding failure on line %s\n", line);
+       goto DONE;
+    }
+
+    if (print_status_database ("notmuch restore", notmuch,
+                              notmuch_database_set_config (notmuch, key, val)))
+       goto DONE;
+
+    ret = EXIT_SUCCESS;
+
+ DONE:
+    talloc_free (local);
+    return ret;
+}
+
+static int
+process_properties_line (notmuch_database_t *notmuch, const char* line)
+
+{
+    const char *id_p, *tok;
+    size_t id_len = 0, tok_len = 0;
+    char *id;
+
+    notmuch_message_t *message = NULL;
+    const char *delim = " \t\n";
+    int ret = EXIT_FAILURE;
+
+    void *local = talloc_new (NULL);
+
+    id_p = strtok_len_c (line, delim, &id_len);
+    id = talloc_strndup (local, id_p, id_len);
+    if (hex_decode_inplace (id) != HEX_SUCCESS) {
+       fprintf (stderr, "hex decoding failure on line %s\n", line);
+       goto DONE;
+    }
+
+    if (print_status_database ("notmuch restore", notmuch,
+                              notmuch_database_find_message (notmuch, id, &message)))
+       goto DONE;
+
+    if (print_status_database ("notmuch restore", notmuch,
+                              notmuch_message_remove_all_properties (message, NULL)))
+       goto DONE;
+
+    tok = id_p + id_len;
+
+    while ((tok = strtok_len_c (tok + tok_len, delim, &tok_len)) != NULL) {
+       char *key, *value;
+       size_t off = strcspn (tok, "=");
+       if (off > tok_len) {
+           fprintf (stderr, "unparsable token %s\n", tok);
+           goto DONE;
+       }
+
+       key = talloc_strndup (local, tok, off);
+       value = talloc_strndup (local, tok + off + 1, tok_len - off - 1);
+
+       if (hex_decode_inplace (key) != HEX_SUCCESS) {
+           fprintf (stderr, "hex decoding failure on key %s\n", key);
+           goto DONE;
+       }
+
+       if (hex_decode_inplace (value) != HEX_SUCCESS) {
+           fprintf (stderr, "hex decoding failure on value %s\n", value);
+           goto DONE;
+       }
+
+       if (print_status_database ("notmuch restore", notmuch,
+                                  notmuch_message_add_property (message, key, value)))
+           goto DONE;
+
+    }
+
+    ret = EXIT_SUCCESS;
+
+  DONE:
+    talloc_free (local);
+    return ret;
+}
+
+
+static regex_t regex;
+
+/* Non-zero return indicates an error in retrieving the message,
+ * or in applying the tags.  Missing messages are reported, but not
+ * considered errors.
+ */
+static int
+tag_message (unused (void *ctx),
+            notmuch_database_t *notmuch,
+            const char *message_id,
+            tag_op_list_t *tag_ops,
+            tag_op_flag_t flags)
+{
+    notmuch_status_t status;
+    notmuch_message_t *message = NULL;
+    int ret = 0;
+
+    status = notmuch_database_find_message (notmuch, message_id, &message);
+    if (status) {
+       fprintf (stderr, "Error applying tags to message %s: %s\n",
+                message_id, notmuch_status_to_string (status));
+       return 1;
+    }
+    if (message == NULL) {
+       fprintf (stderr, "Warning: cannot apply tags to missing message: %s\n",
+                message_id);
+       /* We consider this a non-fatal error. */
+       return 0;
+    }
+
+    /* In order to detect missing messages, this check/optimization is
+     * intentionally done *after* first finding the message. */
+    if ((flags & TAG_FLAG_REMOVE_ALL) || tag_op_list_size (tag_ops))
+       ret = tag_op_list_apply (message, tag_ops, flags);
+
+    notmuch_message_destroy (message);
+
+    return ret;
+}
+
+/* Sup dump output is one line per message. We match a sequence of
+ * non-space characters for the message-id, then one or more
+ * spaces, then a list of space-separated tags as a sequence of
+ * characters within literal '(' and ')'. */
+
+static int
+parse_sup_line (void *ctx, char *line,
+               char **query_str, tag_op_list_t *tag_ops)
+{
+
+    regmatch_t match[3];
+    char *file_tags;
+    int rerr;
+
+    tag_op_list_reset (tag_ops);
+
+    chomp_newline (line);
+
+    /* Silently ignore blank lines */
+    if (line[0] == '\0') {
+       return 1;
+    }
+
+    rerr = xregexec (&regex, line, 3, match, 0);
+    if (rerr == REG_NOMATCH) {
+       fprintf (stderr, "Warning: Ignoring invalid sup format line: %s\n",
+                line);
+       return 1;
+    }
+
+    *query_str = talloc_strndup_debug (ctx, line + match[1].rm_so,
+                                      match[1].rm_eo - match[1].rm_so);
+
+    file_tags = talloc_strndup_debug (ctx, line + match[2].rm_so,
+                                     match[2].rm_eo - match[2].rm_so);
+
+    char *tok = file_tags;
+    size_t tok_len = 0;
+
+    tag_op_list_reset (tag_ops);
+
+    while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {
+
+       if (*(tok + tok_len) != '\0') {
+           *(tok + tok_len) = '\0';
+           tok_len++;
+       }
+
+       if (tag_op_list_append (tag_ops, tok, false))
+           return -1;
+    }
+
+    return 0;
+
+}
+
+int
+notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    bool accumulate = false;
+    tag_op_flag_t flags = 0;
+    tag_op_list_t *tag_ops;
+
+    const char *input_file_name = NULL;
+    const char *name_for_error = NULL;
+    gzFile input = NULL;
+    char *line = NULL;
+    void *line_ctx = NULL;
+    ssize_t line_len;
+
+    int ret = 0;
+    int opt_index;
+    int include = 0;
+    int input_format = DUMP_FORMAT_AUTO;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    if (notmuch_config_get_maildir_synchronize_flags (config))
+       flags |= TAG_FLAG_MAILDIR_SYNC;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &input_format, .name = "format", .keywords =
+         (notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO },
+                                 { "batch-tag", DUMP_FORMAT_BATCH_TAG },
+                                 { "sup", DUMP_FORMAT_SUP },
+                                 { 0, 0 } } },
+       { .opt_flags = &include, .name = "include", .keywords =
+         (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
+                                 { "properties", DUMP_INCLUDE_PROPERTIES },
+                                 { "tags", DUMP_INCLUDE_TAGS} } },
+
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_bool = &accumulate, .name = "accumulate" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0) {
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    notmuch_process_shared_options (argv[0]);
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    if (include == 0) {
+       include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_PROPERTIES | DUMP_INCLUDE_TAGS;
+    }
+
+    name_for_error = input_file_name ? input_file_name : "stdin";
+
+    if (! accumulate)
+       flags |= TAG_FLAG_REMOVE_ALL;
+
+    errno = 0;
+    if (input_file_name)
+       input = gzopen (input_file_name, "r");
+    else {
+       int infd = dup (STDIN_FILENO);
+       if (infd < 0) {
+           fprintf (stderr, "Error duping stdin: %s\n",
+                    strerror (errno));
+           ret = EXIT_FAILURE;
+           goto DONE;
+       }
+       input = gzdopen (infd, "r");
+       if (! input)
+           close (infd);
+    }
+
+    if (input == NULL) {
+       fprintf (stderr, "Error opening %s for (gzip) reading: %s\n",
+                name_for_error, strerror (errno));
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    if (opt_index < argc) {
+       fprintf (stderr, "Unused positional parameter: %s\n", argv[opt_index]);
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    tag_ops = tag_op_list_create (config);
+    if (tag_ops == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    do {
+       util_status_t status;
+
+       status = gz_getline (line_ctx, &line, &line_len, input);
+
+       /* empty input file not considered an error */
+       if (status == UTIL_EOF) {
+           ret = EXIT_SUCCESS;
+           goto DONE;
+       }
+
+       if (status) {
+           fprintf (stderr, "Error reading (gzipped) input: %s\n",
+                    gz_error_string(status, input));
+           ret = EXIT_FAILURE;
+           goto DONE;
+       }
+
+       if ((include & DUMP_INCLUDE_CONFIG) && line_len >= 2 && line[0] == '#' && line[1] == '@') {
+           ret = process_config_line(notmuch, line+2);
+           if (ret)
+               goto DONE;
+       }
+       if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {
+           ret = process_properties_line (notmuch, line + 2);
+           if (ret)
+               goto DONE;
+       }
+
+    } while ((line_len == 0) ||
+            (line[0] == '#') ||
+            /* the cast is safe because we checked about for line_len < 0 */
+            (strspn (line, " \t\n") == (unsigned)line_len));
+
+    if (! ((include & DUMP_INCLUDE_TAGS) || (include & DUMP_INCLUDE_PROPERTIES))) {
+       ret = EXIT_SUCCESS;
+       goto DONE;
+    }
+
+    char *p;
+    for (p = line; (input_format == DUMP_FORMAT_AUTO) && *p; p++) {
+       if (*p == '(')
+           input_format = DUMP_FORMAT_SUP;
+    }
+
+    if (input_format == DUMP_FORMAT_AUTO)
+       input_format = DUMP_FORMAT_BATCH_TAG;
+
+    if (input_format == DUMP_FORMAT_SUP)
+       if ( xregcomp (&regex,
+                      "^([^ ]+) \\(([^)]*)\\)$",
+                      REG_EXTENDED) )
+           INTERNAL_ERROR ("compile time constant regex failed.");
+
+    do {
+       char *query_string, *prefix, *term;
+
+       if (line_ctx != NULL)
+           talloc_free (line_ctx);
+
+       line_ctx = talloc_new (config);
+
+       if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {
+           ret = process_properties_line (notmuch, line + 2);
+           if (ret)
+               goto DONE;
+       }
+
+       if (input_format == DUMP_FORMAT_SUP) {
+           ret = parse_sup_line (line_ctx, line, &query_string, tag_ops);
+       } else {
+           ret = parse_tag_line (line_ctx, line, TAG_FLAG_BE_GENEROUS,
+                                 &query_string, tag_ops);
+
+           if (ret == 0) {
+               ret = parse_boolean_term (line_ctx, query_string,
+                                         &prefix, &term);
+               if (ret && errno == EINVAL) {
+                   fprintf (stderr, "Warning: cannot parse query: %s (skipping)\n", query_string);
+                   continue;
+               } else if (ret) {
+                   /* This is more fatal (e.g., out of memory) */
+                   fprintf (stderr, "Error parsing query: %s\n",
+                            strerror (errno));
+                   ret = 1;
+                   break;
+               } else if (strcmp ("id", prefix) != 0) {
+                   fprintf (stderr, "Warning: not an id query: %s (skipping)\n", query_string);
+                   continue;
+               }
+               query_string = term;
+           }
+       }
+
+       if (ret > 0)
+           continue;
+
+       if (ret < 0)
+           break;
+
+       ret = tag_message (line_ctx, notmuch, query_string,
+                          tag_ops, flags);
+       if (ret)
+           break;
+
+    }  while (! (ret = gz_getline (line_ctx, &line, &line_len, input)));
+
+
+    /* EOF is normal loop termination condition, UTIL_SUCCESS is
+     * impossible here */
+    if (ret == UTIL_EOF) {
+       ret = EXIT_SUCCESS;
+    } else {
+       fprintf (stderr, "Error reading (gzipped) input: %s\n",
+                gz_error_string (ret, input));
+       ret = EXIT_FAILURE;
+    }
+
+    /* currently this should not be after DONE: since we don't 
+     * know if the xregcomp was reached
+     */
+
+    if (input_format == DUMP_FORMAT_SUP)
+       regfree (&regex);
+
+ DONE:
+    if (line_ctx != NULL)
+       talloc_free (line_ctx);
+
+    if (notmuch)
+       notmuch_database_destroy (notmuch);
+
+    if (input && gzclose_r (input)) {
+       fprintf (stderr, "Error closing %s: %s\n",
+                name_for_error, gzerror (input, NULL));
+       ret = EXIT_FAILURE;
+    }
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-search.c b/notmuch-search.c
new file mode 100644 (file)
index 0000000..8f467db
--- /dev/null
@@ -0,0 +1,935 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "sprinter.h"
+#include "string-util.h"
+
+typedef enum {
+    /* Search command */
+    OUTPUT_SUMMARY     = 1 << 0,
+    OUTPUT_THREADS     = 1 << 1,
+    OUTPUT_MESSAGES    = 1 << 2,
+    OUTPUT_FILES       = 1 << 3,
+    OUTPUT_TAGS                = 1 << 4,
+
+    /* Address command */
+    OUTPUT_SENDER      = 1 << 5,
+    OUTPUT_RECIPIENTS  = 1 << 6,
+    OUTPUT_COUNT       = 1 << 7,
+    OUTPUT_ADDRESS     = 1 << 8,
+} output_t;
+
+typedef enum {
+    DEDUP_NONE,
+    DEDUP_MAILBOX,
+    DEDUP_ADDRESS,
+} dedup_t;
+
+typedef enum {
+    NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_TEXT,
+    NOTMUCH_FORMAT_TEXT0,
+    NOTMUCH_FORMAT_SEXP
+} format_sel_t;
+
+typedef struct {
+    notmuch_database_t *notmuch;
+    int format_sel;
+    sprinter_t *format;
+    int exclude;
+    notmuch_query_t *query;
+    int sort;
+    int output;
+    int offset;
+    int limit;
+    int dupe;
+    GHashTable *addresses;
+    int dedup;
+} search_context_t;
+
+typedef struct {
+    const char *name;
+    const char *addr;
+    int count;
+} mailbox_t;
+
+/* Return two stable query strings that identify exactly the matched
+ * and unmatched messages currently in thread.  If there are no
+ * matched or unmatched messages, the returned buffers will be
+ * NULL. */
+static int
+get_thread_query (notmuch_thread_t *thread,
+                 char **matched_out, char **unmatched_out)
+{
+    notmuch_messages_t *messages;
+    char *escaped = NULL;
+    size_t escaped_len = 0;
+
+    *matched_out = *unmatched_out = NULL;
+
+    for (messages = notmuch_thread_get_messages (thread);
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       notmuch_message_t *message = notmuch_messages_get (messages);
+       const char *mid = notmuch_message_get_message_id (message);
+       /* Determine which query buffer to extend */
+       char **buf = notmuch_message_get_flag (
+           message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out;
+       /* Add this message's id: query.  Since "id" is an exclusive
+        * prefix, it is implicitly 'or'd together, so we only need to
+        * join queries with a space. */
+       if (make_boolean_term (thread, "id", mid, &escaped, &escaped_len) < 0)
+           return -1;
+       if (*buf)
+           *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped);
+       else
+           *buf = talloc_strdup (thread, escaped);
+       if (!*buf)
+           return -1;
+    }
+    talloc_free (escaped);
+    return 0;
+}
+
+static int
+do_search_threads (search_context_t *ctx)
+{
+    notmuch_thread_t *thread;
+    notmuch_threads_t *threads;
+    notmuch_tags_t *tags;
+    sprinter_t *format = ctx->format;
+    time_t date;
+    int i;
+    notmuch_status_t status;
+
+    if (ctx->offset < 0) {
+       unsigned count;
+       notmuch_status_t status;
+       status = notmuch_query_count_threads (ctx->query, &count);
+       if (print_status_query ("notmuch search", ctx->query, status))
+           return 1;
+
+       ctx->offset += count;
+       if (ctx->offset < 0)
+           ctx->offset = 0;
+    }
+
+    status = notmuch_query_search_threads (ctx->query, &threads);
+    if (print_status_query("notmuch search", ctx->query, status))
+       return 1;
+
+    format->begin_list (format);
+
+    for (i = 0;
+        notmuch_threads_valid (threads) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
+        notmuch_threads_move_to_next (threads), i++)
+    {
+       thread = notmuch_threads_get (threads);
+
+       if (i < ctx->offset) {
+           notmuch_thread_destroy (thread);
+           continue;
+       }
+
+       if (ctx->output == OUTPUT_THREADS) {
+           format->set_prefix (format, "thread");
+           format->string (format,
+                           notmuch_thread_get_thread_id (thread));
+           format->separator (format);
+       } else { /* output == OUTPUT_SUMMARY */
+           void *ctx_quote = talloc_new (thread);
+           const char *authors = notmuch_thread_get_authors (thread);
+           const char *subject = notmuch_thread_get_subject (thread);
+           const char *thread_id = notmuch_thread_get_thread_id (thread);
+           int matched = notmuch_thread_get_matched_messages (thread);
+           int files = notmuch_thread_get_total_files (thread);
+           int total = notmuch_thread_get_total_messages (thread);
+           const char *relative_date = NULL;
+           bool first_tag = true;
+
+           format->begin_map (format);
+
+           if (ctx->sort == NOTMUCH_SORT_OLDEST_FIRST)
+               date = notmuch_thread_get_oldest_date (thread);
+           else
+               date = notmuch_thread_get_newest_date (thread);
+
+           relative_date = notmuch_time_relative_date (ctx_quote, date);
+
+           if (format->is_text_printer) {
+                /* Special case for the text formatter */
+               printf ("thread:%s %12s ",
+                       thread_id,
+                       relative_date);
+               if (total == files)
+                   printf ("[%d/%d] %s; %s (",
+                       matched,
+                       total,
+                       sanitize_string (ctx_quote, authors),
+                       sanitize_string (ctx_quote, subject));
+               else
+                   printf ("[%d/%d(%d)] %s; %s (",
+                       matched,
+                       total,
+                       files,
+                       sanitize_string (ctx_quote, authors),
+                       sanitize_string (ctx_quote, subject));
+
+           } else { /* Structured Output */
+               format->map_key (format, "thread");
+               format->string (format, thread_id);
+               format->map_key (format, "timestamp");
+               format->integer (format, date);
+               format->map_key (format, "date_relative");
+               format->string (format, relative_date);
+               format->map_key (format, "matched");
+               format->integer (format, matched);
+               format->map_key (format, "total");
+               format->integer (format, total);
+               format->map_key (format, "authors");
+               format->string (format, authors);
+               format->map_key (format, "subject");
+               format->string (format, subject);
+               if (notmuch_format_version >= 2) {
+                   char *matched_query, *unmatched_query;
+                   if (get_thread_query (thread, &matched_query,
+                                         &unmatched_query) < 0) {
+                       fprintf (stderr, "Out of memory\n");
+                       return 1;
+                   }
+                   format->map_key (format, "query");
+                   format->begin_list (format);
+                   if (matched_query)
+                       format->string (format, matched_query);
+                   else
+                       format->null (format);
+                   if (unmatched_query)
+                       format->string (format, unmatched_query);
+                   else
+                       format->null (format);
+                   format->end (format);
+               }
+           }
+
+           talloc_free (ctx_quote);
+
+           format->map_key (format, "tags");
+           format->begin_list (format);
+
+           for (tags = notmuch_thread_get_tags (thread);
+                notmuch_tags_valid (tags);
+                notmuch_tags_move_to_next (tags))
+           {
+               const char *tag = notmuch_tags_get (tags);
+
+               if (format->is_text_printer) {
+                  /* Special case for the text formatter */
+                   if (first_tag)
+                       first_tag = false;
+                   else
+                       fputc (' ', stdout);
+                   fputs (tag, stdout);
+               } else { /* Structured Output */
+                   format->string (format, tag);
+               }
+           }
+
+           if (format->is_text_printer)
+               printf (")");
+
+           format->end (format);
+           format->end (format);
+           format->separator (format);
+       }
+
+       notmuch_thread_destroy (thread);
+    }
+
+    format->end (format);
+
+    return 0;
+}
+
+static mailbox_t *new_mailbox (void *ctx, const char *name, const char *addr)
+{
+    mailbox_t *mailbox;
+
+    mailbox = talloc (ctx, mailbox_t);
+    if (! mailbox)
+       return NULL;
+
+    mailbox->name = talloc_strdup (mailbox, name);
+    mailbox->addr = talloc_strdup (mailbox, addr);
+    mailbox->count = 1;
+
+    return mailbox;
+}
+
+static int mailbox_compare (const void *v1, const void *v2)
+{
+    const mailbox_t *m1 = v1, *m2 = v2;
+    int ret;
+
+    ret = strcmp_null (m1->name, m2->name);
+    if (! ret)
+       ret = strcmp (m1->addr, m2->addr);
+
+    return ret;
+}
+
+/* Returns true iff name and addr is duplicate. If not, stores the
+ * name/addr pair in order to detect subsequent duplicates. */
+static bool
+is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
+{
+    char *key;
+    GList *list, *l;
+    mailbox_t *mailbox;
+
+    list = g_hash_table_lookup (ctx->addresses, addr);
+    if (list) {
+       mailbox_t find = {
+           .name = name,
+           .addr = addr,
+       };
+
+       l = g_list_find_custom (list, &find, mailbox_compare);
+       if (l) {
+           mailbox = l->data;
+           mailbox->count++;
+           return true;
+       }
+
+       mailbox = new_mailbox (ctx->format, name, addr);
+       if (! mailbox)
+           return false;
+
+       /*
+        * XXX: It would be more efficient to prepend to the list, but
+        * then we'd have to store the changed list head back to the
+        * hash table. This check is here just to avoid the compiler
+        * warning for unused result.
+        */
+       if (list != g_list_append (list, mailbox))
+           INTERNAL_ERROR ("appending to list changed list head\n");
+
+       return false;
+    }
+
+    key = talloc_strdup (ctx->format, addr);
+    if (! key)
+       return false;
+
+    mailbox = new_mailbox (ctx->format, name, addr);
+    if (! mailbox)
+       return false;
+
+    list = g_list_append (NULL, mailbox);
+    if (! list)
+       return false;
+
+    g_hash_table_insert (ctx->addresses, key, list);
+
+    return false;
+}
+
+static void
+print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox)
+{
+    const char *name = mailbox->name;
+    const char *addr = mailbox->addr;
+    int count = mailbox->count;
+    sprinter_t *format = ctx->format;
+    InternetAddress *ia = internet_address_mailbox_new (name, addr);
+    char *name_addr;
+
+    /* name_addr has the name part quoted if necessary. Compare
+     * 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
+    name_addr = internet_address_to_string (ia, false);
+
+    if (format->is_text_printer) {
+       if (ctx->output & OUTPUT_COUNT) {
+           format->integer (format, count);
+           format->string (format, "\t");
+       }
+       if (ctx->output & OUTPUT_ADDRESS)
+           format->string (format, addr);
+       else
+           format->string (format, name_addr);
+       format->separator (format);
+    } else {
+       format->begin_map (format);
+       format->map_key (format, "name");
+       format->string (format, name);
+       format->map_key (format, "address");
+       format->string (format, addr);
+       format->map_key (format, "name-addr");
+       format->string (format, name_addr);
+       if (ctx->output & OUTPUT_COUNT) {
+           format->map_key (format, "count");
+           format->integer (format, count);
+       }
+       format->end (format);
+       format->separator (format);
+    }
+
+    g_object_unref (ia);
+    g_free (name_addr);
+}
+
+/* Print or prepare for printing addresses from InternetAddressList. */
+static void
+process_address_list (const search_context_t *ctx,
+                     InternetAddressList *list)
+{
+    InternetAddress *address;
+    int i;
+
+    for (i = 0; i < internet_address_list_length (list); i++) {
+       address = internet_address_list_get_address (list, i);
+       if (INTERNET_ADDRESS_IS_GROUP (address)) {
+           InternetAddressGroup *group;
+           InternetAddressList *group_list;
+
+           group = INTERNET_ADDRESS_GROUP (address);
+           group_list = internet_address_group_get_members (group);
+           if (group_list == NULL)
+               continue;
+
+           process_address_list (ctx, group_list);
+       } else {
+           InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
+           mailbox_t mbx = {
+               .name = internet_address_get_name (address),
+               .addr = internet_address_mailbox_get_addr (mailbox),
+           };
+
+           /* OUTPUT_COUNT only works with deduplication */
+           if (ctx->dedup != DEDUP_NONE &&
+               is_duplicate (ctx, mbx.name, mbx.addr))
+               continue;
+
+           /* OUTPUT_COUNT and DEDUP_ADDRESS require a full pass. */
+           if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
+               continue;
+
+           print_mailbox (ctx, &mbx);
+       }
+    }
+}
+
+/* Print or prepare for printing addresses from a message header. */
+static void
+process_address_header (const search_context_t *ctx, const char *value)
+{
+    InternetAddressList *list;
+
+    if (value == NULL)
+       return;
+
+    list = internet_address_list_parse_string (value);
+    if (list == NULL)
+       return;
+
+    process_address_list (ctx, list);
+
+    g_object_unref (list);
+}
+
+/* Destructor for talloc-allocated GHashTable keys and values. */
+static void
+_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
+static void
+_list_free_for_g_hash (void *ptr)
+{
+    g_list_free_full (ptr, _talloc_free_for_g_hash);
+}
+
+/* Print the most common variant of a list of unique mailboxes, and
+ * conflate the counts. */
+static void
+print_popular (const search_context_t *ctx, GList *list)
+{
+    GList *l;
+    mailbox_t *mailbox = NULL, *m;
+    int max = 0;
+    int total = 0;
+
+    for (l = list; l; l = l->next) {
+       m = l->data;
+       total += m->count;
+       if (m->count > max) {
+           mailbox = m;
+           max = m->count;
+       }
+    }
+
+    if (! mailbox)
+       INTERNAL_ERROR("Empty list in address hash table\n");
+
+    /* The original count is no longer needed, so overwrite. */
+    mailbox->count = total;
+
+    print_mailbox (ctx, mailbox);
+}
+
+static void
+print_list_value (void *mailbox, void *context)
+{
+    print_mailbox (context, mailbox);
+}
+
+static void
+print_hash_value (unused (void *key), void *list, void *context)
+{
+    const search_context_t *ctx = context;
+
+    if (ctx->dedup == DEDUP_ADDRESS)
+       print_popular (ctx, list);
+    else
+       g_list_foreach (list, print_list_value, context);
+}
+
+static int
+_count_filenames (notmuch_message_t *message)
+{
+    notmuch_filenames_t *filenames;
+    int i = 0;
+
+    filenames = notmuch_message_get_filenames (message);
+
+    while (notmuch_filenames_valid (filenames)) {
+        notmuch_filenames_move_to_next (filenames);
+        i++;
+    }
+
+    notmuch_filenames_destroy (filenames);
+
+    return i;
+}
+
+static int
+do_search_messages (search_context_t *ctx)
+{
+    notmuch_message_t *message;
+    notmuch_messages_t *messages;
+    notmuch_filenames_t *filenames;
+    sprinter_t *format = ctx->format;
+    int i;
+    notmuch_status_t status;
+
+    if (ctx->offset < 0) {
+       unsigned count;
+       notmuch_status_t status;
+       status = notmuch_query_count_messages (ctx->query, &count);
+       if (print_status_query ("notmuch search", ctx->query, status))
+           return 1;
+
+       ctx->offset += count;
+       if (ctx->offset < 0)
+           ctx->offset = 0;
+    }
+
+    status = notmuch_query_search_messages (ctx->query, &messages);
+    if (print_status_query ("notmuch search", ctx->query, status))
+       return 1;
+
+    format->begin_list (format);
+
+    for (i = 0;
+        notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit);
+        notmuch_messages_move_to_next (messages), i++)
+    {
+       if (i < ctx->offset)
+           continue;
+
+       message = notmuch_messages_get (messages);
+
+       if (ctx->output == OUTPUT_FILES) {
+           int j;
+           filenames = notmuch_message_get_filenames (message);
+
+           for (j = 1;
+                notmuch_filenames_valid (filenames);
+                notmuch_filenames_move_to_next (filenames), j++)
+           {
+               if (ctx->dupe < 0 || ctx->dupe == j) {
+                   format->string (format, notmuch_filenames_get (filenames));
+                   format->separator (format);
+               }
+           }
+           
+           notmuch_filenames_destroy( filenames );
+
+       } else if (ctx->output == OUTPUT_MESSAGES) {
+            /* special case 1 for speed */
+            if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) {
+                format->set_prefix (format, "id");
+                format->string (format,
+                                notmuch_message_get_message_id (message));
+                format->separator (format);
+            }
+       } else {
+           if (ctx->output & OUTPUT_SENDER) {
+               const char *addrs;
+
+               addrs = notmuch_message_get_header (message, "from");
+               process_address_header (ctx, addrs);
+           }
+
+           if (ctx->output & OUTPUT_RECIPIENTS) {
+               const char *hdrs[] = { "to", "cc", "bcc" };
+               const char *addrs;
+               size_t j;
+
+               for (j = 0; j < ARRAY_SIZE (hdrs); j++) {
+                   addrs = notmuch_message_get_header (message, hdrs[j]);
+                   process_address_header (ctx, addrs);
+               }
+           }
+       }
+
+       notmuch_message_destroy (message);
+    }
+
+    if (ctx->addresses &&
+       (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS))
+       g_hash_table_foreach (ctx->addresses, print_hash_value, ctx);
+
+    notmuch_messages_destroy (messages);
+
+    format->end (format);
+
+    return 0;
+}
+
+static int
+do_search_tags (const search_context_t *ctx)
+{
+    notmuch_messages_t *messages = NULL;
+    notmuch_tags_t *tags;
+    const char *tag;
+    sprinter_t *format = ctx->format;
+    notmuch_query_t *query = ctx->query;
+    notmuch_database_t *notmuch = ctx->notmuch;
+
+    /* should the following only special case if no excluded terms
+     * specified? */
+
+    /* Special-case query of "*" for better performance. */
+    if (strcmp (notmuch_query_get_query_string (query), "*") == 0) {
+       tags = notmuch_database_get_all_tags (notmuch);
+    } else {
+       notmuch_status_t status;
+       status = notmuch_query_search_messages (query, &messages);
+       if (print_status_query ("notmuch search", query, status))
+           return 1;
+
+       tags = notmuch_messages_collect_tags (messages);
+    }
+    if (tags == NULL)
+       return 1;
+
+    format->begin_list (format);
+
+    for (;
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       format->string (format, tag);
+       format->separator (format);
+
+    }
+
+    notmuch_tags_destroy (tags);
+
+    if (messages)
+       notmuch_messages_destroy (messages);
+
+    format->end (format);
+
+    return 0;
+}
+
+static int
+_notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int argc, char *argv[])
+{
+    char *query_str;
+    unsigned int i;
+    char *status_string = NULL;
+
+    switch (ctx->format_sel) {
+    case NOTMUCH_FORMAT_TEXT:
+       ctx->format = sprinter_text_create (config, stdout);
+       break;
+    case NOTMUCH_FORMAT_TEXT0:
+       if (ctx->output == OUTPUT_SUMMARY) {
+           fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
+           return EXIT_FAILURE;
+       }
+       ctx->format = sprinter_text0_create (config, stdout);
+       break;
+    case NOTMUCH_FORMAT_JSON:
+       ctx->format = sprinter_json_create (config, stdout);
+       break;
+    case NOTMUCH_FORMAT_SEXP:
+       ctx->format = sprinter_sexp_create (config, stdout);
+       break;
+    default:
+       /* this should never happen */
+       INTERNAL_ERROR("no output format selected");
+    }
+
+    notmuch_exit_if_unsupported_format ();
+
+    if (notmuch_database_open_verbose (
+           notmuch_config_get_database_path (config),
+           NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx->notmuch, &status_string)) {
+
+       if (status_string) {
+           fputs (status_string, stderr);
+           free (status_string);
+       }
+
+       return EXIT_FAILURE;
+    }
+
+    notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
+
+    query_str = query_string_from_args (ctx->notmuch, argc, argv);
+    if (query_str == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return EXIT_FAILURE;
+    }
+    if (*query_str == '\0') {
+       fprintf (stderr, "Error: notmuch search requires at least one search term.\n");
+       return EXIT_FAILURE;
+    }
+
+    ctx->query = notmuch_query_create (ctx->notmuch, query_str);
+    if (ctx->query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    notmuch_query_set_sort (ctx->query, ctx->sort);
+
+    if (ctx->exclude == NOTMUCH_EXCLUDE_FLAG && ctx->output != OUTPUT_SUMMARY) {
+       /* If we are not doing summary output there is nowhere to
+        * print the excluded flag so fall back on including the
+        * excluded messages. */
+       fprintf (stderr, "Warning: this output format cannot flag excluded messages.\n");
+       ctx->exclude = NOTMUCH_EXCLUDE_FALSE;
+    }
+
+    if (ctx->exclude != NOTMUCH_EXCLUDE_FALSE) {
+       const char **search_exclude_tags;
+       size_t search_exclude_tags_length;
+       notmuch_status_t status;
+
+       search_exclude_tags = notmuch_config_get_search_exclude_tags
+           (config, &search_exclude_tags_length);
+
+       for (i = 0; i < search_exclude_tags_length; i++) {
+           status = notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
+           if (status && status != NOTMUCH_STATUS_IGNORED) {
+               print_status_query ("notmuch search", ctx->query, status);
+               return EXIT_FAILURE;
+           }
+       }
+
+       notmuch_query_set_omit_excluded (ctx->query, ctx->exclude);
+    }
+
+    return 0;
+}
+
+static void
+_notmuch_search_cleanup (search_context_t *ctx)
+{
+    notmuch_query_destroy (ctx->query);
+    notmuch_database_destroy (ctx->notmuch);
+
+    talloc_free (ctx->format);
+}
+
+static search_context_t search_context = {
+    .format_sel = NOTMUCH_FORMAT_TEXT,
+    .exclude = NOTMUCH_EXCLUDE_TRUE,
+    .sort = NOTMUCH_SORT_NEWEST_FIRST,
+    .output = 0,
+    .offset = 0,
+    .limit = -1, /* unlimited */
+    .dupe = -1,
+    .dedup = DEDUP_MAILBOX,
+};
+
+static const notmuch_opt_desc_t common_options[] = {
+    { .opt_keyword = &search_context.sort, .name = "sort", .keywords =
+      (notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
+                             { "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
+                             { 0, 0 } } },
+    { .opt_keyword = &search_context.format_sel, .name = "format", .keywords =
+      (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+                             { "sexp", NOTMUCH_FORMAT_SEXP },
+                             { "text", NOTMUCH_FORMAT_TEXT },
+                             { "text0", NOTMUCH_FORMAT_TEXT0 },
+                             { 0, 0 } } },
+    { .opt_int = &notmuch_format_version, .name = "format-version" },
+    { }
+};
+
+int
+notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    search_context_t *ctx = &search_context;
+    int opt_index, ret;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &ctx->output, .name = "output", .keywords =
+         (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
+                                 { "threads", OUTPUT_THREADS },
+                                 { "messages", OUTPUT_MESSAGES },
+                                 { "files", OUTPUT_FILES },
+                                 { "tags", OUTPUT_TAGS },
+                                 { 0, 0 } } },
+        { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
+          (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
+                                  { "false", NOTMUCH_EXCLUDE_FALSE },
+                                  { "flag", NOTMUCH_EXCLUDE_FLAG },
+                                  { "all", NOTMUCH_EXCLUDE_ALL },
+                                  { 0, 0 } } },
+       { .opt_int = &ctx->offset, .name = "offset" },
+       { .opt_int = &ctx->limit, .name = "limit" },
+       { .opt_int = &ctx->dupe, .name = "duplicate" },
+       { .opt_inherit = common_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    ctx->output = OUTPUT_SUMMARY;
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
+       ctx->dupe != -1) {
+        fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
+        return EXIT_FAILURE;
+    }
+
+    if (_notmuch_search_prepare (ctx, config,
+                                argc - opt_index, argv + opt_index))
+       return EXIT_FAILURE;
+
+    switch (ctx->output) {
+    case OUTPUT_SUMMARY:
+    case OUTPUT_THREADS:
+       ret = do_search_threads (ctx);
+       break;
+    case OUTPUT_MESSAGES:
+    case OUTPUT_FILES:
+       ret = do_search_messages (ctx);
+       break;
+    case OUTPUT_TAGS:
+       ret = do_search_tags (ctx);
+       break;
+    default:
+       INTERNAL_ERROR ("Unexpected output");
+    }
+
+    _notmuch_search_cleanup (ctx);
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+int
+notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    search_context_t *ctx = &search_context;
+    int opt_index, ret;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_flags = &ctx->output, .name = "output", .keywords =
+         (notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
+                                 { "recipients", OUTPUT_RECIPIENTS },
+                                 { "count", OUTPUT_COUNT },
+                                 { "address", OUTPUT_ADDRESS },
+                                 { 0, 0 } } },
+       { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
+         (notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
+                                 { "false", NOTMUCH_EXCLUDE_FALSE },
+                                 { 0, 0 } } },
+       { .opt_keyword = &ctx->dedup, .name = "deduplicate", .keywords =
+         (notmuch_keyword_t []){ { "no", DEDUP_NONE },
+                                 { "mailbox", DEDUP_MAILBOX },
+                                 { "address", DEDUP_ADDRESS },
+                                 { 0, 0 } } },
+       { .opt_inherit = common_options },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (! (ctx->output & (OUTPUT_SENDER | OUTPUT_RECIPIENTS)))
+       ctx->output |= OUTPUT_SENDER;
+
+    if (ctx->output & OUTPUT_COUNT && ctx->dedup == DEDUP_NONE) {
+       fprintf (stderr, "--output=count is not applicable with --deduplicate=no\n");
+       return EXIT_FAILURE;
+    }
+
+    if (_notmuch_search_prepare (ctx, config,
+                                argc - opt_index, argv + opt_index))
+       return EXIT_FAILURE;
+
+    ctx->addresses = g_hash_table_new_full (strcase_hash, strcase_equal,
+                                           _talloc_free_for_g_hash,
+                                           _list_free_for_g_hash);
+
+    /* The order is not guaranteed if a full pass is required, so go
+     * for fastest. */
+    if (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)
+       notmuch_query_set_sort (ctx->query, NOTMUCH_SORT_UNSORTED);
+
+    ret = do_search_messages (ctx);
+
+    g_hash_table_unref (ctx->addresses);
+
+
+    _notmuch_search_cleanup (ctx);
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-setup.c b/notmuch-setup.c
new file mode 100644 (file)
index 0000000..5304800
--- /dev/null
@@ -0,0 +1,240 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+static const char *
+make_path_absolute (void *ctx, const char *path)
+{
+    char *cwd;
+
+    if (*path == '/')
+       return path;
+
+    cwd = getcwd (NULL, 0);
+    if (cwd == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return NULL;
+    }
+
+    path = talloc_asprintf (ctx, "%s/%s", cwd, path);
+    if (path == NULL)
+       fprintf (stderr, "Out of memory.\n");
+
+    free (cwd);
+
+    return path;
+}
+
+static void
+welcome_message_pre_setup (void)
+{
+    printf (
+"Welcome to notmuch!\n\n"
+
+"The goal of notmuch is to help you manage and search your collection of\n"
+"email, and to efficiently keep up with the flow of email as it comes in.\n\n"
+
+"Notmuch needs to know a few things about you such as your name and email\n"
+"address, as well as the directory that contains your email. This is where\n"
+"you already have mail stored and where messages will be delivered in the\n"
+"future. This directory can contain any number of sub-directories. Regular\n"
+"files in these directories should be individual email messages. If there\n"
+"are other, non-email files (such as indexes maintained by other email\n"
+"programs) then notmuch will do its best to detect those and ignore them.\n\n"
+
+"If you already have your email being delivered to directories in either\n"
+"maildir or mh format, then that's perfect. Mail storage that uses mbox\n"
+"format, (where one mbox file contains many messages), will not work with\n"
+"notmuch. If that's how your mail is currently stored, we recommend you\n"
+"first convert it to maildir format with a utility such as mb2md. You can\n"
+"continue configuring notmuch now, but be sure to complete the conversion\n"
+"before you run \"notmuch new\" for the first time.\n\n");
+}
+
+static void
+welcome_message_post_setup (void)
+{
+    printf ("\n"
+"Notmuch is now configured, and the configuration settings are saved in\n"
+"a file in your home directory named .notmuch-config . If you'd like to\n"
+"change the configuration in the future, you can either edit that file\n"
+"directly or run \"notmuch setup\".  To choose an alternate configuration\n"
+"location, set ${NOTMUCH_CONFIG}.\n\n"
+
+"The next step is to run \"notmuch new\" which will create a database\n"
+"that indexes all of your mail. Depending on the amount of mail you have\n"
+"the initial indexing process can take a long time, so expect that.\n"
+"Also, the resulting database will require roughly the same amount of\n"
+"storage space as your current collection of email. So please ensure you\n"
+"have sufficient storage space available now.\n\n");
+}
+
+static void
+print_tag_list (const char **tags, size_t tags_len)
+{
+    unsigned int i;
+    for (i = 0; i < tags_len; i++) {
+       if (i != 0)
+           printf (" ");
+       printf ("%s", tags[i]);
+    }
+}
+
+static GPtrArray *
+parse_tag_list (void *ctx, char *response)
+{
+    GPtrArray *tags = g_ptr_array_new ();
+    char *tag = response;
+    char *space;
+
+    while (tag && *tag) {
+       space = strchr (tag, ' ');
+       if (space)
+           g_ptr_array_add (tags, talloc_strndup (ctx, tag, space - tag));
+       else
+           g_ptr_array_add (tags, talloc_strdup (ctx, tag));
+       tag = space;
+       while (tag && *tag == ' ')
+           tag++;
+    }
+
+    return tags;
+}
+
+int
+notmuch_setup_command (notmuch_config_t *config,
+                      unused (int argc), unused (char *argv[]))
+{
+    char *response = NULL;
+    size_t response_size = 0;
+    const char **old_other_emails;
+    size_t old_other_emails_len;
+    GPtrArray *other_emails;
+    unsigned int i;
+    const char **new_tags;
+    size_t new_tags_len;
+    const char **search_exclude_tags;
+    size_t search_exclude_tags_len;
+
+#define prompt(format, ...)                                    \
+    do {                                                       \
+       printf (format, ##__VA_ARGS__);                         \
+       fflush (stdout);                                        \
+       if (getline (&response, &response_size, stdin) < 0) {   \
+           printf ("Exiting.\n");                              \
+           exit (EXIT_FAILURE);                                \
+       }                                                       \
+       chomp_newline (response);                               \
+    } while (0)
+
+    if (notmuch_minimal_options ("setup", argc, argv) < 0)
+       return EXIT_FAILURE;
+
+    if (notmuch_requested_db_uuid)
+       fprintf (stderr, "Warning: ignoring --uuid=%s\n",
+                notmuch_requested_db_uuid);
+
+    if (notmuch_config_is_new (config))
+       welcome_message_pre_setup ();
+
+    prompt ("Your full name [%s]: ", notmuch_config_get_user_name (config));
+    if (strlen (response))
+       notmuch_config_set_user_name (config, response);
+
+    prompt ("Your primary email address [%s]: ",
+           notmuch_config_get_user_primary_email (config));
+    if (strlen (response))
+       notmuch_config_set_user_primary_email (config, response);
+
+    other_emails = g_ptr_array_new ();
+
+    old_other_emails = notmuch_config_get_user_other_email (config,
+                                            &old_other_emails_len);
+    for (i = 0; i < old_other_emails_len; i++) {
+       prompt ("Additional email address [%s]: ", old_other_emails[i]);
+       if (strlen (response))
+           g_ptr_array_add (other_emails, talloc_strdup (config, response));
+       else
+           g_ptr_array_add (other_emails, talloc_strdup (config,
+                                                        old_other_emails[i]));
+    }
+
+    do {
+       prompt ("Additional email address [Press 'Enter' if none]: ");
+       if (strlen (response))
+           g_ptr_array_add (other_emails, talloc_strdup (config, response));
+    } while (strlen (response));
+    if (other_emails->len)
+       notmuch_config_set_user_other_email (config,
+                                            (const char **)
+                                            other_emails->pdata,
+                                            other_emails->len);
+    g_ptr_array_free (other_emails, true);
+
+    prompt ("Top-level directory of your email archive [%s]: ",
+           notmuch_config_get_database_path (config));
+    if (strlen (response)) {
+       const char *absolute_path;
+
+       absolute_path = make_path_absolute (config, response);
+       notmuch_config_set_database_path (config, absolute_path);
+    }
+
+    new_tags = notmuch_config_get_new_tags (config, &new_tags_len);
+
+    printf ("Tags to apply to all new messages (separated by spaces) [");
+    print_tag_list (new_tags, new_tags_len);
+    prompt ("]: ");
+
+    if (strlen (response)) {
+       GPtrArray *tags = parse_tag_list (config, response);
+
+       notmuch_config_set_new_tags (config, (const char **) tags->pdata,
+                                    tags->len);
+
+       g_ptr_array_free (tags, true);
+    }
+
+
+    search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_len);
+
+    printf ("Tags to exclude when searching messages (separated by spaces) [");
+    print_tag_list (search_exclude_tags, search_exclude_tags_len);
+    prompt ("]: ");
+
+    if (strlen (response)) {
+       GPtrArray *tags = parse_tag_list (config, response);
+
+       notmuch_config_set_search_exclude_tags (config,
+                                               (const char **) tags->pdata,
+                                               tags->len);
+
+       g_ptr_array_free (tags, true);
+    }
+
+    if (notmuch_config_save (config))
+       return EXIT_FAILURE;
+
+    if (notmuch_config_is_new (config))
+       welcome_message_post_setup ();
+
+    return EXIT_SUCCESS;
+}
diff --git a/notmuch-show.c b/notmuch-show.c
new file mode 100644 (file)
index 0000000..c3a3783
--- /dev/null
@@ -0,0 +1,1295 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "gmime-filter-reply.h"
+#include "sprinter.h"
+
+static const char *
+_get_tags_as_string (const void *ctx, notmuch_message_t *message)
+{
+    notmuch_tags_t *tags;
+    int first = 1;
+    const char *tag;
+    char *result;
+
+    result = talloc_strdup (ctx, "");
+    if (result == NULL)
+       return NULL;
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       result = talloc_asprintf_append (result, "%s%s",
+                                        first ? "" : " ", tag);
+       first = 0;
+    }
+
+    return result;
+}
+
+/* Get a nice, single-line summary of message. */
+static const char *
+_get_one_line_summary (const void *ctx, notmuch_message_t *message)
+{
+    const char *from;
+    time_t date;
+    const char *relative_date;
+    const char *tags;
+
+    from = notmuch_message_get_header (message, "from");
+
+    date = notmuch_message_get_date (message);
+    relative_date = notmuch_time_relative_date (ctx, date);
+
+    tags = _get_tags_as_string (ctx, message);
+
+    return talloc_asprintf (ctx, "%s (%s) (%s)",
+                           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
+format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
+{
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
+
+    void *local = talloc_new (NULL);
+    notmuch_tags_t *tags;
+    time_t date;
+    const char *relative_date;
+
+    sp->map_key (sp, "id");
+    sp->string (sp, notmuch_message_get_message_id (message));
+
+    sp->map_key (sp, "match");
+    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));
+
+    sp->map_key (sp, "excluded");
+    sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
+
+    sp->map_key (sp, "filename");
+    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);
+    sp->integer (sp, date);
+
+    sp->map_key (sp, "date_relative");
+    relative_date = notmuch_time_relative_date (local, date);
+    sp->string (sp, relative_date);
+
+    sp->map_key (sp, "tags");
+    sp->begin_list (sp);
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags))
+       sp->string (sp, notmuch_tags_get (tags));
+    sp->end (sp);
+
+    talloc_free (local);
+}
+
+/* Extract just the email address from the contents of a From:
+ * header. */
+static const char *
+_extract_email_address (const void *ctx, const char *from)
+{
+    InternetAddressList *addresses;
+    InternetAddress *address;
+    InternetAddressMailbox *mailbox;
+    const char *email = "MAILER-DAEMON";
+
+    addresses = internet_address_list_parse_string (from);
+
+    /* Bail if there is no address here. */
+    if (addresses == NULL || internet_address_list_length (addresses) < 1)
+       goto DONE;
+
+    /* Otherwise, just use the first address. */
+    address = internet_address_list_get_address (addresses, 0);
+
+    /* The From header should never contain an address group rather
+     * than a mailbox. So bail if it does. */
+    if (! INTERNET_ADDRESS_IS_MAILBOX (address))
+       goto DONE;
+
+    mailbox = INTERNET_ADDRESS_MAILBOX (address);
+    email = internet_address_mailbox_get_addr (mailbox);
+    email = talloc_strdup (ctx, email);
+
+  DONE:
+    if (addresses)
+       g_object_unref (addresses);
+
+    return email;
+   }
+
+/* Return 1 if 'line' is an mbox From_ line---that is, a line
+ * beginning with zero or more '>' characters followed by the
+ * characters 'F', 'r', 'o', 'm', and space.
+ *
+ * Any characters at all may appear after that in the line.
+ */
+static int
+_is_from_line (const char *line)
+{
+    const char *s = line;
+
+    if (line == NULL)
+       return 0;
+
+    while (*s == '>')
+       s++;
+
+    if (STRNCMP_LITERAL (s, "From ") == 0)
+       return 1;
+    else
+       return 0;
+}
+
+void
+format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
+                        bool reply)
+{
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
+
+    char *recipients_string;
+    const char *reply_to_string;
+    void *local = talloc_new (sp);
+
+    sp->begin_map (sp);
+
+    sp->map_key (sp, "Subject");
+    sp->string (sp, g_mime_message_get_subject (message));
+
+    sp->map_key (sp, "From");
+    sp->string (sp, g_mime_message_get_from_string (message));
+
+    recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO);
+    if (recipients_string) {
+       sp->map_key (sp, "To");
+       sp->string (sp, recipients_string);
+       g_free (recipients_string);
+    }
+
+    recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC);
+    if (recipients_string) {
+       sp->map_key (sp, "Cc");
+       sp->string (sp, recipients_string);
+       g_free (recipients_string);
+    }
+
+    recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_BCC);
+    if (recipients_string) {
+       sp->map_key (sp, "Bcc");
+       sp->string (sp, recipients_string);
+       g_free (recipients_string);
+    }
+
+    reply_to_string = g_mime_message_get_reply_to_string (local, message);
+    if (reply_to_string) {
+       sp->map_key (sp, "Reply-To");
+       sp->string (sp, reply_to_string);
+    }
+
+    if (reply) {
+       sp->map_key (sp, "In-reply-to");
+       sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"));
+
+       sp->map_key (sp, "References");
+       sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "References"));
+    } else {
+       sp->map_key (sp, "Date");
+       sp->string (sp, g_mime_message_get_date_string (sp, message));
+    }
+
+    sp->end (sp);
+    talloc_free (local);
+}
+
+/* Write a MIME text part out to the given stream.
+ *
+ * If (flags & NOTMUCH_SHOW_TEXT_PART_REPLY), this prepends "> " to
+ * each output line.
+ *
+ * Both line-ending conversion (CRLF->LF) and charset conversion ( ->
+ * UTF-8) will be performed, so it is inappropriate to call this
+ * function with a non-text part. Doing so will trigger an internal
+ * error.
+ */
+void
+show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
+                       notmuch_show_text_part_flags flags)
+{
+    GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+    GMimeStream *stream_filter = NULL;
+    GMimeFilter *crlf_filter = NULL;
+    GMimeFilter *windows_filter = NULL;
+    GMimeDataWrapper *wrapper;
+    const char *charset;
+
+    if (! g_mime_content_type_is_type (content_type, "text", "*"))
+       INTERNAL_ERROR ("Illegal request to format non-text part (%s) as text.",
+                       g_mime_content_type_to_string (content_type));
+
+    if (stream_out == NULL)
+       return;
+
+    charset = g_mime_object_get_content_type_parameter (part, "charset");
+    charset = charset ? g_mime_charset_canon_name (charset) : NULL;
+    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+    if (wrapper && charset && !g_ascii_strncasecmp (charset, "iso-8859-", 9)) {
+       GMimeStream *null_stream = NULL;
+       GMimeStream *null_stream_filter = NULL;
+
+       /* Check for mislabeled Windows encoding */
+       null_stream = g_mime_stream_null_new ();
+       null_stream_filter = g_mime_stream_filter_new (null_stream);
+       windows_filter = g_mime_filter_windows_new (charset);
+       g_mime_stream_filter_add(GMIME_STREAM_FILTER (null_stream_filter),
+                                windows_filter);
+       g_mime_data_wrapper_write_to_stream (wrapper, null_stream_filter);
+       charset = g_mime_filter_windows_real_charset(
+           (GMimeFilterWindows *) windows_filter);
+
+       if (null_stream_filter)
+           g_object_unref (null_stream_filter);
+       if (null_stream)
+           g_object_unref (null_stream);
+       /* Keep a reference to windows_filter in order to prevent the
+        * charset string from deallocation. */
+    }
+
+    stream_filter = g_mime_stream_filter_new (stream_out);
+    crlf_filter = g_mime_filter_crlf_new (false, false);
+    g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
+                            crlf_filter);
+    g_object_unref (crlf_filter);
+
+    if (charset) {
+       GMimeFilter *charset_filter;
+       charset_filter = g_mime_filter_charset_new (charset, "UTF-8");
+       /* This result can be NULL for things like "unknown-8bit".
+        * Don't set a NULL filter as that makes GMime print
+        * annoying assertion-failure messages on stderr. */
+       if (charset_filter) {
+           g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
+                                     charset_filter);
+           g_object_unref (charset_filter);
+       }
+
+    }
+
+    if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) {
+       GMimeFilter *reply_filter;
+       reply_filter = g_mime_filter_reply_new (true);
+       if (reply_filter) {
+           g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
+                                     reply_filter);
+           g_object_unref (reply_filter);
+       }
+    }
+
+    if (wrapper && stream_filter)
+       g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
+    if (stream_filter)
+       g_object_unref(stream_filter);
+    if (windows_filter)
+       g_object_unref (windows_filter);
+}
+
+static const char*
+signature_status_to_string (GMimeSignatureStatus status)
+{
+    if (g_mime_signature_status_bad (status))
+       return "bad";
+
+    if (g_mime_signature_status_error (status))
+       return "error";
+
+    if (g_mime_signature_status_good (status))
+       return "good";
+
+    return "unknown";
+}
+
+/* Print signature flags */
+struct key_map_struct {
+    GMimeSignatureError bit;
+    const char * string;
+};
+
+static void
+do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
+                           unsigned int array_map_len, GMimeSignatureError errors) {
+    sp->map_key (sp, "errors");
+    sp->begin_map (sp);
+
+    for (unsigned int i = 0; i < array_map_len; i++) {
+       if (errors & key_map[i].bit) {
+           sp->map_key (sp, key_map[i].string);
+           sp->boolean (sp, true);
+       }
+    }
+
+    sp->end (sp);
+}
+
+#if (GMIME_MAJOR_VERSION < 3)
+static void
+format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
+{
+    GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+
+    if (errors == GMIME_SIGNATURE_ERROR_NONE)
+       return;
+
+    struct key_map_struct key_map[] = {
+       { GMIME_SIGNATURE_ERROR_EXPSIG, "sig-expired" },
+       { GMIME_SIGNATURE_ERROR_NO_PUBKEY, "key-missing"},
+       { GMIME_SIGNATURE_ERROR_EXPKEYSIG, "key-expired"},
+       { GMIME_SIGNATURE_ERROR_REVKEYSIG, "key-revoked"},
+       { GMIME_SIGNATURE_ERROR_UNSUPP_ALGO, "alg-unsupported"},
+    };
+
+    do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors);
+}
+#else
+static void
+format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
+{
+    GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+
+    if (!(errors & GMIME_SIGNATURE_STATUS_ERROR_MASK))
+       return;
+
+    struct key_map_struct key_map[] = {
+       { GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked"},
+       { GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired"},
+       { GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "sig-expired" },
+       { GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing"},
+       { GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing"},
+       { GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old"},
+       { GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy"},
+       { GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error"},
+       { GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict"},
+    };
+
+    do_format_signature_errors (sp, key_map, ARRAY_SIZE(key_map), errors);
+}
+#endif
+
+/* Signature status sprinter (GMime 2.6) */
+static void
+format_part_sigstatus_sprinter (sprinter_t *sp, mime_node_t *node)
+{
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
+
+    GMimeSignatureList *siglist = node->sig_list;
+
+    sp->begin_list (sp);
+
+    if (!siglist) {
+       sp->end (sp);
+       return;
+    }
+
+    int i;
+    for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
+       GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
+
+       sp->begin_map (sp);
+
+       /* status */
+       GMimeSignatureStatus status = g_mime_signature_get_status (signature);
+       sp->map_key (sp, "status");
+       sp->string (sp, signature_status_to_string (status));
+
+       GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
+       if (g_mime_signature_status_good (status)) {
+           if (certificate) {
+               sp->map_key (sp, "fingerprint");
+               sp->string (sp, g_mime_certificate_get_fingerprint (certificate));
+           }
+           /* these dates are seconds since the epoch; should we
+            * provide a more human-readable format string? */
+           time_t created = g_mime_signature_get_created (signature);
+           if (created != -1) {
+               sp->map_key (sp, "created");
+               sp->integer (sp, created);
+           }
+           time_t expires = g_mime_signature_get_expires (signature);
+           if (expires > 0) {
+               sp->map_key (sp, "expires");
+               sp->integer (sp, expires);
+           }
+           if (certificate) {
+               const char *uid = g_mime_certificate_get_valid_userid (certificate);
+               if (uid) {
+                   sp->map_key (sp, "userid");
+                   sp->string (sp, uid);
+               }
+           }
+       } else if (certificate) {
+           const char *key_id = g_mime_certificate_get_fpr16 (certificate);
+           if (key_id) {
+               sp->map_key (sp, "keyid");
+               sp->string (sp, key_id);
+           }
+       }
+
+       if (notmuch_format_version <= 3) {
+           GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+           if (g_mime_signature_status_error (errors)) {
+               sp->map_key (sp, "errors");
+               sp->integer (sp, errors);
+           }
+       } else {
+           format_signature_errors (sp, signature);
+       }
+
+       sp->end (sp);
+     }
+
+    sp->end (sp);
+}
+
+static notmuch_status_t
+format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
+                 int indent, const notmuch_show_params_t *params)
+{
+    /* The disposition and content-type metadata are associated with
+     * the envelope for message parts */
+    GMimeObject *meta = node->envelope_part ?
+       GMIME_OBJECT (node->envelope_part) : node->part;
+    GMimeContentType *content_type = g_mime_object_get_content_type (meta);
+    const bool leaf = GMIME_IS_PART (node->part);
+    GMimeStream *stream = params->out_stream;
+    const char *part_type;
+    int i;
+
+    if (node->envelope_file) {
+       notmuch_message_t *message = node->envelope_file;
+
+       part_type = "message";
+       g_mime_stream_printf (stream, "\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n",
+                             part_type,
+                             notmuch_message_get_message_id (message),
+                             indent,
+                             notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
+                             notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
+                             notmuch_message_get_filename (message));
+    } else {
+       char *content_string;
+       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 (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+           part_type = "attachment";
+       else
+           part_type = "part";
+
+       g_mime_stream_printf (stream, "\f%s{ ID: %d", part_type, node->part_num);
+       if (filename)
+           g_mime_stream_printf (stream, ", Filename: %s", filename);
+       if (cid)
+           g_mime_stream_printf (stream, ", Content-id: %s", cid);
+
+       content_string = g_mime_content_type_to_string (content_type);
+       g_mime_stream_printf (stream, ", Content-type: %s\n", content_string);
+       g_free (content_string);
+    }
+
+    if (GMIME_IS_MESSAGE (node->part)) {
+       GMimeMessage *message = GMIME_MESSAGE (node->part);
+       char *recipients_string;
+       char *date_string;
+
+       g_mime_stream_printf (stream, "\fheader{\n");
+       if (node->envelope_file)
+           g_mime_stream_printf (stream, "%s\n", _get_one_line_summary (ctx, node->envelope_file));
+       g_mime_stream_printf (stream, "Subject: %s\n", g_mime_message_get_subject (message));
+       g_mime_stream_printf (stream, "From: %s\n", g_mime_message_get_from_string (message));
+       recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO);
+       if (recipients_string)
+           g_mime_stream_printf (stream, "To: %s\n", recipients_string);
+       g_free (recipients_string);
+       recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC);
+       if (recipients_string)
+           g_mime_stream_printf (stream, "Cc: %s\n", recipients_string);
+       g_free (recipients_string);
+       date_string = g_mime_message_get_date_string (node, message);
+       g_mime_stream_printf (stream, "Date: %s\n", date_string);
+       g_mime_stream_printf (stream, "\fheader}\n");
+
+       g_mime_stream_printf (stream, "\fbody{\n");
+    }
+
+    if (leaf) {
+       if (g_mime_content_type_is_type (content_type, "text", "*") &&
+           !g_mime_content_type_is_type (content_type, "text", "html"))
+       {
+           show_text_part_content (node->part, stream, 0);
+       } else {
+           char *content_string = g_mime_content_type_to_string (content_type);
+           g_mime_stream_printf (stream, "Non-text part: %s\n", content_string);
+           g_free (content_string);
+       }
+    }
+
+    for (i = 0; i < node->nchildren; i++)
+       format_part_text (ctx, sp, mime_node_child (node, i), indent, params);
+
+    if (GMIME_IS_MESSAGE (node->part))
+       g_mime_stream_printf (stream, "\fbody}\n");
+
+    g_mime_stream_printf (stream, "\f%s}\n", part_type);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static void
+format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart *part)
+{
+    const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
+    const char *cte = g_mime_object_get_header (meta, "content-transfer-encoding");
+    GMimeDataWrapper *wrapper = g_mime_part_get_content_object (part);
+    GMimeStream *stream = g_mime_data_wrapper_get_stream (wrapper);
+    ssize_t content_length = g_mime_stream_length (stream);
+
+    if (content_charset != NULL) {
+       sp->map_key (sp, "content-charset");
+       sp->string (sp, content_charset);
+    }
+    if (cte != NULL) {
+       sp->map_key (sp, "content-transfer-encoding");
+       sp->string (sp, cte);
+    }
+    if (content_length >= 0) {
+       sp->map_key (sp, "content-length");
+       sp->integer (sp, content_length);
+    }
+}
+
+void
+format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
+                     bool output_body,
+                     bool include_html)
+{
+    /* Any changes to the JSON or S-Expression format should be
+     * reflected in the file devel/schemata. */
+
+    if (node->envelope_file) {
+       sp->begin_map (sp);
+       format_message_sprinter (sp, node->envelope_file);
+
+       sp->map_key (sp, "headers");
+       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
+
+       if (output_body) {
+           sp->map_key (sp, "body");
+           sp->begin_list (sp);
+           format_part_sprinter (ctx, sp, mime_node_child (node, 0), true, include_html);
+           sp->end (sp);
+       }
+       sp->end (sp);
+       return;
+    }
+
+    /* The disposition and content-type metadata are associated with
+     * the envelope for message parts */
+    GMimeObject *meta = node->envelope_part ?
+       GMIME_OBJECT (node->envelope_part) : node->part;
+    GMimeContentType *content_type = g_mime_object_get_content_type (meta);
+    char *content_string;
+    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;
+    int nclose = 0;
+    int i;
+
+    sp->begin_map (sp);
+
+    sp->map_key (sp, "id");
+    sp->integer (sp, node->part_num);
+
+    if (node->decrypt_attempted) {
+       sp->map_key (sp, "encstatus");
+       sp->begin_list (sp);
+       sp->begin_map (sp);
+       sp->map_key (sp, "status");
+       sp->string (sp, node->decrypt_success ? "good" : "bad");
+       sp->end (sp);
+       sp->end (sp);
+    }
+
+    if (node->verify_attempted) {
+       sp->map_key (sp, "sigstatus");
+       format_part_sigstatus_sprinter (sp, node);
+    }
+
+    sp->map_key (sp, "content-type");
+    content_string = g_mime_content_type_to_string (content_type);
+    sp->string (sp, content_string);
+    g_free (content_string);
+
+    if (disposition) {
+       sp->map_key (sp, "content-disposition");
+       sp->string (sp, disposition);
+    }
+
+    if (cid) {
+       sp->map_key (sp, "content-id");
+       sp->string (sp, cid);
+    }
+
+    if (filename) {
+       sp->map_key (sp, "filename");
+       sp->string (sp, filename);
+    }
+
+    if (GMIME_IS_PART (node->part)) {
+       /* For non-HTML text parts, we include the content in the
+        * JSON. Since JSON must be Unicode, we handle charset
+        * decoding here and do not report a charset to the caller.
+        * For text/html parts, we do not include the content unless
+        * the --include-html option has been passed. If a html part
+        * is not included, it can be requested directly. This makes
+        * charset decoding the responsibility on the caller so we
+        * report the charset for text/html parts.
+        */
+       if (g_mime_content_type_is_type (content_type, "text", "*") &&
+           (include_html ||
+            ! g_mime_content_type_is_type (content_type, "text", "html")))
+       {
+           GMimeStream *stream_memory = g_mime_stream_mem_new ();
+           GByteArray *part_content;
+           show_text_part_content (node->part, stream_memory, 0);
+           part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
+           sp->map_key (sp, "content");
+           sp->string_len (sp, (char *) part_content->data, part_content->len);
+           g_object_unref (stream_memory);
+       } else {
+           format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part));
+       }
+    } else if (GMIME_IS_MULTIPART (node->part)) {
+       sp->map_key (sp, "content");
+       sp->begin_list (sp);
+       nclose = 1;
+    } else if (GMIME_IS_MESSAGE (node->part)) {
+       sp->map_key (sp, "content");
+       sp->begin_list (sp);
+       sp->begin_map (sp);
+
+       sp->map_key (sp, "headers");
+       format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
+
+       sp->map_key (sp, "body");
+       sp->begin_list (sp);
+       nclose = 3;
+    }
+
+    for (i = 0; i < node->nchildren; i++)
+       format_part_sprinter (ctx, sp, mime_node_child (node, i), true, include_html);
+
+    /* Close content structures */
+    for (i = 0; i < nclose; i++)
+       sp->end (sp);
+    /* Close part map */
+    sp->end (sp);
+}
+
+static notmuch_status_t
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
+                           mime_node_t *node, unused (int indent),
+                           const notmuch_show_params_t *params)
+{
+    format_part_sprinter (ctx, sp, node, params->output_body, params->include_html);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Print a message in "mboxrd" format as documented, for example,
+ * here:
+ *
+ * http://qmail.org/qmail-manual-html/man5/mbox.html
+ */
+static notmuch_status_t
+format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
+                 unused (int indent),
+                 unused (const notmuch_show_params_t *params))
+{
+    notmuch_message_t *message = node->envelope_file;
+
+    const char *filename;
+    FILE *file;
+    const char *from;
+
+    time_t date;
+    struct tm date_gmtime;
+    char date_asctime[26];
+
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
+
+    if (!message)
+       INTERNAL_ERROR ("format_part_mbox requires a root part");
+
+    filename = notmuch_message_get_filename (message);
+    file = fopen (filename, "r");
+    if (file == NULL) {
+       fprintf (stderr, "Failed to open %s: %s\n",
+                filename, strerror (errno));
+       return NOTMUCH_STATUS_FILE_ERROR;
+    }
+
+    from = notmuch_message_get_header (message, "from");
+    from = _extract_email_address (ctx, from);
+
+    date = notmuch_message_get_date (message);
+    gmtime_r (&date, &date_gmtime);
+    asctime_r (&date_gmtime, date_asctime);
+
+    printf ("From %s %s", from, date_asctime);
+
+    while ((line_len = getline (&line, &line_size, file)) != -1 ) {
+       if (_is_from_line (line))
+           putchar ('>');
+       printf ("%s", line);
+    }
+
+    printf ("\n");
+
+    fclose (file);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
+                mime_node_t *node, unused (int indent),
+                const notmuch_show_params_t *params)
+{
+    if (node->envelope_file) {
+       /* Special case the entire message to avoid MIME parsing. */
+       const char *filename;
+       FILE *file;
+       size_t size;
+       char buf[4096];
+
+       filename = notmuch_message_get_filename (node->envelope_file);
+       if (filename == NULL) {
+           fprintf (stderr, "Error: Cannot get message filename.\n");
+           return NOTMUCH_STATUS_FILE_ERROR;
+       }
+
+       file = fopen (filename, "r");
+       if (file == NULL) {
+           fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
+           return NOTMUCH_STATUS_FILE_ERROR;
+       }
+
+       while (!feof (file)) {
+           size = fread (buf, 1, sizeof (buf), file);
+           if (ferror (file)) {
+               fprintf (stderr, "Error: Read failed from %s\n", filename);
+               fclose (file);
+               return NOTMUCH_STATUS_FILE_ERROR;
+           }
+
+           if (fwrite (buf, size, 1, stdout) != 1) {
+               fprintf (stderr, "Error: Write failed\n");
+               fclose (file);
+               return NOTMUCH_STATUS_FILE_ERROR;
+           }
+       }
+
+       fclose (file);
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    GMimeStream *stream_filter = g_mime_stream_filter_new (params->out_stream);
+
+    if (GMIME_IS_PART (node->part)) {
+       /* For leaf parts, we emit only the transfer-decoded
+        * body. */
+       GMimeDataWrapper *wrapper;
+       wrapper = g_mime_part_get_content_object (GMIME_PART (node->part));
+
+       if (wrapper && stream_filter)
+           g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
+    } else {
+       /* Write out the whole part.  For message parts (the root
+        * part and embedded message parts), this will be the
+        * message including its headers (but not the
+        * encapsulating part's headers).  For multipart parts,
+        * this will include the headers. */
+       if (stream_filter)
+           g_mime_object_write_to_stream (node->part, stream_filter);
+    }
+
+    if (stream_filter)
+       g_object_unref (stream_filter);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+show_message (void *ctx,
+             const notmuch_show_format_t *format,
+             sprinter_t *sp,
+             notmuch_message_t *message,
+             int indent,
+             notmuch_show_params_t *params)
+{
+    void *local = talloc_new (ctx);
+    mime_node_t *root, *part;
+    notmuch_status_t status;
+    unsigned int session_keys = 0;
+    notmuch_status_t session_key_count_error = NOTMUCH_STATUS_SUCCESS;
+
+    if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
+       session_key_count_error = notmuch_message_count_properties (message, "session-key", &session_keys);
+
+    status = mime_node_open (local, message, &(params->crypto), &root);
+    if (status)
+       goto DONE;
+    part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
+    if (part)
+       status = format->part (local, sp, part, indent, params);
+#if HAVE_GMIME_SESSION_KEYS
+    if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE && session_key_count_error == NOTMUCH_STATUS_SUCCESS) {
+       unsigned int new_session_keys = 0;
+       if (notmuch_message_count_properties (message, "session-key", &new_session_keys) == NOTMUCH_STATUS_SUCCESS &&
+           new_session_keys > session_keys) {
+           /* try a quiet re-indexing */
+           notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch_message_get_database (message));
+           if (indexopts) {
+               notmuch_indexopts_set_decrypt_policy (indexopts, NOTMUCH_DECRYPT_AUTO);
+               print_status_message ("Error re-indexing message with --decrypt=stash",
+                                     message, notmuch_message_reindex (message, indexopts));
+           }
+       }
+    }
+#endif
+  DONE:
+    talloc_free (local);
+    return status;
+}
+
+static notmuch_status_t
+show_messages (void *ctx,
+              const notmuch_show_format_t *format,
+              sprinter_t *sp,
+              notmuch_messages_t *messages,
+              int indent,
+              notmuch_show_params_t *params)
+{
+    notmuch_message_t *message;
+    bool match;
+    bool excluded;
+    int next_indent;
+    notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
+
+    sp->begin_list (sp);
+
+    for (;
+        notmuch_messages_valid (messages);
+        notmuch_messages_move_to_next (messages))
+    {
+       sp->begin_list (sp);
+
+       message = notmuch_messages_get (messages);
+
+       match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
+       excluded = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
+
+       next_indent = indent;
+
+       if ((match && (!excluded || !params->omit_excluded)) || params->entire_thread) {
+           status = show_message (ctx, format, sp, message, indent, params);
+           if (status && !res)
+               res = status;
+           next_indent = indent + 1;
+       } else {
+           sp->null (sp);
+       }
+
+       status = show_messages (ctx,
+                               format, sp,
+                               notmuch_message_get_replies (message),
+                               next_indent,
+                               params);
+       if (status && !res)
+           res = status;
+
+       notmuch_message_destroy (message);
+
+       sp->end (sp);
+    }
+
+    sp->end (sp);
+
+    return res;
+}
+
+/* Formatted output of single message */
+static int
+do_show_single (void *ctx,
+               notmuch_query_t *query,
+               const notmuch_show_format_t *format,
+               sprinter_t *sp,
+               notmuch_show_params_t *params)
+{
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+    unsigned int count;
+
+    status = notmuch_query_count_messages (query, &count);
+    if (print_status_query ("notmuch show", query, status))
+       return 1;
+
+    if (count != 1) {
+       fprintf (stderr, "Error: search term did not match precisely one message (matched %u messages).\n", count);
+       return 1;
+    }
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch show", query, status))
+       return 1;
+
+    message = notmuch_messages_get (messages);
+
+    if (message == NULL) {
+       fprintf (stderr, "Error: Cannot find matching message.\n");
+       return 1;
+    }
+
+    notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1);
+
+    return show_message (ctx, format, sp, message, 0, params)
+       != NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Formatted output of threads */
+static int
+do_show (void *ctx,
+        notmuch_query_t *query,
+        const notmuch_show_format_t *format,
+        sprinter_t *sp,
+        notmuch_show_params_t *params)
+{
+    notmuch_threads_t *threads;
+    notmuch_thread_t *thread;
+    notmuch_messages_t *messages;
+    notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
+
+    status= notmuch_query_search_threads (query, &threads);
+    if (print_status_query ("notmuch show", query, status))
+       return 1;
+
+    sp->begin_list (sp);
+
+    for ( ;
+        notmuch_threads_valid (threads);
+        notmuch_threads_move_to_next (threads))
+    {
+       thread = notmuch_threads_get (threads);
+
+       messages = notmuch_thread_get_toplevel_messages (thread);
+
+       if (messages == NULL)
+           INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
+                           notmuch_thread_get_thread_id (thread));
+
+       status = show_messages (ctx, format, sp, messages, 0, params);
+       if (status && !res)
+           res = status;
+
+       notmuch_thread_destroy (thread);
+
+    }
+
+    sp->end (sp);
+
+    return res != NOTMUCH_STATUS_SUCCESS;
+}
+
+enum {
+    NOTMUCH_FORMAT_NOT_SPECIFIED,
+    NOTMUCH_FORMAT_JSON,
+    NOTMUCH_FORMAT_SEXP,
+    NOTMUCH_FORMAT_TEXT,
+    NOTMUCH_FORMAT_MBOX,
+    NOTMUCH_FORMAT_RAW
+};
+
+static const notmuch_show_format_t format_json = {
+    .new_sprinter = sprinter_json_create,
+    .part = format_part_sprinter_entry,
+};
+
+static const notmuch_show_format_t format_sexp = {
+    .new_sprinter = sprinter_sexp_create,
+    .part = format_part_sprinter_entry,
+};
+
+static const notmuch_show_format_t format_text = {
+    .new_sprinter = sprinter_text_create,
+    .part = format_part_text,
+};
+
+static const notmuch_show_format_t format_mbox = {
+    .new_sprinter = sprinter_text_create,
+    .part = format_part_mbox,
+};
+
+static const notmuch_show_format_t format_raw = {
+    .new_sprinter = sprinter_text_create,
+    .part = format_part_raw,
+};
+
+static const notmuch_show_format_t *formatters[] = {
+    [NOTMUCH_FORMAT_JSON] = &format_json,
+    [NOTMUCH_FORMAT_SEXP] = &format_sexp,
+    [NOTMUCH_FORMAT_TEXT] = &format_text,
+    [NOTMUCH_FORMAT_MBOX] = &format_mbox,
+    [NOTMUCH_FORMAT_RAW] = &format_raw,
+};
+
+int
+notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    notmuch_query_t *query;
+    char *query_string;
+    int opt_index, ret;
+    const notmuch_show_format_t *formatter;
+    sprinter_t *sprinter;
+    notmuch_show_params_t params = {
+       .part = -1,
+       .omit_excluded = true,
+       .output_body = true,
+       .crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
+    };
+    int format = NOTMUCH_FORMAT_NOT_SPECIFIED;
+    bool exclude = true;
+    bool entire_thread_set = false;
+    bool single_message;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &format, .name = "format", .keywords =
+         (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
+                                 { "text", NOTMUCH_FORMAT_TEXT },
+                                 { "sexp", NOTMUCH_FORMAT_SEXP },
+                                 { "mbox", NOTMUCH_FORMAT_MBOX },
+                                 { "raw", NOTMUCH_FORMAT_RAW },
+                                 { 0, 0 } } },
+       { .opt_int = &notmuch_format_version, .name = "format-version" },
+       { .opt_bool = &exclude, .name = "exclude" },
+       { .opt_bool = &params.entire_thread, .name = "entire-thread",
+         .present = &entire_thread_set },
+       { .opt_int = &params.part, .name = "part" },
+       { .opt_keyword = (int*)(&params.crypto.decrypt), .name = "decrypt",
+         .keyword_no_arg_value = "true", .keywords =
+         (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                                 { "auto", NOTMUCH_DECRYPT_AUTO },
+                                 { "true", NOTMUCH_DECRYPT_NOSTASH },
+                                 { "stash", NOTMUCH_DECRYPT_TRUE },
+                                 { 0, 0 } } },
+       { .opt_bool = &params.crypto.verify, .name = "verify" },
+       { .opt_bool = &params.output_body, .name = "body" },
+       { .opt_bool = &params.include_html, .name = "include-html" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    /* explicit decryption implies verification */
+    if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH ||
+       params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
+       params.crypto.verify = true;
+
+    /* specifying a part implies single message display */
+    single_message = params.part >= 0;
+
+    if (format == NOTMUCH_FORMAT_NOT_SPECIFIED) {
+       /* if part was requested and format was not specified, use format=raw */
+       if (params.part >= 0)
+           format = NOTMUCH_FORMAT_RAW;
+       else
+           format = NOTMUCH_FORMAT_TEXT;
+    }
+
+    if (format == NOTMUCH_FORMAT_MBOX) {
+       if (params.part > 0) {
+           fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
+           return EXIT_FAILURE;
+       }
+    } else if (format == NOTMUCH_FORMAT_RAW) {
+       /* raw format only supports single message display */
+       single_message = true;
+    }
+
+    notmuch_exit_if_unsupported_format ();
+
+    /* Default is entire-thread = false except for format=json and
+     * format=sexp. */
+    if (! entire_thread_set &&
+       (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP))
+       params.entire_thread = true;
+
+    if (!params.output_body) {
+       if (params.part > 0) {
+           fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
+           params.output_body = true;
+       } else {
+           if (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)
+               fprintf (stderr,
+                        "Warning: --body=false only implemented for format=json and format=sexp\n");
+       }
+    }
+
+    if (params.include_html &&
+        (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)) {
+       fprintf (stderr, "Warning: --include-html only implemented for format=json and format=sexp\n");
+    }
+
+    query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+    if (query_string == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    if (*query_string == '\0') {
+       fprintf (stderr, "Error: notmuch show requires at least one search term.\n");
+       return EXIT_FAILURE;
+    }
+
+#if (GMIME_MAJOR_VERSION < 3)
+    params.crypto.gpgpath = notmuch_config_get_crypto_gpg_path (config);
+#endif
+
+    notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+    if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
+       mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              mode, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       return EXIT_FAILURE;
+    }
+
+    /* Create structure printer. */
+    formatter = formatters[format];
+    sprinter = formatter->new_sprinter(config, stdout);
+
+    params.out_stream = g_mime_stream_stdout_new ();
+
+    /* If a single message is requested we do not use search_excludes. */
+    if (single_message) {
+       ret = do_show_single (config, query, formatter, sprinter, &params);
+    } else {
+       /* We always apply set the exclude flag. The
+        * exclude=true|false option controls whether or not we return
+        * threads that only match in an excluded message */
+       const char **search_exclude_tags;
+       size_t search_exclude_tags_length;
+       unsigned int i;
+       notmuch_status_t status;
+
+       search_exclude_tags = notmuch_config_get_search_exclude_tags
+           (config, &search_exclude_tags_length);
+
+       for (i = 0; i < search_exclude_tags_length; i++) {
+           status = notmuch_query_add_tag_exclude (query, search_exclude_tags[i]);
+           if (status && status != NOTMUCH_STATUS_IGNORED) {
+               print_status_query ("notmuch show", query, status);
+               ret = -1;
+               goto DONE;
+           }
+       }
+
+       if (exclude == false) {
+           notmuch_query_set_omit_excluded (query, false);
+           params.omit_excluded = false;
+       }
+
+       ret = do_show (config, query, formatter, sprinter, &params);
+    }
+
+ DONE:
+    g_mime_stream_flush (params.out_stream);
+    g_object_unref (params.out_stream);
+
+    _notmuch_crypto_cleanup (&params.crypto);
+    notmuch_query_destroy (query);
+    notmuch_database_destroy (notmuch);
+
+    return ret ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-tag.c b/notmuch-tag.c
new file mode 100644 (file)
index 0000000..05b1837
--- /dev/null
@@ -0,0 +1,287 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+#include "tag-util.h"
+#include "string-util.h"
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+
+static char *
+_optimize_tag_query (void *ctx, const char *orig_query_string,
+                    const tag_op_list_t *list)
+{
+    /* This is subtler than it looks.  Xapian ignores the '-' operator
+     * at the beginning both queries and parenthesized groups and,
+     * furthermore, the presence of a '-' operator at the beginning of
+     * a group can inhibit parsing of the previous operator.  Hence,
+     * the user-provided query MUST appear first, but it is safe to
+     * parenthesize and the exclusion part of the query must not use
+     * the '-' operator (though the NOT operator is fine). */
+
+    char *escaped = NULL;
+    size_t escaped_len = 0;
+    char *query_string;
+    const char *join = "";
+    size_t i;
+
+    /* Don't optimize if there are no tag changes. */
+    if (tag_op_list_size (list) == 0)
+       return talloc_strdup (ctx, orig_query_string);
+
+    /* Build the new query string */
+    if (strcmp (orig_query_string, "*") == 0)
+       query_string = talloc_strdup (ctx, "(");
+    else
+       query_string = talloc_asprintf (ctx, "( %s ) and (", orig_query_string);
+
+    for (i = 0; i < tag_op_list_size (list) && query_string; i++) {
+       /* XXX in case of OOM, query_string will be deallocated when
+        * ctx is, which might be at shutdown */
+       if (make_boolean_term (ctx,
+                              "tag", tag_op_list_tag (list, i),
+                              &escaped, &escaped_len))
+           return NULL;
+
+       query_string = talloc_asprintf_append_buffer (
+           query_string, "%s%s%s", join,
+           tag_op_list_isremove (list, i) ? "" : "not ",
+           escaped);
+       join = " or ";
+    }
+
+    if (query_string)
+       query_string = talloc_strdup_append_buffer (query_string, ")");
+
+    talloc_free (escaped);
+    return query_string;
+}
+
+/* Tag messages matching 'query_string' according to 'tag_ops'
+ */
+static int
+tag_query (void *ctx, notmuch_database_t *notmuch, const char *query_string,
+          tag_op_list_t *tag_ops, tag_op_flag_t flags)
+{
+    notmuch_query_t *query;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    int ret = NOTMUCH_STATUS_SUCCESS;
+
+    if (! (flags & TAG_FLAG_REMOVE_ALL)) {
+       /* Optimize the query so it excludes messages that already
+        * have the specified set of tags. */
+       query_string = _optimize_tag_query (ctx, query_string, tag_ops);
+       if (query_string == NULL) {
+           fprintf (stderr, "Out of memory.\n");
+           return 1;
+       }
+       flags |= TAG_FLAG_PRE_OPTIMIZED;
+    }
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return 1;
+    }
+
+    /* tagging is not interested in any special sort order */
+    notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+    status = notmuch_query_search_messages (query, &messages);
+    if (print_status_query ("notmuch tag", query, status))
+       return status;
+
+    for (;
+        notmuch_messages_valid (messages) && ! interrupted;
+        notmuch_messages_move_to_next (messages)) {
+       message = notmuch_messages_get (messages);
+       ret = tag_op_list_apply (message, tag_ops, flags);
+       notmuch_message_destroy (message);
+       if (ret != NOTMUCH_STATUS_SUCCESS)
+           break;
+    }
+
+    notmuch_query_destroy (query);
+
+    return ret || interrupted;
+}
+
+static int
+tag_file (void *ctx, notmuch_database_t *notmuch, tag_op_flag_t flags,
+         FILE *input)
+{
+    char *line = NULL;
+    char *query_string = NULL;
+    size_t line_size = 0;
+    ssize_t line_len;
+    int ret = 0;
+    int warn = 0;
+    tag_op_list_t *tag_ops;
+
+    tag_ops = tag_op_list_create (ctx);
+    if (tag_ops == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return 1;
+    }
+
+    while ((line_len = getline (&line, &line_size, input)) != -1 &&
+          ! interrupted) {
+
+       ret = parse_tag_line (ctx, line, TAG_FLAG_NONE,
+                             &query_string, tag_ops);
+
+       if (ret > 0) {
+           if (ret != TAG_PARSE_SKIPPED)
+               /* remember there has been problematic lines */
+               warn = 1;
+           ret = 0;
+           continue;
+       }
+
+       if (ret < 0)
+           break;
+
+       ret = tag_query (ctx, notmuch, query_string, tag_ops, flags);
+       if (ret)
+           break;
+    }
+
+    if (line)
+       free (line);
+
+    return ret || warn;
+}
+
+int
+notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    tag_op_list_t *tag_ops = NULL;
+    char *query_string = NULL;
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    tag_op_flag_t tag_flags = TAG_FLAG_NONE;
+    bool batch = false;
+    bool remove_all = false;
+    FILE *input = stdin;
+    const char *input_file_name = NULL;
+    int opt_index;
+    int ret;
+
+    /* Set up our handler for SIGINT */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = SA_RESTART;
+    sigaction (SIGINT, &action, NULL);
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_bool = &batch, .name = "batch" },
+       { .opt_string = &input_file_name, .name = "input" },
+       { .opt_bool = &remove_all, .name = "remove-all" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    notmuch_process_shared_options (argv[0]);
+
+    if (input_file_name) {
+       batch = true;
+       input = fopen (input_file_name, "r");
+       if (input == NULL) {
+           fprintf (stderr, "Error opening %s for reading: %s\n",
+                    input_file_name, strerror (errno));
+           return EXIT_FAILURE;
+       }
+    }
+
+    if (batch) {
+       if (opt_index != argc) {
+           fprintf (stderr, "Can't specify both cmdline and stdin!\n");
+           if (input)
+               fclose (input);
+           return EXIT_FAILURE;
+       }
+    } else {
+       tag_ops = tag_op_list_create (config);
+       if (tag_ops == NULL) {
+           fprintf (stderr, "Out of memory.\n");
+           return EXIT_FAILURE;
+       }
+
+       if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+                                   &query_string, tag_ops))
+           return EXIT_FAILURE;
+
+       if (tag_op_list_size (tag_ops) == 0 && ! remove_all) {
+           fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n");
+           return EXIT_FAILURE;
+       }
+
+       if (*query_string == '\0') {
+           fprintf (stderr, "Error: notmuch tag requires at least one search term.\n");
+           return EXIT_FAILURE;
+       }
+    }
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return EXIT_FAILURE;
+
+    notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+    if (notmuch_config_get_maildir_synchronize_flags (config))
+       tag_flags |= TAG_FLAG_MAILDIR_SYNC;
+
+    if (remove_all)
+       tag_flags |= TAG_FLAG_REMOVE_ALL;
+
+    if (batch)
+       ret = tag_file (config, notmuch, tag_flags, input);
+    else
+       ret = tag_query (config, notmuch, query_string, tag_ops, tag_flags);
+
+    notmuch_database_destroy (notmuch);
+
+    if (input != stdin)
+       fclose (input);
+
+    return ret || interrupted ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/notmuch-time.c b/notmuch-time.c
new file mode 100644 (file)
index 0000000..2734b36
--- /dev/null
@@ -0,0 +1,137 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+/* Format a nice representation of 'time' relative to the current time.
+ *
+ * Examples include:
+ *
+ *     5 mins. ago     (For times less than 60 minutes ago)
+ *     Today 12:30     (For times >60 minutes but still today)
+ *     Yest. 12:30
+ *     Mon.  12:30     (Before yesterday but fewer than 7 days ago)
+ *     October 12      (Between 7 and 180 days ago (about 6 months))
+ *     2008-06-30      (More than 180 days ago)
+ *
+ * The returned string is either static data (a string literal) or
+ * newly talloced data belonging to 'ctx'. That is, the caller should
+ * not modify nor free the returned value. But when the caller
+ * arranges for 'ctx' to be talloc_freed, then memory allocated here
+ * (if any) will be reclaimed.
+ *
+ */
+#define MINUTE (60)
+#define HOUR (60 * MINUTE)
+#define DAY (24 * HOUR)
+#define RELATIVE_DATE_MAX 20
+const char *
+notmuch_time_relative_date (const void *ctx, time_t then)
+{
+    struct tm tm_now, tm_then;
+    time_t now = time(NULL);
+    time_t delta;
+    char *result;
+
+    localtime_r (&now, &tm_now);
+    localtime_r (&then, &tm_then);
+
+    result = talloc_zero_size (ctx, RELATIVE_DATE_MAX);
+    if (result == NULL)
+       return "when?";
+
+    if (then > now)
+       return "the future";
+
+    delta = now - then;
+
+    if (delta > 180 * DAY) {
+       strftime (result, RELATIVE_DATE_MAX,
+                 "%F", &tm_then); /* 2008-06-30 */
+       return result;
+    }
+
+    if (delta < 3600) {
+       snprintf (result, RELATIVE_DATE_MAX,
+                 "%d mins. ago", (int) (delta / 60));
+       return result;
+    }
+
+    if (delta <= 7 * DAY) {
+       if (tm_then.tm_wday == tm_now.tm_wday &&
+           delta < DAY)
+       {
+           strftime (result, RELATIVE_DATE_MAX,
+                     "Today %R", &tm_then); /* Today 12:30 */
+           return result;
+       } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
+           strftime (result, RELATIVE_DATE_MAX,
+                     "Yest. %R", &tm_then); /* Yest. 12:30 */
+           return result;
+       } else {
+           if (tm_then.tm_wday != tm_now.tm_wday) {
+               strftime (result, RELATIVE_DATE_MAX,
+                         "%a. %R", &tm_then); /* Mon. 12:30 */
+               return result;
+           }
+       }
+    }
+
+    strftime (result, RELATIVE_DATE_MAX,
+             "%B %d", &tm_then); /* October 12 */
+    return result;
+}
+#undef MINUTE
+#undef HOUR
+#undef DAY
+
+void
+notmuch_time_print_formatted_seconds (double seconds)
+{
+    int hours;
+    int minutes;
+
+    if (seconds < 1) {
+       printf ("almost no time");
+       return;
+    }
+
+    if (seconds > 3600) {
+       hours = (int) seconds / 3600;
+       printf ("%dh ", hours);
+       seconds -= hours * 3600;
+    }
+
+    if (seconds > 60) {
+       minutes = (int) seconds / 60;
+       printf ("%dm ", minutes);
+       seconds -= minutes * 60;
+    }
+
+    printf ("%ds", (int) seconds);
+}
+
+/* Compute the number of seconds elapsed from start to end. */
+double
+notmuch_time_elapsed (struct timeval start, struct timeval end)
+{
+    return ((end.tv_sec - start.tv_sec) +
+           (end.tv_usec - start.tv_usec) / 1e6);
+}
diff --git a/notmuch-vim.README.Debian b/notmuch-vim.README.Debian
deleted file mode 100644 (file)
index ec052ee..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-notmuch for Debian
-==================
-
-To use the vim plugin, please install it using vim-addons(1)
-
-See also /usr/share/doc/notmuch/README.
-
- -- David Bremner <bremner@debian.org>, Fri,  1 Jul 2011 11:34:39 -0300
diff --git a/notmuch-vim.dirs b/notmuch-vim.dirs
deleted file mode 100644 (file)
index c6373e4..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-usr/share/vim/registry
-usr/share/vim/addons/plugin
-usr/share/vim/addons/doc
-usr/share/vim/addons/syntax
diff --git a/notmuch-vim.docs b/notmuch-vim.docs
deleted file mode 100644 (file)
index d1496b5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-vim/README
diff --git a/notmuch-vim.install b/notmuch-vim.install
deleted file mode 100644 (file)
index a1af708..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-vim/notmuch.vim usr/share/vim/addons/plugin
-vim/notmuch.txt usr/share/vim/addons/doc
-vim/syntax/notmuch-*.vim usr/share/vim/addons/syntax
-vim/notmuch.yaml usr/share/vim/registry
diff --git a/notmuch.c b/notmuch.c
new file mode 100644 (file)
index 0000000..7810b68
--- /dev/null
+++ b/notmuch.c
@@ -0,0 +1,535 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * 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: Carl Worth <cworth@cworth.org>
+ *         Keith Packard <keithp@keithp.com>
+ */
+
+#include "notmuch-client.h"
+
+/*
+ * Notmuch subcommand hook.
+ *
+ * The return value will be used as notmuch exit status code,
+ * preferably EXIT_SUCCESS or EXIT_FAILURE.
+ */
+typedef int (*command_function_t) (notmuch_config_t *config, int argc, char *argv[]);
+
+typedef struct command {
+    const char *name;
+    command_function_t function;
+    notmuch_config_mode_t config_mode;
+    const char *summary;
+} command_t;
+
+static int
+notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
+
+static int
+notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
+
+static int
+_help_for (const char *topic);
+
+static bool print_version = false, print_help = false;
+const char *notmuch_requested_db_uuid = NULL;
+
+const notmuch_opt_desc_t notmuch_shared_options [] = {
+    { .opt_bool = &print_version, .name = "version" },
+    { .opt_bool = &print_help, .name = "help" },
+    { .opt_string = &notmuch_requested_db_uuid, .name = "uuid" },
+    { }
+};
+
+/* any subcommand wanting to support these options should call
+ * inherit notmuch_shared_options and call
+ * notmuch_process_shared_options (subcommand_name);
+ */
+void
+notmuch_process_shared_options (const char *subcommand_name) {
+    if (print_version) {
+       printf ("notmuch " STRINGIFY(NOTMUCH_VERSION) "\n");
+       exit (EXIT_SUCCESS);
+    }
+
+    if (print_help) {
+       int ret = _help_for (subcommand_name);
+       exit (ret);
+    }
+}
+
+/* This is suitable for subcommands that do not actually open the
+ * database.
+ */
+int notmuch_minimal_options (const char *subcommand_name,
+                                 int argc, char **argv)
+{
+    int opt_index;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+
+    if (opt_index < 0)
+       return -1;
+
+    /* We can't use argv here as it is sometimes NULL */
+    notmuch_process_shared_options (subcommand_name);
+    return opt_index;
+}
+
+
+struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
+const notmuch_opt_desc_t  notmuch_shared_indexing_options [] = {
+    { .opt_keyword = &indexing_cli_choices.decrypt_policy,
+      .present = &indexing_cli_choices.decrypt_policy_set, .keywords =
+      (notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
+                             { "true", NOTMUCH_DECRYPT_TRUE },
+                             { "auto", NOTMUCH_DECRYPT_AUTO },
+                             { "nostash", NOTMUCH_DECRYPT_NOSTASH },
+                             { 0, 0 } },
+      .name = "decrypt" },
+    { }
+};
+
+
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
+{
+    if (indexing_cli_choices.opts == NULL)
+       indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
+    if (indexing_cli_choices.decrypt_policy_set) {
+       notmuch_status_t status;
+       if (indexing_cli_choices.opts == NULL)
+           return NOTMUCH_STATUS_OUT_OF_MEMORY;
+       status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts, indexing_cli_choices.decrypt_policy);
+       if (status != NOTMUCH_STATUS_SUCCESS) {
+           fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n",
+                    indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status));
+           notmuch_indexopts_destroy (indexing_cli_choices.opts);
+           indexing_cli_choices.opts = NULL;
+           return status;
+       }
+    }
+#if (GMIME_MAJOR_VERSION < 3)
+    if (indexing_cli_choices.opts && notmuch_indexopts_get_decrypt_policy (indexing_cli_choices.opts) != NOTMUCH_DECRYPT_FALSE) {
+       const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
+       if (gpg_path && strcmp(gpg_path, "gpg"))
+           fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
+                    "\tbut ignoring (use $PATH instead)\n", gpg_path);
+    }
+#endif
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+
+static command_t commands[] = {
+    { NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
+      "Notmuch main command." },
+    { "setup", notmuch_setup_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
+      "Interactively set up notmuch for first use." },
+    { "new", notmuch_new_command, NOTMUCH_CONFIG_OPEN,
+      "Find and import new messages to the notmuch database." },
+    { "insert", notmuch_insert_command, NOTMUCH_CONFIG_OPEN,
+      "Add a new message into the maildir and notmuch database." },
+    { "search", notmuch_search_command, NOTMUCH_CONFIG_OPEN,
+      "Search for messages matching the given search terms." },
+    { "address", notmuch_address_command, NOTMUCH_CONFIG_OPEN,
+      "Get addresses from messages matching the given search terms." },
+    { "show", notmuch_show_command, NOTMUCH_CONFIG_OPEN,
+      "Show all messages matching the search terms." },
+    { "count", notmuch_count_command, NOTMUCH_CONFIG_OPEN,
+      "Count messages matching the search terms." },
+    { "reply", notmuch_reply_command, NOTMUCH_CONFIG_OPEN,
+      "Construct a reply template for a set of messages." },
+    { "tag", notmuch_tag_command, NOTMUCH_CONFIG_OPEN,
+      "Add/remove tags for all messages matching the search terms." },
+    { "dump", notmuch_dump_command, NOTMUCH_CONFIG_OPEN,
+      "Create a plain-text dump of the tags for each message." },
+    { "restore", notmuch_restore_command, NOTMUCH_CONFIG_OPEN,
+      "Restore the tags from the given dump file (see 'dump')." },
+    { "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
+      "Compact the notmuch database." },
+    { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
+      "Re-index all messages matching the search terms." },
+    { "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
+      "Get or set settings in the notmuch configuration file." },
+#if WITH_EMACS
+    { "emacs-mua", NULL, 0,
+      "send mail with notmuch and emacs." },
+#endif
+    { "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
+      "This message, or more detailed help for the named command." }
+};
+
+typedef struct help_topic {
+    const char *name;
+    const char *summary;
+} help_topic_t;
+
+static help_topic_t help_topics[] = {
+    { "search-terms",
+      "Common search term syntax." },
+    { "hooks",
+      "Hooks that will be run before or after certain commands." },
+    { "properties",
+      "Message property conventions and documentation." },
+};
+
+static command_t *
+find_command (const char *name)
+{
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE (commands); i++)
+       if ((!name && !commands[i].name) ||
+           (name && commands[i].name && strcmp (name, commands[i].name) == 0))
+           return &commands[i];
+
+    return NULL;
+}
+
+int notmuch_format_version;
+
+static void
+usage (FILE *out)
+{
+    command_t *command;
+    help_topic_t *topic;
+    unsigned int i;
+
+    fprintf (out,
+            "Usage: notmuch --help\n"
+            "       notmuch --version\n"
+            "       notmuch <command> [args...]\n");
+    fprintf (out, "\n");
+    fprintf (out, "The available commands are as follows:\n");
+    fprintf (out, "\n");
+
+    for (i = 0; i < ARRAY_SIZE (commands); i++) {
+       command = &commands[i];
+
+       if (command->name)
+           fprintf (out, "  %-12s  %s\n", command->name, command->summary);
+    }
+
+    fprintf (out, "\n");
+    fprintf (out, "Additional help topics are as follows:\n");
+    fprintf (out, "\n");
+
+    for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
+       topic = &help_topics[i];
+       fprintf (out, "  %-12s  %s\n", topic->name, topic->summary);
+    }
+
+    fprintf (out, "\n");
+    fprintf (out,
+            "Use \"notmuch help <command or topic>\" for more details "
+            "on each command or topic.\n\n");
+}
+
+void
+notmuch_exit_if_unsupported_format (void)
+{
+    if (notmuch_format_version > NOTMUCH_FORMAT_CUR) {
+       fprintf (stderr, "\
+A caller requested output format version %d, but the installed notmuch\n\
+CLI only supports up to format version %d.  You may need to upgrade your\n\
+notmuch CLI.\n",
+                notmuch_format_version, NOTMUCH_FORMAT_CUR);
+       exit (NOTMUCH_EXIT_FORMAT_TOO_NEW);
+    } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN) {
+       fprintf (stderr, "\
+A caller requested output format version %d, which is no longer supported\n\
+by the notmuch CLI (it requires at least version %d).  You may need to\n\
+upgrade your notmuch front-end.\n",
+                notmuch_format_version, NOTMUCH_FORMAT_MIN);
+       exit (NOTMUCH_EXIT_FORMAT_TOO_OLD);
+    } else if (notmuch_format_version < NOTMUCH_FORMAT_MIN_ACTIVE) {
+       /* Warn about old version requests so compatibility issues are
+        * less likely when we drop support for a deprecated format
+        * versions. */
+       fprintf (stderr, "\
+A caller requested deprecated output format version %d, which may not\n\
+be supported in the future.\n", notmuch_format_version);
+    }
+}
+
+void
+notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch)
+{
+    const char *uuid = NULL;
+
+    if (!notmuch_requested_db_uuid)
+       return;
+    IGNORE_RESULT (notmuch_database_get_revision (notmuch, &uuid));
+
+    if (strcmp (notmuch_requested_db_uuid, uuid) != 0){
+       fprintf (stderr, "Error: requested database revision %s does not match %s\n",
+                notmuch_requested_db_uuid, uuid);
+       exit (1);
+    }
+}
+
+static void
+exec_man (const char *page)
+{
+    if (execlp ("man", "man", page, (char *) NULL)) {
+       perror ("exec man");
+       exit (1);
+    }
+}
+
+static int
+_help_for (const char *topic_name)
+{
+    command_t *command;
+    help_topic_t *topic;
+    unsigned int i;
+
+    if (!topic_name) {
+       printf ("The notmuch mail system.\n\n");
+       usage (stdout);
+       return EXIT_SUCCESS;
+    }
+
+    if (strcmp (topic_name, "help") == 0) {
+       printf ("The notmuch help system.\n\n"
+               "\tNotmuch uses the man command to display help. In case\n"
+               "\tof difficulties check that MANPATH includes the pages\n"
+               "\tinstalled by notmuch.\n\n"
+               "\tTry \"notmuch help\" for a list of topics.\n");
+       return EXIT_SUCCESS;
+    }
+
+    command = find_command (topic_name);
+    if (command) {
+       char *page = talloc_asprintf (NULL, "notmuch-%s", command->name);
+       exec_man (page);
+    }
+
+    for (i = 0; i < ARRAY_SIZE (help_topics); i++) {
+       topic = &help_topics[i];
+       if (strcmp (topic_name, topic->name) == 0) {
+           char *page = talloc_asprintf (NULL, "notmuch-%s", topic->name);
+           exec_man (page);
+       }
+    }
+
+    fprintf (stderr,
+            "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
+            topic_name);
+    return EXIT_FAILURE;
+}
+
+static int
+notmuch_help_command (unused (notmuch_config_t * config), int argc, char *argv[])
+{
+    int opt_index;
+
+    opt_index = notmuch_minimal_options ("help", argc, argv);
+    if (opt_index < 0)
+       return EXIT_FAILURE;
+
+    /* skip at least subcommand argument */
+    argc-= opt_index;
+    argv+= opt_index;
+
+    if (argc == 0) {
+       return _help_for (NULL);
+    }
+
+    return _help_for (argv[0]);
+}
+
+/* Handle the case of "notmuch" being invoked with no command
+ * argument. For now we just call notmuch_setup_command, but we plan
+ * to be more clever about this in the future.
+ */
+static int
+notmuch_command (notmuch_config_t *config,
+                unused(int argc), unused(char *argv[]))
+{
+    char *db_path;
+    struct stat st;
+
+    /* If the user has never configured notmuch, then run
+     * notmuch_setup_command which will give a nice welcome message,
+     * and interactively guide the user through the configuration. */
+    if (notmuch_config_is_new (config))
+       return notmuch_setup_command (config, 0, NULL);
+
+    /* Notmuch is already configured, but is there a database? */
+    db_path = talloc_asprintf (config, "%s/%s",
+                              notmuch_config_get_database_path (config),
+                              ".notmuch");
+    if (stat (db_path, &st)) {
+       if (errno != ENOENT) {
+           fprintf (stderr, "Error looking for notmuch database at %s: %s\n",
+                    db_path, strerror (errno));
+           return EXIT_FAILURE;
+       }
+       printf ("Notmuch is configured, but there's not yet a database at\n\n\t%s\n\n",
+               db_path);
+       printf ("You probably want to run \"notmuch new\" now to create that database.\n\n"
+               "Note that the first run of \"notmuch new\" can take a very long time\n"
+               "and that the resulting database will use roughly the same amount of\n"
+               "storage space as the email being indexed.\n\n");
+       return EXIT_SUCCESS;
+    }
+
+    printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
+           "At this point you can start exploring the functionality of notmuch by\n"
+           "using commands such as:\n\n"
+           "\tnotmuch search tag:inbox\n\n"
+           "\tnotmuch search to:\"%s\"\n\n"
+           "\tnotmuch search from:\"%s\"\n\n"
+           "\tnotmuch search subject:\"my favorite things\"\n\n"
+           "See \"notmuch help search\" for more details.\n\n"
+           "You can also use \"notmuch show\" with any of the thread IDs resulting\n"
+           "from a search. Finally, you may want to explore using a more sophisticated\n"
+           "interface to notmuch such as the emacs interface implemented in notmuch.el\n"
+           "or any other interface described at https://notmuchmail.org\n\n"
+           "And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
+           "Have fun, and may your inbox never have much mail.\n\n",
+           notmuch_config_get_user_name (config),
+           notmuch_config_get_user_primary_email (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 bool try_external_command(char *argv[])
+{
+    char *old_argv0 = argv[0];
+    bool 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[])
+{
+    void *local;
+    char *talloc_report;
+    const char *command_name = NULL;
+    command_t *command;
+    const char *config_file_name = NULL;
+    notmuch_config_t *config = NULL;
+    int opt_index;
+    int ret;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_string = &config_file_name, .name = "config" },
+       { .opt_inherit = notmuch_shared_options },
+       { }
+    };
+
+    talloc_enable_null_tracking ();
+
+    local = talloc_new (NULL);
+
+    g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
+#if !GLIB_CHECK_VERSION(2, 35, 1)
+    g_type_init ();
+#endif
+
+    /* Globally default to the current output format version. */
+    notmuch_format_version = NOTMUCH_FORMAT_CUR;
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+    if (opt_index < 0) {
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    if (opt_index < argc)
+       command_name = argv[opt_index];
+
+    notmuch_process_shared_options (command_name);
+
+    command = find_command (command_name);
+    /* if command->function is NULL, try external command */
+    if (!command || !command->function) {
+       /* 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;
+    }
+
+    config = notmuch_config_open (local, config_file_name, command->config_mode);
+    if (!config) {
+       ret = EXIT_FAILURE;
+       goto DONE;
+    }
+
+    ret = (command->function)(config, argc - opt_index, argv + opt_index);
+
+  DONE:
+    if (config)
+       notmuch_config_close (config);
+
+    talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
+    if (talloc_report && strcmp (talloc_report, "") != 0) {
+       /* this relies on the previous call to
+        * talloc_enable_null_tracking
+        */
+
+       FILE *report = fopen (talloc_report, "w");
+       if (report) {
+           talloc_report_full (NULL, report);
+       } else {
+           ret = 1;
+           fprintf (stderr, "ERROR: unable to write talloc log. ");
+           perror (talloc_report);
+       }
+    }
+
+    talloc_free (local);
+
+    return ret;
+}
diff --git a/notmuch.dirs b/notmuch.dirs
deleted file mode 100644 (file)
index e772481..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/bin
diff --git a/notmuch.docs b/notmuch.docs
deleted file mode 100644 (file)
index 50bd824..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-NEWS
-README
diff --git a/notmuch.install b/notmuch.install
deleted file mode 100644 (file)
index 2514ca6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-usr/bin/notmuch
-usr/bin/notmuch-emacs-mua
-usr/share/bash-completion
-usr/share/zsh/vendor-completions
diff --git a/notmuch.maintscript b/notmuch.maintscript
deleted file mode 100644 (file)
index 6f93feb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-rm_conffile /etc/emacs/site-start.d/50notmuch.el
diff --git a/notmuch.manpages b/notmuch.manpages
deleted file mode 100644 (file)
index f9fcb54..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-usr/share/man/man5/notmuch-hooks.5.gz
-usr/share/man/man1/notmuch-dump.1.gz
-usr/share/man/man1/notmuch-count.1.gz
-usr/share/man/man1/notmuch-compact.1.gz
-usr/share/man/man1/notmuch-emacs-mua.1.gz
-usr/share/man/man1/notmuch-new.1.gz
-usr/share/man/man1/notmuch.1.gz
-usr/share/man/man1/notmuch-reindex.1.gz
-usr/share/man/man1/notmuch-address.1.gz
-usr/share/man/man1/notmuch-tag.1.gz
-usr/share/man/man1/notmuch-reply.1.gz
-usr/share/man/man1/notmuch-search.1.gz
-usr/share/man/man1/notmuch-restore.1.gz
-usr/share/man/man1/notmuch-insert.1.gz
-usr/share/man/man1/notmuch-show.1.gz
-usr/share/man/man1/notmuch-config.1.gz
-usr/share/man/man7/notmuch-properties.7.gz
-usr/share/man/man7/notmuch-search-terms.7.gz
diff --git a/packaging/debian b/packaging/debian
new file mode 100644 (file)
index 0000000..c8e8ddd
--- /dev/null
@@ -0,0 +1,2 @@
+The Debian packaging exists in the top-level "debian" directory within
+this source-code repository.
diff --git a/packaging/fedora/notmuch.spec b/packaging/fedora/notmuch.spec
new file mode 100644 (file)
index 0000000..5146edd
--- /dev/null
@@ -0,0 +1,171 @@
+%global git 6b9a717c
+%global date %(date +%Y%m%d)
+
+# If you are doing a git snapshot:
+#
+# Release should be 1%{git}%{?dist}
+# Source0 should be notmuch-%{version}-%{git}.tar.gz
+# git version is generated by 'git show-ref --hash=8 HEAD'
+#
+# To create a tarball:
+#
+# git clone git://notmuchmail.org/git/notmuch
+# cd notmuch
+# git archive --format=tar --prefix=notmuch-0.4/ HEAD | gzip > notmuch-0.4-`git show-ref --hash=8 HEAD`.tar.gz
+#
+
+Name:           notmuch
+Version:        0.15.2
+Release:        3%{?dist}
+Summary:        Thread-based email index, search and tagging
+
+Group:          Applications/Internet
+License:        GPLv3+
+URL:            https://notmuchmail.org/
+
+Source0:        https://notmuchmail.org/releases/notmuch-%{version}.tar.gz
+
+BuildRequires:  xapian-core-devel gmime-devel libtalloc-devel
+BuildRequires:  zlib-devel emacs-el emacs-nox python ruby ruby-devel perl
+
+%description
+Fast system for indexing, searching, and tagging email.  Even if you
+receive 12000 messages per month or have on the order of millions of
+messages that you've been saving for decades, Notmuch will be able to
+quickly search all of it.
+
+Notmuch is not much of an email program. It doesn't receive messages
+(no POP or IMAP support). It doesn't send messages (no mail composer,
+no network code at all). And for what it does do (email search) that
+work is provided by an external library, Xapian. So if Notmuch
+provides no user interface and Xapian does all the heavy lifting, then
+what's left here? Not much.
+
+%package devel
+Summary:        Development libraries and header files for %{name}
+Group:          Development/Libraries
+Requires:       %{name} = %{version}-%{release}
+
+%description devel
+The %{name}-devel package contains libraries and header files for
+developing applications that use %{name}.
+
+%package -n emacs-notmuch
+Summary:        Not much support for Emacs
+Group:          Applications/Editors
+BuildArch:      noarch
+Requires:       %{name} = %{version}-%{release}, emacs(bin) >= %{_emacs_version}
+
+%description -n emacs-notmuch
+%{summary}.
+
+%package -n python-notmuch
+Summary:        Python bindings for notmuch
+Group:          Development/Libraries
+BuildArch:      noarch
+Requires:       %{name} = %{version}-%{release}
+
+%description -n python-notmuch
+%{summary}.
+
+%package -n notmuch-ruby
+Summary:        Ruby bindings for notmuch
+Group:          Development/Libraries
+Requires:       %{name} = %{version}-%{release}
+
+%description -n notmuch-ruby
+%{summary}.
+
+%package mutt
+Summary:        Notmuch (of a) helper for Mutt
+Group:          Development/Libraries
+BuildArch:      noarch
+Requires:       %{name} = %{version}-%{release}
+Requires:       perl(Term::ReadLine::Gnu)
+
+%description mutt
+notmuch-mutt provide integration among the Mutt mail user agent and
+the Notmuch mail indexer.
+
+%prep
+%setup -q
+
+%build
+./configure --prefix=%{_prefix} --libdir=%{_libdir} --sysconfdir=%{_sysconfdir} \
+    --mandir=%{_mandir} --includedir=%{_includedir} --emacslispdir=%{_emacs_sitelispdir}
+make %{?_smp_mflags} CFLAGS="%{optflags}"
+
+pushd bindings/python
+    python setup.py build
+popd
+
+pushd bindings/ruby
+    export CONFIGURE_ARGS="--with-cflags='%{optflags}'"
+    ruby extconf.rb --vendor
+    make
+popd
+
+pushd contrib/notmuch-mutt
+    make
+popd
+
+%install
+make install DESTDIR=%{buildroot}
+
+pushd bindings/python
+    python setup.py install -O1 --skip-build --root %{buildroot}
+popd
+
+pushd bindings/ruby
+    make install DESTDIR=%{buildroot}
+popd
+
+install contrib/notmuch-mutt/notmuch-mutt %{buildroot}%{_bindir}/notmuch-mutt
+install contrib/notmuch-mutt/notmuch-mutt.1 %{buildroot}%{_mandir}/man1/notmuch-mutt.1
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%doc AUTHORS COPYING COPYING-GPL-3 INSTALL README
+%{_sysconfdir}/bash_completion.d/notmuch
+%{_datadir}/zsh/functions/Completion/Unix/_notmuch
+%{_bindir}/notmuch
+%{_mandir}/man?/*
+%{_libdir}/libnotmuch.so.3*
+
+%files devel
+%{_libdir}/libnotmuch.so
+%{_includedir}/*
+
+%files -n emacs-notmuch
+%{_emacs_sitelispdir}/*
+
+%files -n python-notmuch
+%doc bindings/python/README
+%{python_sitelib}/*
+
+%files -n notmuch-ruby
+%{ruby_vendorarchdir}/*
+
+%files mutt
+%{_bindir}/notmuch-mutt
+%{_mandir}/man1/notmuch-mutt.1*
+
+%changelog
+* Sun Apr 28 2013 Felipe Contreras <felipe.contreras@gmail.com> - 0.15.2-3
+- Add ruby bingings
+
+* Sun Apr 28 2013 Felipe Contreras <felipe.contreras@gmail.com> - 0.15.2-2
+- Sync with Fedora
+
+* Sun Apr 28 2013 Felipe Contreras <felipe.contreras@gmail.com> - 0.15.2-1
+- Update to latest upstream
+
+* Tue Nov  2 2010 Scott Henson <shenson@redhat.com> - 0.4-1
+- New upstream release
+
+* Wed Nov 18 2009 Jeffrey C. Ollie <jeff@ocjtech.us> - 0.0-0.3.306635c2
+- First version
+
diff --git a/parse-time-string/Makefile b/parse-time-string/Makefile
new file mode 100644 (file)
index 0000000..fa25832
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/parse-time-string/Makefile.local b/parse-time-string/Makefile.local
new file mode 100644 (file)
index 0000000..53534f3
--- /dev/null
@@ -0,0 +1,12 @@
+dir := parse-time-string
+extra_cflags += -I$(srcdir)/$(dir)
+
+libparse-time-string_c_srcs := $(dir)/parse-time-string.c
+
+libparse-time-string_modules := $(libparse-time-string_c_srcs:.c=.o)
+
+$(dir)/libparse-time-string.a: $(libparse-time-string_modules)
+       $(call quiet,AR) rcs $@ $^
+
+SRCS := $(SRCS) $(libparse-time-string_c_srcs)
+CLEAN := $(CLEAN) $(libparse-time-string_modules) $(dir)/libparse-time-string.a
diff --git a/parse-time-string/README b/parse-time-string/README
new file mode 100644 (file)
index 0000000..300ff1f
--- /dev/null
@@ -0,0 +1,9 @@
+PARSE TIME STRING
+=================
+
+parse_time_string() is a date/time parser originally written for
+notmuch by Jani Nikula <jani@nikula.org>. However, there is nothing
+notmuch specific in it, and it should be kept reusable for other
+projects, and ready to be packaged on its own as needed. Please do not
+add dependencies on or references to anything notmuch specific. The
+parser should only depend on the C library.
diff --git a/parse-time-string/parse-time-string.c b/parse-time-string/parse-time-string.c
new file mode 100644 (file)
index 0000000..48ec5b0
--- /dev/null
@@ -0,0 +1,1504 @@
+/*
+ * parse time string - user friendly date and time parser
+ * Copyright © 2012 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 2 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/>.
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "compat.h"
+#include "parse-time-string.h"
+
+/*
+ * IMPLEMENTATION DETAILS
+ *
+ * At a high level, the parsing is done in two phases: 1) actual
+ * parsing of the input string and storing the parsed data into
+ * 'struct state', and 2) processing of the data in 'struct state'
+ * according to current time (or provided reference time) and
+ * rounding. This is evident in the main entry point function
+ * parse_time_string().
+ *
+ * 1) The parsing phase - parse_input()
+ *
+ * Parsing is greedy and happens from left to right. The parsing is as
+ * unambiguous as possible; only unambiguous date/time formats are
+ * accepted. Redundant or contradictory absolute date/time in the
+ * input (e.g. date specified multiple times/ways) is not
+ * accepted. Relative date/time on the other hand just accumulates if
+ * present multiple times (e.g. "5 days 5 days" just turns into 10
+ * days).
+ *
+ * Parsing decisions are made on the input format, not value. For
+ * example, "20/5/2005" fails because the recognized format here is
+ * MM/D/YYYY, even though the values would suggest DD/M/YYYY.
+ *
+ * Parsing is mostly stateless in the sense that parsing decisions are
+ * not made based on the values of previously parsed data, or whether
+ * certain data is present in the first place. (There are a few
+ * exceptions to the latter part, though, such as parsing of time zone
+ * that would otherwise look like plain time.)
+ *
+ * When the parser encounters a number that is not greedily parsed as
+ * part of a format, the interpretation is postponed until the next
+ * token is parsed. The parser for the next token may consume the
+ * previously postponed number. For example, when parsing "20 May" the
+ * meaning of "20" is not known until "May" is parsed. If the parser
+ * for the next token does not consume the postponed number, the
+ * number is handled as a "lone" number before parser for the next
+ * token finishes.
+ *
+ * 2) The processing phase - create_output()
+ *
+ * Once the parser in phase 1 has finished, 'struct state' contains
+ * all the information from the input string, and it's no longer
+ * needed. Since the parser does not even handle the concept of "now",
+ * the processing initializes the fields referring to the current
+ * date/time.
+ *
+ * If requested, the result is rounded towards past or future. The
+ * idea behind rounding is to support parsing date/time ranges in an
+ * obvious way. For example, for a range defined as two dates (without
+ * time), one would typically want to have an inclusive range from the
+ * beginning of start date to the end of the end date. The caller
+ * would use rounding towards past in the start date, and towards
+ * future in the end date.
+ *
+ * The absolute date and time is shifted by the relative date and
+ * time, and time zone adjustments are made. Daylight saving time
+ * (DST) is specifically *not* handled at all.
+ *
+ * Finally, the result is stored to time_t.
+ */
+
+#define unused(x) x __attribute__ ((unused))
+
+/* XXX: Redefine these to add i18n support. The keyword table uses
+ * N_() to mark strings to be translated; they are accessed
+ * dynamically using _(). */
+#define _(s) (s)       /* i18n: define as gettext (s) */
+#define N_(s) (s)      /* i18n: define as gettext_noop (s) */
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
+
+/*
+ * Field indices in the tm and set arrays of struct state.
+ *
+ * NOTE: There's some code that depends on the ordering of this enum.
+ */
+enum field {
+    /* Keep SEC...YEAR in this order. */
+    TM_ABS_SEC,                /* seconds */
+    TM_ABS_MIN,                /* minutes */
+    TM_ABS_HOUR,       /* hours */
+    TM_ABS_MDAY,       /* day of the month */
+    TM_ABS_MON,                /* month */
+    TM_ABS_YEAR,       /* year */
+
+    TM_WDAY,           /* day of the week. special: may be relative */
+    TM_ABS_ISDST,      /* daylight saving time */
+
+    TM_AMPM,           /* am vs. pm */
+    TM_TZ,             /* timezone in minutes */
+
+    /* Keep SEC...YEAR in this order. */
+    TM_REL_SEC,                /* seconds relative to absolute or reference time */
+    TM_REL_MIN,                /* minutes ... */
+    TM_REL_HOUR,       /* hours ... */
+    TM_REL_DAY,                /* days ... */
+    TM_REL_MON,                /* months ... */
+    TM_REL_YEAR,       /* years ... */
+    TM_REL_WEEK,       /* weeks ... */
+
+    TM_NONE,           /* not a field */
+
+    TM_SIZE = TM_NONE,
+    TM_FIRST_ABS = TM_ABS_SEC,
+    TM_FIRST_REL = TM_REL_SEC,
+};
+
+/* Values for the set array of struct state. */
+enum field_set {
+    FIELD_UNSET,       /* The field has not been touched by parser. */
+    FIELD_SET,         /* The field has been set by parser. */
+    FIELD_NOW,         /* The field will be set to reference time. */
+};
+
+static enum field
+next_abs_field (enum field field)
+{
+    /* NOTE: Depends on the enum ordering. */
+    return field < TM_ABS_YEAR ? field + 1 : TM_NONE;
+}
+
+static enum field
+abs_to_rel_field (enum field field)
+{
+    assert (field <= TM_ABS_YEAR);
+
+    /* NOTE: Depends on the enum ordering. */
+    return field + (TM_FIRST_REL - TM_FIRST_ABS);
+}
+
+/* Get the smallest acceptable value for field. */
+static int
+get_field_epoch_value (enum field field)
+{
+    if (field == TM_ABS_MDAY || field == TM_ABS_MON)
+       return 1;
+    else if (field == TM_ABS_YEAR)
+       return 1970;
+    else
+       return 0;
+}
+
+/* The parsing state. */
+struct state {
+    int tm[TM_SIZE];                   /* parsed date and time */
+    enum field_set set[TM_SIZE];       /* set status of tm */
+
+    enum field last_field;     /* Previously set field. */
+    char delim;
+
+    int postponed_length;      /* Number of digits in postponed value. */
+    int postponed_value;
+    char postponed_delim;      /* The delimiter preceding postponed number. */
+};
+
+/*
+ * Helpers for postponed numbers.
+ *
+ * postponed_length is the number of digits in postponed value. 0
+ * means there is no postponed number. -1 means there is a postponed
+ * number, but it comes from a keyword, and it doesn't have digits.
+ */
+static int
+get_postponed_length (struct state *state)
+{
+    return state->postponed_length;
+}
+
+/*
+ * Consume a previously postponed number. Return true if a number was
+ * in fact postponed, false otherwise. Store the postponed number's
+ * value in *v, length in the input string in *n (or -1 if the number
+ * was written out and parsed as a keyword), and the preceding
+ * delimiter to *d. If a number was not postponed, *v, *n and *d are
+ * unchanged.
+ */
+static bool
+consume_postponed_number (struct state *state, int *v, int *n, char *d)
+{
+    if (!state->postponed_length)
+       return false;
+
+    if (n)
+       *n = state->postponed_length;
+
+    if (v)
+       *v = state->postponed_value;
+
+    if (d)
+       *d = state->postponed_delim;
+
+    state->postponed_length = 0;
+    state->postponed_value = 0;
+    state->postponed_delim = 0;
+
+    return true;
+}
+
+static int parse_postponed_number (struct state *state, enum field next_field);
+
+/*
+ * Postpone a number to be handled later. If one exists already,
+ * handle it first. n may be -1 to indicate a keyword that has no
+ * number length.
+ */
+static int
+set_postponed_number (struct state *state, int v, int n)
+{
+    int r;
+    char d = state->delim;
+
+    /* Parse a previously postponed number, if any. */
+    r = parse_postponed_number (state, TM_NONE);
+    if (r)
+       return r;
+
+    state->postponed_length = n;
+    state->postponed_value = v;
+    state->postponed_delim = d;
+
+    return 0;
+}
+
+static void
+set_delim (struct state *state, char delim)
+{
+    state->delim = delim;
+}
+
+static void
+unset_delim (struct state *state)
+{
+    state->delim = 0;
+}
+
+/*
+ * Field set/get/mod helpers.
+ */
+
+/* Return true if field has been set. */
+static bool
+is_field_set (struct state *state, enum field field)
+{
+    assert (field < ARRAY_SIZE (state->tm));
+
+    return state->set[field] != FIELD_UNSET;
+}
+
+static void
+unset_field (struct state *state, enum field field)
+{
+    assert (field < ARRAY_SIZE (state->tm));
+
+    state->set[field] = FIELD_UNSET;
+    state->tm[field] = 0;
+}
+
+/*
+ * Set field to value. A field can only be set once to ensure the
+ * input does not contain redundant and potentially conflicting data.
+ */
+static int
+set_field (struct state *state, enum field field, int value)
+{
+    int r;
+
+    /* Fields can only be set once. */
+    if (is_field_set (state, field))
+       return -PARSE_TIME_ERR_ALREADYSET;
+
+    state->set[field] = FIELD_SET;
+
+    /* Parse a previously postponed number, if any. */
+    r = parse_postponed_number (state, field);
+    if (r)
+       return r;
+
+    unset_delim (state);
+
+    state->tm[field] = value;
+    state->last_field = field;
+
+    return 0;
+}
+
+/*
+ * Mark n fields in fields to be set to the reference date/time in the
+ * specified time zone, or local timezone if not specified. The fields
+ * will be initialized after parsing is complete and timezone is
+ * known.
+ */
+static int
+set_fields_to_now (struct state *state, enum field *fields, size_t n)
+{
+    size_t i;
+    int r;
+
+    for (i = 0; i < n; i++) {
+       r = set_field (state, fields[i], 0);
+       if (r)
+           return r;
+       state->set[fields[i]] = FIELD_NOW;
+    }
+
+    return 0;
+}
+
+/* Modify field by adding value to it. To be used on relative fields,
+ * which can be modified multiple times (to accumulate). */
+static int
+add_to_field (struct state *state, enum field field, int value)
+{
+    int r;
+
+    assert (field < ARRAY_SIZE (state->tm));
+
+    state->set[field] = FIELD_SET;
+
+    /* Parse a previously postponed number, if any. */
+    r = parse_postponed_number (state, field);
+    if (r)
+       return r;
+
+    unset_delim (state);
+
+    state->tm[field] += value;
+    state->last_field = field;
+
+    return 0;
+}
+
+/*
+ * Get field value. Make sure the field is set before query. It's most
+ * likely an error to call this while parsing (for example fields set
+ * as FIELD_NOW will only be set to some value after parsing).
+ */
+static int
+get_field (struct state *state, enum field field)
+{
+    assert (field < ARRAY_SIZE (state->tm));
+
+    return state->tm[field];
+}
+
+/*
+ * Validity checkers.
+ */
+static bool is_valid_12hour (int h)
+{
+    return h >= 1 && h <= 12;
+}
+
+static bool is_valid_time (int h, int m, int s)
+{
+    /* Allow 24:00:00 to denote end of day. */
+    if (h == 24 && m == 0 && s == 0)
+       return true;
+
+    return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59;
+}
+
+static bool is_valid_mday (int mday)
+{
+    return mday >= 1 && mday <= 31;
+}
+
+static bool is_valid_mon (int mon)
+{
+    return mon >= 1 && mon <= 12;
+}
+
+static bool is_valid_year (int year)
+{
+    return year >= 1970;
+}
+
+static bool is_valid_date (int year, int mon, int mday)
+{
+    return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday);
+}
+
+/* Unset indicator for time and date set helpers. */
+#define UNSET -1
+
+/* Time set helper. No input checking. Use UNSET (-1) to leave unset. */
+static int
+set_abs_time (struct state *state, int hour, int min, int sec)
+{
+    int r;
+
+    if (hour != UNSET) {
+       if ((r = set_field (state, TM_ABS_HOUR, hour)))
+           return r;
+    }
+
+    if (min != UNSET) {
+       if ((r = set_field (state, TM_ABS_MIN, min)))
+           return r;
+    }
+
+    if (sec != UNSET) {
+       if ((r = set_field (state, TM_ABS_SEC, sec)))
+           return r;
+    }
+
+    return 0;
+}
+
+/* Date set helper. No input checking. Use UNSET (-1) to leave unset. */
+static int
+set_abs_date (struct state *state, int year, int mon, int mday)
+{
+    int r;
+
+    if (year != UNSET) {
+       if ((r = set_field (state, TM_ABS_YEAR, year)))
+           return r;
+    }
+
+    if (mon != UNSET) {
+       if ((r = set_field (state, TM_ABS_MON, mon)))
+           return r;
+    }
+
+    if (mday != UNSET) {
+       if ((r = set_field (state, TM_ABS_MDAY, mday)))
+           return r;
+    }
+
+    return 0;
+}
+
+/*
+ * Keyword parsing and handling.
+ */
+struct keyword;
+typedef int (*setter_t)(struct state *state, struct keyword *kw);
+
+struct keyword {
+    const char *name;  /* keyword */
+    enum field field;  /* field to set, or FIELD_NONE if N/A */
+    int value;         /* value to set, or 0 if N/A */
+    setter_t set;      /* function to use for setting, if non-NULL */
+};
+
+/*
+ * Setter callback functions for keywords.
+ */
+static int
+kw_set_rel (struct state *state, struct keyword *kw)
+{
+    int multiplier = 1;
+
+    /* Get a previously set multiplier, if any. */
+    consume_postponed_number (state, &multiplier, NULL, NULL);
+
+    /* Accumulate relative field values. */
+    return add_to_field (state, kw->field, multiplier * kw->value);
+}
+
+static int
+kw_set_number (struct state *state, struct keyword *kw)
+{
+    /* -1 = no length, from keyword. */
+    return set_postponed_number (state, kw->value, -1);
+}
+
+static int
+kw_set_month (struct state *state, struct keyword *kw)
+{
+    int n = get_postponed_length (state);
+
+    /* Consume postponed number if it could be mday. This handles "20
+     * January". */
+    if (n == 1 || n == 2) {
+       int r, v;
+
+       consume_postponed_number (state, &v, NULL, NULL);
+
+       if (!is_valid_mday (v))
+           return -PARSE_TIME_ERR_INVALIDDATE;
+
+       r = set_field (state, TM_ABS_MDAY, v);
+       if (r)
+           return r;
+    }
+
+    return set_field (state, kw->field, kw->value);
+}
+
+static int
+kw_set_ampm (struct state *state, struct keyword *kw)
+{
+    int n = get_postponed_length (state);
+
+    /* Consume postponed number if it could be hour. This handles
+     * "5pm". */
+    if (n == 1 || n == 2) {
+       int r, v;
+
+       consume_postponed_number (state, &v, NULL, NULL);
+
+       if (!is_valid_12hour (v))
+           return -PARSE_TIME_ERR_INVALIDTIME;
+
+       r = set_abs_time (state, v, 0, 0);
+       if (r)
+           return r;
+    }
+
+    return set_field (state, kw->field, kw->value);
+}
+
+static int
+kw_set_timeofday (struct state *state, struct keyword *kw)
+{
+    return set_abs_time (state, kw->value, 0, 0);
+}
+
+static int
+kw_set_today (struct state *state, unused (struct keyword *kw))
+{
+    enum field fields[] = { TM_ABS_YEAR, TM_ABS_MON, TM_ABS_MDAY };
+
+    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
+}
+
+static int
+kw_set_now (struct state *state, unused (struct keyword *kw))
+{
+    enum field fields[] = { TM_ABS_HOUR, TM_ABS_MIN, TM_ABS_SEC };
+
+    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
+}
+
+static int
+kw_set_ordinal (struct state *state, struct keyword *kw)
+{
+    int n, v;
+
+    /* Require a postponed number. */
+    if (!consume_postponed_number (state, &v, &n, NULL))
+       return -PARSE_TIME_ERR_DATEFORMAT;
+
+    /* Ordinals are mday. */
+    if (n != 1 && n != 2)
+       return -PARSE_TIME_ERR_DATEFORMAT;
+
+    /* Be strict about st, nd, rd, and lax about th. */
+    if (strcasecmp (kw->name, "st") == 0 && v != 1 && v != 21 && v != 31)
+       return -PARSE_TIME_ERR_INVALIDDATE;
+    else if (strcasecmp (kw->name, "nd") == 0 && v != 2 && v != 22)
+       return -PARSE_TIME_ERR_INVALIDDATE;
+    else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23)
+       return -PARSE_TIME_ERR_INVALIDDATE;
+    else if (strcasecmp (kw->name, "th") == 0 && !is_valid_mday (v))
+       return -PARSE_TIME_ERR_INVALIDDATE;
+
+    return set_field (state, TM_ABS_MDAY, v);
+}
+
+static int
+kw_ignore (unused (struct state *state), unused (struct keyword *kw))
+{
+    return 0;
+}
+
+/*
+ * Accepted keywords.
+ *
+ * A keyword may optionally contain a '|' to indicate the minimum
+ * match length. Without one, full match is required. It's advisable
+ * to keep the minimum match parts unique across all keywords. If
+ * they're not, the first match wins.
+ *
+ * If keyword begins with '*', then the matching will be case
+ * sensitive. Otherwise the matching is case insensitive.
+ *
+ * If .set is NULL, the field specified by .field will be set to
+ * .value.
+ *
+ * Note: Observe how "m" and "mi" match minutes, "M" and "mo" and
+ * "mont" match months, but "mon" matches Monday.
+ */
+static struct keyword keywords[] = {
+    /* Weekdays. */
+    { N_("sun|day"),   TM_WDAY,        0,      NULL },
+    { N_("mon|day"),   TM_WDAY,        1,      NULL },
+    { N_("tue|sday"),  TM_WDAY,        2,      NULL },
+    { N_("wed|nesday"),        TM_WDAY,        3,      NULL },
+    { N_("thu|rsday"), TM_WDAY,        4,      NULL },
+    { N_("fri|day"),   TM_WDAY,        5,      NULL },
+    { N_("sat|urday"), TM_WDAY,        6,      NULL },
+
+    /* Months. */
+    { N_("jan|uary"),  TM_ABS_MON,     1,      kw_set_month },
+    { N_("feb|ruary"), TM_ABS_MON,     2,      kw_set_month },
+    { N_("mar|ch"),    TM_ABS_MON,     3,      kw_set_month },
+    { N_("apr|il"),    TM_ABS_MON,     4,      kw_set_month },
+    { N_("may"),       TM_ABS_MON,     5,      kw_set_month },
+    { N_("jun|e"),     TM_ABS_MON,     6,      kw_set_month },
+    { N_("jul|y"),     TM_ABS_MON,     7,      kw_set_month },
+    { N_("aug|ust"),   TM_ABS_MON,     8,      kw_set_month },
+    { N_("sep|tember"),        TM_ABS_MON,     9,      kw_set_month },
+    { N_("oct|ober"),  TM_ABS_MON,     10,     kw_set_month },
+    { N_("nov|ember"), TM_ABS_MON,     11,     kw_set_month },
+    { N_("dec|ember"), TM_ABS_MON,     12,     kw_set_month },
+
+    /* Durations. */
+    { N_("y|ears"),    TM_REL_YEAR,    1,      kw_set_rel },
+    { N_("mo|nths"),   TM_REL_MON,     1,      kw_set_rel },
+    { N_("*M"),                TM_REL_MON,     1,      kw_set_rel },
+    { N_("w|eeks"),    TM_REL_WEEK,    1,      kw_set_rel },
+    { N_("d|ays"),     TM_REL_DAY,     1,      kw_set_rel },
+    { N_("h|ours"),    TM_REL_HOUR,    1,      kw_set_rel },
+    { N_("hr|s"),      TM_REL_HOUR,    1,      kw_set_rel },
+    { N_("mi|nutes"),  TM_REL_MIN,     1,      kw_set_rel },
+    { N_("mins"),      TM_REL_MIN,     1,      kw_set_rel },
+    { N_("*m"),                TM_REL_MIN,     1,      kw_set_rel },
+    { N_("s|econds"),  TM_REL_SEC,     1,      kw_set_rel },
+    { N_("secs"),      TM_REL_SEC,     1,      kw_set_rel },
+
+    /* Numbers. */
+    { N_("one"),       TM_NONE,        1,      kw_set_number },
+    { N_("two"),       TM_NONE,        2,      kw_set_number },
+    { N_("three"),     TM_NONE,        3,      kw_set_number },
+    { N_("four"),      TM_NONE,        4,      kw_set_number },
+    { N_("five"),      TM_NONE,        5,      kw_set_number },
+    { N_("six"),       TM_NONE,        6,      kw_set_number },
+    { N_("seven"),     TM_NONE,        7,      kw_set_number },
+    { N_("eight"),     TM_NONE,        8,      kw_set_number },
+    { N_("nine"),      TM_NONE,        9,      kw_set_number },
+    { N_("ten"),       TM_NONE,        10,     kw_set_number },
+    { N_("dozen"),     TM_NONE,        12,     kw_set_number },
+    { N_("hundred"),   TM_NONE,        100,    kw_set_number },
+
+    /* Special number forms. */
+    { N_("this"),      TM_NONE,        0,      kw_set_number },
+    { N_("last"),      TM_NONE,        1,      kw_set_number },
+
+    /* Other special keywords. */
+    { N_("yesterday"), TM_REL_DAY,     1,      kw_set_rel },
+    { N_("today"),     TM_NONE,        0,      kw_set_today },
+    { N_("now"),       TM_NONE,        0,      kw_set_now },
+    { N_("noon"),      TM_NONE,        12,     kw_set_timeofday },
+    { N_("midnight"),  TM_NONE,        0,      kw_set_timeofday },
+    { N_("am"),                TM_AMPM,        0,      kw_set_ampm },
+    { N_("a.m."),      TM_AMPM,        0,      kw_set_ampm },
+    { N_("pm"),                TM_AMPM,        1,      kw_set_ampm },
+    { N_("p.m."),      TM_AMPM,        1,      kw_set_ampm },
+    { N_("st"),                TM_NONE,        0,      kw_set_ordinal },
+    { N_("nd"),                TM_NONE,        0,      kw_set_ordinal },
+    { N_("rd"),                TM_NONE,        0,      kw_set_ordinal },
+    { N_("th"),                TM_NONE,        0,      kw_set_ordinal },
+    { N_("ago"),               TM_NONE,        0,      kw_ignore },
+
+    /* Timezone codes: offset in minutes. XXX: Add more codes. */
+    { N_("pst"),       TM_TZ,          -8*60,  NULL },
+    { N_("mst"),       TM_TZ,          -7*60,  NULL },
+    { N_("cst"),       TM_TZ,          -6*60,  NULL },
+    { N_("est"),       TM_TZ,          -5*60,  NULL },
+    { N_("ast"),       TM_TZ,          -4*60,  NULL },
+    { N_("nst"),       TM_TZ,          -(3*60+30),     NULL },
+
+    { N_("gmt"),       TM_TZ,          0,      NULL },
+    { N_("utc"),       TM_TZ,          0,      NULL },
+
+    { N_("wet"),       TM_TZ,          0,      NULL },
+    { N_("cet"),       TM_TZ,          1*60,   NULL },
+    { N_("eet"),       TM_TZ,          2*60,   NULL },
+    { N_("fet"),       TM_TZ,          3*60,   NULL },
+
+    { N_("wat"),       TM_TZ,          1*60,   NULL },
+    { N_("cat"),       TM_TZ,          2*60,   NULL },
+    { N_("eat"),       TM_TZ,          3*60,   NULL },
+};
+
+/*
+ * Compare strings str and keyword. Return the number of matching
+ * chars on match, 0 for no match.
+ *
+ * All of the alphabetic characters (isalpha) in str up to the first
+ * non-alpha character (or end of string) must match the
+ * keyword. Consequently, the value returned on match is the number of
+ * consecutive alphabetic characters in str.
+ *
+ * Abbreviated match is accepted if the keyword contains a '|'
+ * character, and str matches keyword up to that character. Any alpha
+ * characters after that in str must still match the keyword following
+ * the '|' character. If no '|' is present, all of keyword must match.
+ *
+ * Excessive, consecutive, and misplaced (at the beginning or end) '|'
+ * characters in keyword are handled gracefully. Only the first one
+ * matters.
+ *
+ * If match_case is true, the matching is case sensitive.
+ */
+static size_t
+match_keyword (const char *str, const char *keyword, bool match_case)
+{
+    const char *s = str;
+    bool prefix_matched = false;
+
+    for (;;) {
+       while (*keyword == '|') {
+           prefix_matched = true;
+           keyword++;
+       }
+
+       if (!*s || !isalpha ((unsigned char) *s) || !*keyword)
+           break;
+
+       if (match_case) {
+           if (*s != *keyword)
+               return 0;
+       } else {
+           if (tolower ((unsigned char) *s) !=
+               tolower ((unsigned char) *keyword))
+               return 0;
+       }
+       s++;
+       keyword++;
+    }
+
+    /* did not match all of the keyword in input string */
+    if (*s && isalpha ((unsigned char) *s))
+       return 0;
+
+    /* did not match enough of keyword */
+    if (*keyword && !prefix_matched)
+       return 0;
+
+    return s - str;
+}
+
+/*
+ * Parse a keyword. Return < 0 on error, number of parsed chars on
+ * success.
+ */
+static ssize_t
+parse_keyword (struct state *state, const char *s)
+{
+    unsigned int i;
+    size_t n = 0;
+    struct keyword *kw = NULL;
+    int r;
+
+    for (i = 0; i < ARRAY_SIZE (keywords); i++) {
+       const char *keyword = _(keywords[i].name);
+       bool mcase = false;
+
+       /* Match case if keyword begins with '*'. */
+       if (*keyword == '*') {
+           mcase = true;
+           keyword++;
+       }
+
+       n = match_keyword (s, keyword, mcase);
+       if (n) {
+           kw = &keywords[i];
+           break;
+       }
+    }
+
+    if (!kw)
+       return -PARSE_TIME_ERR_KEYWORD;
+
+    if (kw->set)
+       r = kw->set (state, kw);
+    else
+       r = set_field (state, kw->field, kw->value);
+
+    if (r < 0)
+       return r;
+
+    return n;
+}
+
+/*
+ * Non-keyword parsers and their helpers.
+ */
+
+static int
+set_user_tz (struct state *state, char sign, int hour, int min)
+{
+    int tz = hour * 60 + min;
+
+    assert (sign == '+' || sign == '-');
+
+    if (hour < 0 || hour > 14 || min < 0 || min > 59 || min % 15)
+       return -PARSE_TIME_ERR_INVALIDTIME;
+
+    if (sign == '-')
+       tz = -tz;
+
+    return set_field (state, TM_TZ, tz);
+}
+
+/*
+ * Parse a previously postponed number if one exists. Independent
+ * parsing of a postponed number when it wasn't consumed during
+ * parsing of the following token.
+ */
+static int
+parse_postponed_number (struct state *state, unused (enum field next_field))
+{
+    int v, n;
+    char d;
+
+    /* Bail out if there's no postponed number. */
+    if (!consume_postponed_number (state, &v, &n, &d))
+       return 0;
+
+    if (n == 1 || n == 2) {
+       /* Notable exception: Previous field affects parsing. This
+        * handles "January 20". */
+       if (state->last_field == TM_ABS_MON) {
+           /* D[D] */
+           if (!is_valid_mday (v))
+               return -PARSE_TIME_ERR_INVALIDDATE;
+
+           return set_field (state, TM_ABS_MDAY, v);
+       } else if (n == 2) {
+           /* XXX: Only allow if last field is hour, min, or sec? */
+           if (d == '+' || d == '-') {
+               /* +/-HH */
+               return set_user_tz (state, d, v, 0);
+           }
+       }
+    } else if (n == 4) {
+       /* Notable exception: Value affects parsing. Time zones are
+        * always at most 1400 and we don't understand years before
+        * 1970. */
+       if (!is_valid_year (v)) {
+           if (d == '+' || d == '-') {
+               /* +/-HHMM */
+               return set_user_tz (state, d, v / 100, v % 100);
+           }
+       } else {
+           /* YYYY */
+           return set_field (state, TM_ABS_YEAR, v);
+       }
+    } else if (n == 6) {
+       /* HHMMSS */
+       int hour = v / 10000;
+       int min = (v / 100) % 100;
+       int sec = v % 100;
+
+       if (!is_valid_time (hour, min, sec))
+           return -PARSE_TIME_ERR_INVALIDTIME;
+
+       return set_abs_time (state, hour, min, sec);
+    } else if (n == 8) {
+       /* YYYYMMDD */
+       int year = v / 10000;
+       int mon = (v / 100) % 100;
+       int mday = v % 100;
+
+       if (!is_valid_date (year, mon, mday))
+           return -PARSE_TIME_ERR_INVALIDDATE;
+
+       return set_abs_date (state, year, mon, mday);
+    }
+
+    return -PARSE_TIME_ERR_FORMAT;
+}
+
+static int tm_get_field (const struct tm *tm, enum field field);
+
+static int
+set_timestamp (struct state *state, time_t t)
+{
+    struct tm tm;
+    enum field f;
+    int r;
+
+    if (gmtime_r (&t, &tm) == NULL)
+       return -PARSE_TIME_ERR_LIB;
+
+    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {
+       r = set_field (state, f, tm_get_field (&tm, f));
+       if (r)
+           return r;
+    }
+
+    r = set_field (state, TM_TZ, 0);
+    if (r)
+       return r;
+
+    /* XXX: Prevent TM_AMPM with timestamp, e.g. "@123456 pm" */
+
+    return 0;
+}
+
+/* Parse a single number. Typically postpone parsing until later. */
+static int
+parse_single_number (struct state *state, unsigned long v,
+                    unsigned long n)
+{
+    assert (n);
+
+    if (state->delim == '@')
+       return set_timestamp (state, (time_t) v);
+
+    if (v > INT_MAX)
+       return -PARSE_TIME_ERR_FORMAT;
+
+    return set_postponed_number (state, v, n);
+}
+
+static bool
+is_time_sep (char c)
+{
+    return c == ':';
+}
+
+static bool
+is_date_sep (char c)
+{
+    return c == '/' || c == '-' || c == '.';
+}
+
+static bool
+is_sep (char c)
+{
+    return is_time_sep (c) || is_date_sep (c);
+}
+
+/* Two-digit year: 00...69 is 2000s, 70...99 1900s, if n == 0 keep
+ * unset. */
+static int
+expand_year (unsigned long year, size_t n)
+{
+    if (n == 2) {
+       return (year < 70 ? 2000 : 1900) + year;
+    } else if (n == 4) {
+       return year;
+    } else {
+       return UNSET;
+    }
+}
+
+/* Parse a date number triplet. */
+static int
+parse_date (struct state *state, char sep,
+           unsigned long v1, unsigned long v2, unsigned long v3,
+           size_t n1, size_t n2, size_t n3)
+{
+    int year = UNSET, mon = UNSET, mday = UNSET;
+
+    assert (is_date_sep (sep));
+
+    switch (sep) {
+    case '/': /* Date: M[M]/D[D][/YY[YY]] or M[M]/YYYY */
+       if (n1 != 1 && n1 != 2)
+           return -PARSE_TIME_ERR_DATEFORMAT;
+
+       if ((n2 == 1 || n2 == 2) && (n3 == 0 || n3 == 2 || n3 == 4)) {
+           /* M[M]/D[D][/YY[YY]] */
+           year = expand_year (v3, n3);
+           mon = v1;
+           mday = v2;
+       } else if (n2 == 4 && n3 == 0) {
+           /* M[M]/YYYY */
+           year = v2;
+           mon = v1;
+       } else {
+           return -PARSE_TIME_ERR_DATEFORMAT;
+       }
+       break;
+
+    case '-': /* Date: YYYY-MM[-DD] or DD-MM[-YY[YY]] or MM-YYYY */
+       if (n1 == 4 && n2 == 2 && (n3 == 0 || n3 == 2)) {
+           /* YYYY-MM[-DD] */
+           year = v1;
+           mon = v2;
+           if (n3)
+               mday = v3;
+       } else if (n1 == 2 && n2 == 2 && (n3 == 0 || n3 == 2 || n3 == 4)) {
+           /* DD-MM[-YY[YY]] */
+           year = expand_year (v3, n3);
+           mon = v2;
+           mday = v1;
+       } else if (n1 == 2 && n2 == 4 && n3 == 0) {
+           /* MM-YYYY */
+           year = v2;
+           mon = v1;
+       } else {
+           return -PARSE_TIME_ERR_DATEFORMAT;
+       }
+       break;
+
+    case '.': /* Date: D[D].M[M][.[YY[YY]]] */
+       if ((n1 != 1 && n1 != 2) || (n2 != 1 && n2 != 2) ||
+           (n3 != 0 && n3 != 2 && n3 != 4))
+           return -PARSE_TIME_ERR_DATEFORMAT;
+
+       year = expand_year (v3, n3);
+       mon = v2;
+       mday = v1;
+       break;
+    }
+
+    if (year != UNSET && !is_valid_year (year))
+       return -PARSE_TIME_ERR_INVALIDDATE;
+
+    if (mon != UNSET && !is_valid_mon (mon))
+       return -PARSE_TIME_ERR_INVALIDDATE;
+
+    if (mday != UNSET && !is_valid_mday (mday))
+       return -PARSE_TIME_ERR_INVALIDDATE;
+
+    return set_abs_date (state, year, mon, mday);
+}
+
+/* Parse a time number triplet. */
+static int
+parse_time (struct state *state, char sep,
+           unsigned long v1, unsigned long v2, unsigned long v3,
+           size_t n1, size_t n2, size_t n3)
+{
+    assert (is_time_sep (sep));
+
+    if ((n1 != 1 && n1 != 2) || n2 != 2 || (n3 != 0 && n3 != 2))
+       return -PARSE_TIME_ERR_TIMEFORMAT;
+
+    /*
+     * Notable exception: Previously set fields affect
+     * parsing. Interpret (+|-)HH:MM as time zone only if hour and
+     * minute have been set.
+     *
+     * XXX: This could be fixed by restricting the delimiters
+     * preceding time. For '+' it would be justified, but for '-' it
+     * might be inconvenient. However prefer to allow '-' as an
+     * insignificant delimiter preceding time for convenience, and
+     * handle '+' the same way for consistency between positive and
+     * negative time zones.
+     */
+    if (is_field_set (state, TM_ABS_HOUR) &&
+       is_field_set (state, TM_ABS_MIN) &&
+       n1 == 2 && n2 == 2 && n3 == 0 &&
+       (state->delim == '+' || state->delim == '-')) {
+       return set_user_tz (state, state->delim, v1, v2);
+    }
+
+    if (!is_valid_time (v1, v2, n3 ? v3 : 0))
+       return -PARSE_TIME_ERR_INVALIDTIME;
+
+    return set_abs_time (state, v1, v2, n3 ? (int) v3 : UNSET);
+}
+
+/* strtoul helper that assigns length. */
+static unsigned long
+strtoul_len (const char *s, const char **endp, size_t *len)
+{
+    unsigned long val = strtoul (s, (char **) endp, 10);
+
+    *len = *endp - s;
+    return val;
+}
+
+/*
+ * Parse a (group of) number(s). Return < 0 on error, number of parsed
+ * chars on success.
+ */
+static ssize_t
+parse_number (struct state *state, const char *s)
+{
+    int r;
+    unsigned long v1, v2, v3 = 0;
+    size_t n1, n2, n3 = 0;
+    const char *p = s;
+    char sep;
+
+    v1 = strtoul_len (p, &p, &n1);
+
+    if (!is_sep (*p) || !isdigit ((unsigned char) *(p + 1))) {
+       /* A single number. */
+       r = parse_single_number (state, v1, n1);
+       if (r)
+           return r;
+
+       return p - s;
+    }
+
+    sep = *p;
+    v2 = strtoul_len (p + 1, &p, &n2);
+
+    /* A group of two or three numbers? */
+    if (*p == sep && isdigit ((unsigned char) *(p + 1)))
+       v3 = strtoul_len (p + 1, &p, &n3);
+
+    if (is_time_sep (sep))
+       r = parse_time (state, sep, v1, v2, v3, n1, n2, n3);
+    else
+       r = parse_date (state, sep, v1, v2, v3, n1, n2, n3);
+
+    if (r)
+       return r;
+
+    return p - s;
+}
+
+/*
+ * Parse delimiter(s). Throw away all except the last one, which is
+ * stored for parsing the next non-delimiter. Return < 0 on error,
+ * number of parsed chars on success.
+ *
+ * XXX: We might want to be more strict here.
+ */
+static ssize_t
+parse_delim (struct state *state, const char *s)
+{
+    const char *p = s;
+
+    /*
+     * Skip non-alpha and non-digit, and store the last for further
+     * processing.
+     */
+    while (*p && !isalnum ((unsigned char) *p)) {
+       set_delim (state, *p);
+       p++;
+    }
+
+    return p - s;
+}
+
+/*
+ * Parse a date/time string. Return < 0 on error, number of parsed
+ * chars on success.
+ */
+static ssize_t
+parse_input (struct state *state, const char *s)
+{
+    const char *p = s;
+    ssize_t n;
+    int r;
+
+    while (*p) {
+       if (isalpha ((unsigned char) *p)) {
+           n = parse_keyword (state, p);
+       } else if (isdigit ((unsigned char) *p)) {
+           n = parse_number (state, p);
+       } else {
+           n = parse_delim (state, p);
+       }
+
+       if (n <= 0) {
+           if (n == 0)
+               n = -PARSE_TIME_ERR;
+
+           return n;
+       }
+
+       p += n;
+    }
+
+    /* Parse a previously postponed number, if any. */
+    r = parse_postponed_number (state, TM_NONE);
+    if (r < 0)
+       return r;
+
+    return p - s;
+}
+
+/*
+ * Processing the parsed input.
+ */
+
+/*
+ * Initialize reference time to tm. Use time zone in state if
+ * specified, otherwise local time. Use now for reference time if
+ * non-NULL, otherwise current time.
+ */
+static int
+initialize_now (struct state *state, const time_t *ref, struct tm *tm)
+{
+    time_t t;
+
+    if (ref) {
+       t = *ref;
+    } else {
+       if (time (&t) == (time_t) -1)
+           return -PARSE_TIME_ERR_LIB;
+    }
+
+    if (is_field_set (state, TM_TZ)) {
+       /* Some other time zone. */
+
+       /* Adjust now according to the TZ. */
+       t += get_field (state, TM_TZ) * 60;
+
+       /* It's not gm, but this doesn't mess with the TZ. */
+       if (gmtime_r (&t, tm) == NULL)
+           return -PARSE_TIME_ERR_LIB;
+    } else {
+       /* Local time. */
+       if (localtime_r (&t, tm) == NULL)
+           return -PARSE_TIME_ERR_LIB;
+    }
+
+    return 0;
+}
+
+/*
+ * Normalize tm according to mktime(3); if structure members are
+ * outside their valid interval, they will be normalized (so that, for
+ * example, 40 October is changed into 9 November), and tm_wday and
+ * tm_yday are set to values determined from the contents of the other
+ * fields.
+ *
+ * Both mktime(3) and localtime_r(3) use local time, but they cancel
+ * each other out here, making this function agnostic to time zone.
+ */
+static int
+normalize_tm (struct tm *tm)
+{
+    time_t t = mktime (tm);
+
+    if (t == (time_t) -1)
+       return -PARSE_TIME_ERR_LIB;
+
+    if (!localtime_r (&t, tm))
+       return -PARSE_TIME_ERR_LIB;
+
+    return 0;
+}
+
+/* Get field out of a struct tm. */
+static int
+tm_get_field (const struct tm *tm, enum field field)
+{
+    switch (field) {
+    case TM_ABS_SEC:   return tm->tm_sec;
+    case TM_ABS_MIN:   return tm->tm_min;
+    case TM_ABS_HOUR:  return tm->tm_hour;
+    case TM_ABS_MDAY:  return tm->tm_mday;
+    case TM_ABS_MON:   return tm->tm_mon + 1; /* 0- to 1-based */
+    case TM_ABS_YEAR:  return 1900 + tm->tm_year;
+    case TM_WDAY:      return tm->tm_wday;
+    case TM_ABS_ISDST: return tm->tm_isdst;
+    default:
+       assert (false);
+       break;
+    }
+
+    return 0;
+}
+
+/* Modify hour according to am/pm setting. */
+static int
+fixup_ampm (struct state *state)
+{
+    int hour, hdiff = 0;
+
+    if (!is_field_set (state, TM_AMPM))
+       return 0;
+
+    if (!is_field_set (state, TM_ABS_HOUR))
+       return -PARSE_TIME_ERR_TIMEFORMAT;
+
+    hour = get_field (state, TM_ABS_HOUR);
+    if (!is_valid_12hour (hour))
+       return -PARSE_TIME_ERR_INVALIDTIME;
+
+    if (get_field (state, TM_AMPM)) {
+       /* 12pm is noon. */
+       if (hour != 12)
+           hdiff = 12;
+    } else {
+       /* 12am is midnight, beginning of day. */
+       if (hour == 12)
+           hdiff = -12;
+    }
+
+    add_to_field (state, TM_REL_HOUR, -hdiff);
+
+    return 0;
+}
+
+/* Combine absolute and relative fields, and round. */
+static int
+create_output (struct state *state, time_t *t_out, const time_t *ref,
+              int round)
+{
+    struct tm tm = { .tm_isdst = -1 };
+    struct tm now;
+    time_t t;
+    enum field f;
+    int r;
+    int week_round = PARSE_TIME_NO_ROUND;
+
+    r = initialize_now (state, ref, &now);
+    if (r)
+       return r;
+
+    /* Initialize fields flagged as "now" to reference time. */
+    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {
+       if (state->set[f] == FIELD_NOW) {
+           state->tm[f] = tm_get_field (&now, f);
+           state->set[f] = FIELD_SET;
+       }
+    }
+
+    /*
+     * If WDAY is set but MDAY is not, we consider WDAY relative
+     *
+     * XXX: This fails on stuff like "two months monday" because two
+     * months ago wasn't the same day as today. Postpone until we know
+     * date?
+     */
+    if (is_field_set (state, TM_WDAY) &&
+       !is_field_set (state, TM_ABS_MDAY)) {
+       int wday = get_field (state, TM_WDAY);
+       int today = tm_get_field (&now, TM_WDAY);
+       int rel_days;
+
+       if (today > wday)
+           rel_days = today - wday;
+       else
+           rel_days = today + 7 - wday;
+
+       /* This also prevents special week rounding from happening. */
+       add_to_field (state, TM_REL_DAY, rel_days);
+
+       unset_field (state, TM_WDAY);
+    }
+
+    r = fixup_ampm (state);
+    if (r)
+       return r;
+
+    /*
+     * Iterate fields from most accurate to least accurate, and set
+     * unset fields according to requested rounding.
+     */
+    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {
+       if (round != PARSE_TIME_NO_ROUND) {
+           enum field r = abs_to_rel_field (f);
+
+           if (is_field_set (state, f) || is_field_set (state, r)) {
+               if (round >= PARSE_TIME_ROUND_UP && f != TM_ABS_SEC) {
+                   /*
+                    * This is the most accurate field
+                    * specified. Round up adjusting it towards
+                    * future.
+                    */
+                   add_to_field (state, r, -1);
+
+                   /*
+                    * Go back a second if the result is to be used
+                    * for inclusive comparisons.
+                    */
+                   if (round == PARSE_TIME_ROUND_UP_INCLUSIVE)
+                       add_to_field (state, TM_REL_SEC, 1);
+               }
+               round = PARSE_TIME_NO_ROUND; /* No more rounding. */
+           } else {
+               if (f == TM_ABS_MDAY &&
+                   is_field_set (state, TM_REL_WEEK)) {
+                   /* Week is most accurate. */
+                   week_round = round;
+                   round = PARSE_TIME_NO_ROUND;
+               } else {
+                   set_field (state, f, get_field_epoch_value (f));
+               }
+           }
+       }
+
+       if (!is_field_set (state, f))
+           set_field (state, f, tm_get_field (&now, f));
+    }
+
+    /* Special case: rounding with week accuracy. */
+    if (week_round != PARSE_TIME_NO_ROUND) {
+       /* Temporarily set more accurate fields to now. */
+       set_field (state, TM_ABS_SEC, tm_get_field (&now, TM_ABS_SEC));
+       set_field (state, TM_ABS_MIN, tm_get_field (&now, TM_ABS_MIN));
+       set_field (state, TM_ABS_HOUR, tm_get_field (&now, TM_ABS_HOUR));
+       set_field (state, TM_ABS_MDAY, tm_get_field (&now, TM_ABS_MDAY));
+    }
+
+    /*
+     * Set all fields. They may contain out of range values before
+     * normalization by mktime(3).
+     */
+    tm.tm_sec = get_field (state, TM_ABS_SEC) - get_field (state, TM_REL_SEC);
+    tm.tm_min = get_field (state, TM_ABS_MIN) - get_field (state, TM_REL_MIN);
+    tm.tm_hour = get_field (state, TM_ABS_HOUR) - get_field (state, TM_REL_HOUR);
+    tm.tm_mday = get_field (state, TM_ABS_MDAY) -
+                get_field (state, TM_REL_DAY) - 7 * get_field (state, TM_REL_WEEK);
+    tm.tm_mon = get_field (state, TM_ABS_MON) - get_field (state, TM_REL_MON);
+    tm.tm_mon--; /* 1- to 0-based */
+    tm.tm_year = get_field (state, TM_ABS_YEAR) - get_field (state, TM_REL_YEAR) - 1900;
+
+    /*
+     * It's always normal time.
+     *
+     * XXX: This is probably not a solution that universally
+     * works. Just make sure DST is not taken into account. We don't
+     * want rounding to be affected by DST.
+     */
+    tm.tm_isdst = -1;
+
+    /* Special case: rounding with week accuracy. */
+    if (week_round != PARSE_TIME_NO_ROUND) {
+       /* Normalize to get proper tm.wday. */
+       r = normalize_tm (&tm);
+       if (r < 0)
+           return r;
+
+       /* Set more accurate fields back to zero. */
+       tm.tm_sec = 0;
+       tm.tm_min = 0;
+       tm.tm_hour = 0;
+       tm.tm_isdst = -1;
+
+       /* Monday is the true 1st day of week, but this is easier. */
+       if (week_round >= PARSE_TIME_ROUND_UP) {
+           tm.tm_mday += 7 - tm.tm_wday;
+           if (week_round == PARSE_TIME_ROUND_UP_INCLUSIVE)
+               tm.tm_sec--;
+       } else {
+           tm.tm_mday -= tm.tm_wday;
+       }
+    }
+
+    if (is_field_set (state, TM_TZ)) {
+       /* tm is in specified TZ, convert to UTC for timegm(3). */
+       tm.tm_min -= get_field (state, TM_TZ);
+       t = timegm (&tm);
+    } else {
+       /* tm is in local time. */
+       t = mktime (&tm);
+    }
+
+    if (t == (time_t) -1)
+       return -PARSE_TIME_ERR_LIB;
+
+    *t_out = t;
+
+    return 0;
+}
+
+/* Internally, all errors are < 0. parse_time_string() returns errors > 0. */
+#define EXTERNAL_ERR(r) (-r)
+
+int
+parse_time_string (const char *s, time_t *t, const time_t *ref, int round)
+{
+    struct state state = { .last_field = TM_NONE };
+    int r;
+
+    if (!s || !t)
+       return EXTERNAL_ERR (-PARSE_TIME_ERR);
+
+    r = parse_input (&state, s);
+    if (r < 0)
+       return EXTERNAL_ERR (r);
+
+    r = create_output (&state, t, ref, round);
+    if (r < 0)
+       return EXTERNAL_ERR (r);
+
+    return 0;
+}
diff --git a/parse-time-string/parse-time-string.h b/parse-time-string/parse-time-string.h
new file mode 100644 (file)
index 0000000..c394ecd
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * parse time string - user friendly date and time parser
+ * Copyright © 2012 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 2 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/>.
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#ifndef PARSE_TIME_STRING_H
+#define PARSE_TIME_STRING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+
+/* return values for parse_time_string() */
+enum {
+    PARSE_TIME_OK = 0,
+    PARSE_TIME_ERR,            /* unspecified error */
+    PARSE_TIME_ERR_LIB,                /* library call failed */
+    PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */
+    PARSE_TIME_ERR_FORMAT,     /* generic date/time format error */
+    PARSE_TIME_ERR_DATEFORMAT, /* date format error */
+    PARSE_TIME_ERR_TIMEFORMAT, /* time format error */
+    PARSE_TIME_ERR_INVALIDDATE,        /* date value error */
+    PARSE_TIME_ERR_INVALIDTIME,        /* time value error */
+    PARSE_TIME_ERR_KEYWORD,    /* unknown keyword */
+};
+
+/* round values for parse_time_string() */
+enum {
+    PARSE_TIME_ROUND_DOWN = -1,
+    PARSE_TIME_NO_ROUND = 0,
+    PARSE_TIME_ROUND_UP = 1,
+    PARSE_TIME_ROUND_UP_INCLUSIVE = 2,
+};
+
+/**
+ * parse_time_string() - user friendly date and time parser
+ * @s:         string to parse
+ * @t:         pointer to time_t to store parsed time in
+ * @ref:       pointer to time_t containing reference date/time, or NULL
+ * @round:     PARSE_TIME_NO_ROUND, PARSE_TIME_ROUND_DOWN, or
+ *             PARSE_TIME_ROUND_UP
+ *
+ * Parse a date/time string 's' and store the parsed date/time result
+ * in 't'.
+ *
+ * A reference date/time is used for determining the "date/time units"
+ * (roughly equivalent to struct tm members) not specified by 's'. If
+ * 'ref' is non-NULL, it must contain a pointer to a time_t to be used
+ * as reference date/time. Otherwise, the current time is used.
+ *
+ * If 's' does not specify a full date/time, the 'round' parameter
+ * specifies if and how the result should be rounded as follows:
+ *
+ *   PARSE_TIME_NO_ROUND: All date/time units that are not specified
+ *   by 's' are set to the corresponding unit derived from the
+ *   reference date/time.
+ *
+ *   PARSE_TIME_ROUND_DOWN: All date/time units that are more accurate
+ *   than the most accurate unit specified by 's' are set to the
+ *   smallest valid value for that unit. Rest of the unspecified units
+ *   are set as in PARSE_TIME_NO_ROUND.
+ *
+ *   PARSE_TIME_ROUND_UP: All date/time units that are more accurate
+ *   than the most accurate unit specified by 's' are set to the
+ *   smallest valid value for that unit. The most accurate unit
+ *   specified by 's' is incremented by one (and this is rolled over
+ *   to the less accurate units as necessary), unless the most
+ *   accurate unit is seconds. Rest of the unspecified units are set
+ *   as in PARSE_TIME_NO_ROUND.
+ *
+ *   PARSE_TIME_ROUND_UP_INCLUSIVE: Same as PARSE_TIME_ROUND_UP, minus
+ *   one second, unless the most accurate unit specified by 's' is
+ *   seconds. This is useful for callers that require a value for
+ *   inclusive comparison of the result.
+ *
+ * Return 0 (PARSE_TIME_OK) for successfully parsed date/time, or one
+ * of PARSE_TIME_ERR_* on error. 't' is not modified on error.
+ */
+int parse_time_string (const char *s, time_t *t, const time_t *ref, int round);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PARSE_TIME_STRING_H */
diff --git a/performance-test/.gitignore b/performance-test/.gitignore
new file mode 100644 (file)
index 0000000..8a5dabf
--- /dev/null
@@ -0,0 +1,4 @@
+/tmp.*/
+/log.*/
+/corpus/
+/notmuch.cache.*/
diff --git a/performance-test/M00-new.sh b/performance-test/M00-new.sh
new file mode 100755 (executable)
index 0000000..aab36e6
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+test_description='notmuch new'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+# ensure initial 'notmuch new' is run by memory_start
+uncache_database
+
+memory_start
+
+# run 'notmuch new' a second time, to test different code paths
+memory_run "notmuch new" "notmuch new"
+
+memory_done
diff --git a/performance-test/M01-dump-restore.sh b/performance-test/M01-dump-restore.sh
new file mode 100755 (executable)
index 0000000..32ab8dc
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+test_description='dump and restore'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'load nmbug tags' 'notmuch restore --accumulate --input=corpus.tags/nmbug.sup-dump'
+memory_run 'dump *' 'notmuch dump --output=tags.sup'
+memory_run 'restore *' 'notmuch restore --input=tags.sup'
+memory_run 'dump --format=batch-tag *' 'notmuch dump --format=batch-tag --output=tags.bt'
+memory_run 'restore --format=batch-tag *' 'notmuch restore --format=batch-tag --input=tags.bt'
+
+memory_done
diff --git a/performance-test/M02-show.sh b/performance-test/M02-show.sh
new file mode 100755 (executable)
index 0000000..2e218fd
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='show'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'show *' "notmuch show '*' 1>/dev/null"
+memory_run 'show --format=json *' "notmuch show --format=json '*' 1>/dev/null"
+memory_run 'show --format=sexp *' "notmuch show --format=sexp '*' 1>/dev/null"
+
+memory_done
diff --git a/performance-test/M03-search.sh b/performance-test/M03-search.sh
new file mode 100755 (executable)
index 0000000..343f5c7
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='search'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'search *' "notmuch search '*' 1>/dev/null"
+memory_run 'search --format=json *' "notmuch search --format=json '*' 1>/dev/null"
+memory_run 'search --format=sexp *' "notmuch search --format=sexp '*' 1>/dev/null"
+
+memory_done
diff --git a/performance-test/M04-reply.sh b/performance-test/M04-reply.sh
new file mode 100755 (executable)
index 0000000..3c1205d
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+test_description='search'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+for id in $(notmuch search --output=messages '*' | shuf -n 5); do
+    memory_run "reply $id" "notmuch reply \"$id\" 1>/dev/null"
+    memory_run "reply --format=json $id" "notmuch reply --format=json \"$id\" 1>/dev/null"
+    memory_run "reply --format=sexp $id" "notmuch reply --format=sexp \"$id\" 1>/dev/null"
+done
+
+memory_done
diff --git a/performance-test/M05-reindex.sh b/performance-test/M05-reindex.sh
new file mode 100755 (executable)
index 0000000..17e2c82
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+test_description='reindex'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'reindex *' "notmuch reindex '*'"
+
+memory_done
diff --git a/performance-test/M06-insert.sh b/performance-test/M06-insert.sh
new file mode 100755 (executable)
index 0000000..5ae0656
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+test_description='search'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+
+for count in {1..20}; do
+    generate_message "[file]=\"insert-$count\"" "[dir]='tmp/'"
+    memory_run "insert $count" "notmuch insert < $gen_msg_filename"
+done
+
+memory_done
diff --git a/performance-test/Makefile b/performance-test/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/performance-test/Makefile.local b/performance-test/Makefile.local
new file mode 100644 (file)
index 0000000..9dc260e
--- /dev/null
@@ -0,0 +1,44 @@
+# -*- makefile -*-
+
+dir := performance-test
+
+include $(srcdir)/$(dir)/version.sh
+
+TIME_TEST_SCRIPT := ${dir}/notmuch-time-test
+MEMORY_TEST_SCRIPT := ${dir}/notmuch-memory-test
+
+CORPUS_NAME := notmuch-email-corpus-$(PERFTEST_VERSION).tar.xz
+TXZFILE := ${dir}/download/${CORPUS_NAME}
+SIGFILE := ${TXZFILE}.asc
+DEFAULT_URL :=  https://notmuchmail.org/releases/${CORPUS_NAME}
+
+perf-test: time-test memory-test
+
+time-test: setup-perf-test all
+       @echo
+       $(TIME_TEST_SCRIPT) $(OPTIONS)
+
+memory-test: setup-perf-test all
+       @echo
+       $(MEMORY_TEST_SCRIPT) $(OPTIONS)
+
+
+.PHONY: download-corpus setup-perf-test
+
+# Note that this intentionally does not depend on download-corpus.
+setup-perf-test: $(TXZFILE)
+       gpg --verify $(SIGFILE)
+
+$(TXZFILE):
+       @printf "\nPlease download ${TXZFILE} using:\n\n"
+       @printf "\t%% make download-corpus\n\n"
+       @echo or see https://notmuchmail.org/corpus for download locations
+       @echo
+       @false
+
+download-corpus:
+       wget -O ${TXZFILE} ${DEFAULT_URL}
+
+CLEAN := $(CLEAN) $(dir)/tmp.* $(dir)/log.*
+DISTCLEAN := $(DISTCLEAN) $(dir)/corpus $(dir)/notmuch.cache.*
+DATACLEAN := $(DATACLEAN) $(TXZFILE)
diff --git a/performance-test/README b/performance-test/README
new file mode 100644 (file)
index 0000000..fbc6102
--- /dev/null
@@ -0,0 +1,90 @@
+Performance Tests
+-----------------
+
+This directory contains two kinds of performance tests: time tests,
+and memory tests. The former use gnu time, and the latter use
+valgrind.
+
+Pre-requisites
+--------------
+
+In addition to having notmuch, you need:
+
+- gpg
+- gnu tar
+- gnu time (for the time tests)
+- xz. Some speedup can be gotten by installing "pixz", but this is
+  probably only worthwhile if you are debugging the tests.
+- valgrind (for the memory tests)
+
+Getting set up to run tests:
+----------------------------
+
+First, you need to get the corpus.  If you don't already have the gpg
+key for David Bremner, run
+
+   % gpg --search 'david@tethera.net'
+
+This should get you a key with fingerprint
+
+    815B 6398 2A79 F8E7 C727  86C4 762B 57BB 7842 06AD
+
+(the last 8 digits are printed as the "key id").
+
+To fetch the actual corpus it should work to run
+
+   % make download-corpus
+
+In case that fails or is too slow, check
+
+   https://notmuchmail.org/corpus
+
+for a list of mirrors.
+
+Running tests
+-------------
+
+The easiest way to run performance tests is to say "make perf-test".
+This will run all time and memory tests.  Be aware that the memory
+tests are quite time consuming when run on the full corpus, and that
+depending on your interests it may be more sensible to run "make
+time-test" or "make memory-test".  You can also invoke one of the
+scripts notmuch-time-test or notmuch-memory-test or run a more
+specific subset of tests by simply invoking one of the executable
+scripts in this directory, (such as ./T00-new).  Each test script
+supports the following arguments
+
+--small / --medium / --large   Choose corpus size.
+--debug                                Enable debugging. In particular don't delete
+                               temporary directories.
+
+When using the make targets, you can pass arguments to all test
+scripts by defining the make variable OPTIONS.
+
+Writing tests
+-------------
+
+Have a look at "T01-dump-restore" for an example time test and
+"M00-new" for an example memory test. In both cases sourcing
+"perf-test-lib.sh" is mandatory.
+
+Basics:
+
+- '(time|memory)_start' unpacks the mail corpus and calls notmuch new if it
+   cannot find a cache of the appropriate corpus.
+- '(time|memory)_run' runs the command under time or valgrind. Currently
+  "memory_run" does not support i/o redirection in the command.
+- '(time|memory)_done' does the cleanup; comment it out or pass --debug to the
+  script to leave the temporary files around.
+
+Utility functions include
+
+- 'add_email_corpus' unpacks a set of messages and tags
+- 'cache_database': makes a snapshot of the current database
+- 'uncache_database': forces the next '(time|memory)_start' to rebuild the
+  database.
+
+Scripts are run in the order specified in notmuch-perf-test. In the
+future this order might be chosen automatically so please follow the
+convention of starting the name with 'T' or 'M' followed by two digits
+to specify the order.
diff --git a/performance-test/T00-new.sh b/performance-test/T00-new.sh
new file mode 100755 (executable)
index 0000000..6875012
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+test_description='notmuch new'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+uncache_database
+
+time_start
+
+for i in $(seq 2 6); do
+    time_run "notmuch new #$i" 'notmuch new'
+done
+
+time_done
diff --git a/performance-test/T01-dump-restore.sh b/performance-test/T01-dump-restore.sh
new file mode 100755 (executable)
index 0000000..12f12e6
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='dump and restore'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run 'load nmbug tags' 'notmuch restore --accumulate < corpus.tags/nmbug.sup-dump'
+time_run 'dump *' 'notmuch dump > tags.out'
+time_run 'restore *' 'notmuch restore < tags.out'
+
+time_done
diff --git a/performance-test/T02-tag.sh b/performance-test/T02-tag.sh
new file mode 100755 (executable)
index 0000000..8c5dfd6
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+test_description='tagging'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run 'tag * +new_tag' "notmuch tag +new_tag '*'"
+time_run 'tag * +existing_tag' "notmuch tag +new_tag '*'"
+time_run 'tag * -existing_tag' "notmuch tag -new_tag '*'"
+time_run 'tag * -missing_tag' "notmuch tag -new_tag '*'"
+
+time_done
diff --git a/performance-test/T03-reindex.sh b/performance-test/T03-reindex.sh
new file mode 100755 (executable)
index 0000000..d6d5c3c
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='tagging'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+
+time_done
diff --git a/performance-test/T04-thread-subquery.sh b/performance-test/T04-thread-subquery.sh
new file mode 100755 (executable)
index 0000000..665d5a6
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+test_description='thread subqueries'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run "search thread:{} ..." "notmuch search thread:{date:2010} and thread:{from:linus}"
+time_run "search thread:{} ..." "notmuch search thread:{date:2010} and thread:{from:linus}"
+time_run "search thread:{} ..." "notmuch search thread:{date:2010} and thread:{from:linus}"
+
+time_done
diff --git a/performance-test/download/.gitignore b/performance-test/download/.gitignore
new file mode 100644 (file)
index 0000000..5c35620
--- /dev/null
@@ -0,0 +1,2 @@
+/*.tar.gz
+/*.tar.xz
diff --git a/performance-test/download/notmuch-email-corpus-0.3.tar.xz.asc b/performance-test/download/notmuch-email-corpus-0.3.tar.xz.asc
new file mode 100644 (file)
index 0000000..f109e81
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+iJwEAAECAAYFAlC9a90ACgkQTiiN/0Um85nAMAP+LCWdKzolcl/KW+JcCd0Dk+9v
+0vvtBVEhBes0TbK6iWrxCV2OIuYG/RhnFlJTZ4MjgaTRxzDubpC+JktaJdLmIQUN
+B7ZIDMjFduCwmtyLiuu/00CjxJKUXm7vx+ULGpvp0uxFE/vaqGP997BHwBjjfBVm
+YX6BlLX1SV6TfENkuRE=
+=Mks5
+-----END PGP SIGNATURE-----
diff --git a/performance-test/download/notmuch-email-corpus-0.4.tar.xz.asc b/performance-test/download/notmuch-email-corpus-0.4.tar.xz.asc
new file mode 100644 (file)
index 0000000..72dedd8
--- /dev/null
@@ -0,0 +1,14 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.15 (GNU/Linux)
+
+iQGcBAABCAAGBQJSdaDkAAoJEPIClx2kp54sQ54L/ikkvF1fy88hjLitN59v6g2J
+vw85YNRifNHyp/UXI6nt2eXFzyWJiRHuvHFoBgmEsJVxauOKw61Gs2zd53x9Ear4
+MGcQWyiM1cnwX/nD7GvxRQNh33f+FEamTjg+QhG47K0A2YdLWcDC7r9GMatGT11x
+5KE24WQGOqtgQn/9qNtJvkiKIehpRiDTaW/QJ7mTCYeJFjIHJUY8dxyfiTtkJ0z7
+cJ6omehvWSw4STbEg65XJgqykxMdltNEavfvSbAT73FgmkkyXxul0s5hDZ/esd0n
+re3dyDxGt085POiAgPti05a4tJI5EQC2wLBUFri0s2JdMtazcD6yVuHNbVzZ4Do3
+nL/sgwKGUq5wRrPqPWp6HXtZ9zG+/V7hFNrr/l42qGrLqsSh0bqvEnUiwczZLBGy
+NEs4G8VjmfS2cMKePsWaekBAvFUtb47PSB6JIPwpCNvKXDrcCb28eOQVB2atgj1h
+9SktOtWYJhWIQp2YW9iae30Z6lhCcdPRRHTFMQq2nQ==
+=eSMY
+-----END PGP SIGNATURE-----
diff --git a/performance-test/notmuch-memory-test b/performance-test/notmuch-memory-test
new file mode 100755 (executable)
index 0000000..047aac7
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
+#
+# Adapted from a Makefile to a shell script by Carl Worth (2010)
+
+if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
+    echo "Error: The notmuch test suite requires a bash version >= 4.0"
+    echo "due to use of associative arrays within the test suite."
+    echo "Please try again with a newer bash (or help us fix the"
+    echo "test suite to be more portable). Thanks."
+    exit 1
+fi
+
+cd "$(dirname "$0")"
+
+for test in M*.sh; do
+    ./"$test" "$@"
+done
diff --git a/performance-test/notmuch-time-test b/performance-test/notmuch-time-test
new file mode 100755 (executable)
index 0000000..4dd21fe
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
+#
+# Adapted from a Makefile to a shell script by Carl Worth (2010)
+
+if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
+    echo "Error: The notmuch test suite requires a bash version >= 4.0"
+    echo "due to use of associative arrays within the test suite."
+    echo "Please try again with a newer bash (or help us fix the"
+    echo "test suite to be more portable). Thanks."
+    exit 1
+fi
+
+cd "$(dirname "$0")"
+
+for test in T*.sh; do
+    ./"$test" "$@"
+done
diff --git a/performance-test/perf-test-lib.sh b/performance-test/perf-test-lib.sh
new file mode 100644 (file)
index 0000000..b70288c
--- /dev/null
@@ -0,0 +1,219 @@
+. $(dirname "$0")/version.sh || exit 1
+
+corpus_size=large
+
+while test "$#" -ne 0
+do
+       case "$1" in
+       -d|--debug)
+               debug=t;
+               shift
+               ;;
+       -s|--small)
+               corpus_size=small;
+               shift
+               ;;
+       -m|--medium)
+               corpus_size=medium;
+               shift
+               ;;
+       -l|--large)
+               corpus_size=large;
+               shift
+               ;;
+       *)
+               echo "error: unknown performance test option '$1'" >&2; exit 1 ;;
+       esac
+done
+
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/../test/export-dirs.sh || exit 1
+
+# Where to run the tests
+TEST_DIRECTORY=$NOTMUCH_BUILDDIR/performance-test
+
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
+
+set -e
+
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
+       echo >&2 'You do not seem to have built notmuch yet.'
+       exit 1
+fi
+
+DB_CACHE_DIR=${TEST_DIRECTORY}/notmuch.cache.$corpus_size
+
+add_email_corpus ()
+{
+    rm -rf ${MAIL_DIR}
+
+    CORPUS_DIR=${TEST_DIRECTORY}/corpus
+    mkdir -p "${CORPUS_DIR}"
+
+    MAIL_CORPUS="${CORPUS_DIR}/mail.${corpus_size}"
+    TAG_CORPUS="${CORPUS_DIR}/tags"
+
+    if command -v pixz > /dev/null; then
+       XZ=pixz
+    else
+       XZ=xz
+    fi
+
+    if [ ! -d "${CORPUS_DIR}/manifest" ]; then
+
+       printf "Unpacking manifests\n"
+       tar --extract --use-compress-program ${XZ} --strip-components=1 \
+           --directory ${TEST_DIRECTORY}/corpus \
+           --wildcards --file ../download/notmuch-email-corpus-${PERFTEST_VERSION}.tar.xz \
+           'notmuch-email-corpus/manifest/*'
+    fi
+
+    file_list=$(mktemp file_listXXXXXX)
+    declare -a extract_dirs
+    if [ ! -d "$TAG_CORPUS" ] ; then
+       extract_dirs=("${extract_dirs[@]}" notmuch-email-corpus/tags)
+    fi
+
+    if [ ! -d "$MAIL_CORPUS" ] ; then
+       if [[ "$corpus_size" != "large" ]]; then
+           sed s,^,notmuch-email-corpus/, < \
+               ${TEST_DIRECTORY}/corpus/manifest/MANIFEST.${corpus_size} >> $file_list
+       else
+           extract_dirs=("${extract_dirs[@]}" notmuch-email-corpus/mail)
+       fi
+    fi
+
+    if [[ -s $file_list || -n "${extract_dirs[*]}" ]]; then
+
+       printf "Unpacking corpus\n"
+       tar --checkpoint=.5000 --extract --strip-components=1 \
+           --directory ${TEST_DIRECTORY}/corpus \
+           --use-compress-program ${XZ} \
+           --file ../download/notmuch-email-corpus-${PERFTEST_VERSION}.tar.xz \
+           --anchored --recursion \
+           --files-from $file_list "${extract_dirs[@]}"
+
+       printf "\n"
+
+       if [[ ! -d ${MAIL_CORPUS} ]]; then
+           printf "creating link farm\n"
+
+           if [[ "$corpus_size" = large ]]; then
+               cp -rl ${TEST_DIRECTORY}/corpus/mail ${MAIL_CORPUS}
+           else
+               while read -r file; do
+                   tdir=${MAIL_CORPUS}/$(dirname $file)
+                   mkdir -p $tdir
+                   ln ${TEST_DIRECTORY}/corpus/$file $tdir
+               done <${TEST_DIRECTORY}/corpus/manifest/MANIFEST.${corpus_size}
+           fi
+       fi
+
+    fi
+
+    rm $file_list
+    cp -lr $TAG_CORPUS $TMP_DIRECTORY/corpus.tags
+    cp -lr $MAIL_CORPUS $MAIL_DIR
+}
+
+notmuch_new_with_cache ()
+{
+    if [ -d $DB_CACHE_DIR ]; then
+       cp -r $DB_CACHE_DIR ${MAIL_DIR}/.notmuch
+    else
+       "$1" 'Initial notmuch new' "notmuch new"
+       cache_database
+    fi
+}
+
+time_start ()
+{
+    add_email_corpus
+
+    print_header
+
+    notmuch_new_with_cache time_run
+}
+
+memory_start ()
+{
+    add_email_corpus
+
+    local timestamp=$(date +%Y%m%dT%H%M%S)
+    log_dir="${TEST_DIRECTORY}/log.$(basename $0)-$corpus_size-${timestamp}"
+    mkdir -p ${log_dir}
+
+    notmuch_new_with_cache memory_run
+}
+
+memory_run ()
+{
+    test_count=$(($test_count+1))
+
+    log_file=$log_dir/$test_count.log
+    talloc_log=$log_dir/$test_count.talloc
+
+    printf "[ %d ]\t%s\n" $test_count "$1"
+
+    NOTMUCH_TALLOC_REPORT="$talloc_log" eval "valgrind --leak-check=full --log-file='$log_file' $2"
+
+    awk '/LEAK SUMMARY/,/suppressed/ { sub(/^==[0-9]*==/," "); print }' "$log_file"
+    echo
+    sed -n -e 's/.*[(]total *\([^)]*\)[)]/talloced at exit: \1/p' $talloc_log
+    echo
+}
+
+memory_done ()
+{
+    time_done
+}
+
+cache_database ()
+{
+    if [ -d $MAIL_DIR/.notmuch ]; then
+       cp -r $MAIL_DIR/.notmuch $DB_CACHE_DIR
+    else
+       echo "Warning: No database found to cache"
+    fi
+}
+
+uncache_database ()
+{
+    rm -rf $DB_CACHE_DIR
+}
+
+print_header ()
+{
+    printf "\t\t\tWall(s)\tUsr(s)\tSys(s)\tRes(K)\tIn/Out(512B)\n"
+}
+
+time_run ()
+{
+    printf "  %-22s" "$1"
+    test_count=$(($test_count+1))
+    if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi
+    if ! eval >&3 "/usr/bin/time -f '%e\t%U\t%S\t%M\t%I/%O' $2" ; then
+       test_failure=$(($test_failure + 1))
+       return 1
+    fi
+    return 0
+}
+
+time_done ()
+{
+    if [ "$test_failure" = "0" ]; then
+       rm -rf "$remove_tmp"
+       exit 0
+    else
+       exit 1
+    fi
+}
+
+cd -P "$test" || error "Cannot set up test environment"
+test_failure=0
+test_count=0
+
+printf "\n%-55s [%s %s]\n"  \
+    "$(basename "$0"): Testing ${test_description:-notmuch performance}" \
+    "${PERFTEST_VERSION}"  "${corpus_size}"
diff --git a/performance-test/version.sh b/performance-test/version.sh
new file mode 100644 (file)
index 0000000..f02527a
--- /dev/null
@@ -0,0 +1,3 @@
+# this should be both a valid Makefile fragment and valid POSIX(ish) shell.
+
+PERFTEST_VERSION=0.4
diff --git a/python-notmuch.install b/python-notmuch.install
deleted file mode 100644 (file)
index b2cc136..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/python2*
diff --git a/python3-notmuch.install b/python3-notmuch.install
deleted file mode 100644 (file)
index 4606faa..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/python3*
diff --git a/query-string.c b/query-string.c
new file mode 100644 (file)
index 0000000..cc8b27d
--- /dev/null
@@ -0,0 +1,56 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+/* Construct a single query string from the passed arguments, using
+ * 'ctx' as the talloc owner for all allocations.
+ *
+ * Currently, the arguments are just connected with space characters,
+ * but we might do more processing in the future, (such as inserting
+ * any AND operators needed to work around Xapian QueryParser bugs).
+ *
+ * This function returns NULL in case of insufficient memory.
+ */
+char *
+query_string_from_args (void *ctx, int argc, char *argv[])
+{
+    char *query_string;
+    int i;
+
+    query_string = talloc_strdup (ctx, "");
+    if (query_string == NULL)
+       return NULL;
+
+    for (i = 0; i < argc; i++) {
+       if (i != 0) {
+           query_string = talloc_strdup_append (query_string, " ");
+           if (query_string == NULL)
+               return NULL;
+       }
+
+       query_string = talloc_strdup_append (query_string, argv[i]);
+       if (query_string == NULL)
+           return NULL;
+    }
+
+    return query_string;
+}
+
diff --git a/ruby-notmuch.install b/ruby-notmuch.install
deleted file mode 100644 (file)
index d3f2105..0000000
+++ /dev/null
@@ -1 +0,0 @@
-usr/lib/*/*ruby/*/*/notmuch.so
diff --git a/rules b/rules
deleted file mode 100755 (executable)
index d056edb..0000000
--- a/rules
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/make -f
-
-python3_all = py3versions -s | xargs -n1 | xargs -t -I {} env {}
-
-%:
-       dh $@ --with python2,python3,elpa
-
-override_dh_auto_configure:
-       BASHCMD=/bin/bash ./configure --prefix=/usr \
-               --libdir=/usr/lib/$$(dpkg-architecture -q DEB_TARGET_MULTIARCH) \
-               --includedir=/usr/include \
-               --mandir=/usr/share/man \
-               --infodir=/usr/share/info \
-               --sysconfdir=/etc \
-               --zshcompletiondir=/usr/share/zsh/vendor-completions \
-               --localstatedir=/var
-
-override_dh_auto_build:
-       dh_auto_build -- V=1
-       dh_auto_build --sourcedirectory bindings/python
-       cd bindings/python && $(python3_all) setup.py build
-       $(MAKE) -C contrib/notmuch-mutt
-
-override_dh_auto_clean:
-       dh_auto_clean
-       dh_auto_clean --sourcedirectory bindings/python
-       cd bindings/python && $(python3_all) setup.py clean -a
-       dh_auto_clean --sourcedirectory bindings/ruby
-       $(MAKE) -C contrib/notmuch-mutt clean
-
-override_dh_auto_install:
-       dh_auto_install
-       dh_auto_install --sourcedirectory bindings/python
-       cd bindings/python && $(python3_all) setup.py install --install-layout=deb --root=$(CURDIR)/debian/tmp
-       $(MAKE) -C contrib/notmuch-mutt DESTDIR=$(CURDIR)/debian/tmp install
-       dh_auto_install --sourcedirectory bindings/ruby
diff --git a/source/format b/source/format
deleted file mode 100644 (file)
index 163aaf8..0000000
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/source/options b/source/options
deleted file mode 100644 (file)
index cc76e91..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-single-debian-patch
-tar-ignore=.git
-tar-ignore=performance-test/download/*.tar.xz
diff --git a/sprinter-json.c b/sprinter-json.c
new file mode 100644 (file)
index 0000000..c6ec857
--- /dev/null
@@ -0,0 +1,201 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+struct sprinter_json {
+    struct sprinter vtable;
+    FILE *stream;
+    /* Top of the state stack, or NULL if the printer is not currently
+     * inside any aggregate types. */
+    struct json_state *state;
+
+    /* A flag to signify that a separator should be inserted in the
+     * output as soon as possible.
+     */
+    bool insert_separator;
+};
+
+struct json_state {
+    struct json_state *parent;
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the comma before a value. */
+    bool first;
+    /* The character that closes the current aggregate. */
+    char close;
+};
+
+/* Helper function to set up the stream to print a value.  If this
+ * value follows another value, prints a comma. */
+static struct sprinter_json *
+json_begin_value (struct sprinter *sp)
+{
+    struct sprinter_json *spj = (struct sprinter_json *) sp;
+
+    if (spj->state) {
+       if (! spj->state->first) {
+           fputc (',', spj->stream);
+           if (spj->insert_separator) {
+               fputc ('\n', spj->stream);
+               spj->insert_separator = false;
+           } else {
+               fputc (' ', spj->stream);
+           }
+       } else {
+           spj->state->first = false;
+       }
+    }
+    return spj;
+}
+
+/* Helper function to begin an aggregate type.  Prints the open
+ * character and pushes a new state frame. */
+static void
+json_begin_aggregate (struct sprinter *sp, char open, char close)
+{
+    struct sprinter_json *spj = json_begin_value (sp);
+    struct json_state *state = talloc (spj, struct json_state);
+
+    fputc (open, spj->stream);
+    state->parent = spj->state;
+    state->first = true;
+    state->close = close;
+    spj->state = state;
+}
+
+static void
+json_begin_map (struct sprinter *sp)
+{
+    json_begin_aggregate (sp, '{', '}');
+}
+
+static void
+json_begin_list (struct sprinter *sp)
+{
+    json_begin_aggregate (sp, '[', ']');
+}
+
+static void
+json_end (struct sprinter *sp)
+{
+    struct sprinter_json *spj = (struct sprinter_json *) sp;
+    struct json_state *state = spj->state;
+
+    fputc (spj->state->close, spj->stream);
+    spj->state = state->parent;
+    talloc_free (state);
+    if (spj->state == NULL)
+       fputc ('\n', spj->stream);
+}
+
+/* This implementation supports embedded NULs as allowed by the JSON
+ * specification and Unicode.  Support for *parsing* embedded NULs
+ * varies, but is generally not a problem outside of C-based parsers
+ * (Python's json module and Emacs' json.el take embedded NULs in
+ * stride). */
+static void
+json_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    static const char *const escapes[] = {
+       ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
+       ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
+    };
+    struct sprinter_json *spj = json_begin_value (sp);
+
+    fputc ('"', spj->stream);
+    for (; len; ++val, --len) {
+       unsigned char ch = *val;
+       if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+           fputs (escapes[ch], spj->stream);
+       else if (ch >= 32)
+           fputc (ch, spj->stream);
+       else
+           fprintf (spj->stream, "\\u%04x", ch);
+    }
+    fputc ('"', spj->stream);
+}
+
+static void
+json_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+       val = "";
+    json_string_len (sp, val, strlen (val));
+}
+
+static void
+json_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_json *spj = json_begin_value (sp);
+
+    fprintf (spj->stream, "%d", val);
+}
+
+static void
+json_boolean (struct sprinter *sp, bool val)
+{
+    struct sprinter_json *spj = json_begin_value (sp);
+
+    fputs (val ? "true" : "false", spj->stream);
+}
+
+static void
+json_null (struct sprinter *sp)
+{
+    struct sprinter_json *spj = json_begin_value (sp);
+
+    fputs ("null", spj->stream);
+}
+
+static void
+json_map_key (struct sprinter *sp, const char *key)
+{
+    struct sprinter_json *spj = (struct sprinter_json *) sp;
+
+    json_string (sp, key);
+    fputs (": ", spj->stream);
+    spj->state->first = true;
+}
+
+static void
+json_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+json_separator (struct sprinter *sp)
+{
+    struct sprinter_json *spj = (struct sprinter_json *) sp;
+
+    spj->insert_separator = true;
+}
+
+struct sprinter *
+sprinter_json_create (const void *ctx, FILE *stream)
+{
+    static const struct sprinter_json template = {
+       .vtable = {
+           .begin_map = json_begin_map,
+           .begin_list = json_begin_list,
+           .end = json_end,
+           .string = json_string,
+           .string_len = json_string_len,
+           .integer = json_integer,
+           .boolean = json_boolean,
+           .null = json_null,
+           .map_key = json_map_key,
+           .separator = json_separator,
+           .set_prefix = json_set_prefix,
+           .is_text_printer = false,
+       }
+    };
+    struct sprinter_json *res;
+
+    res = talloc (ctx, struct sprinter_json);
+    if (! res)
+       return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
diff --git a/sprinter-sexp.c b/sprinter-sexp.c
new file mode 100644 (file)
index 0000000..6891ea4
--- /dev/null
@@ -0,0 +1,236 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Peter Feigl
+ *
+ * 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/ .
+ *
+ * Author: Peter Feigl <peter.feigl@gmx.at>
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+#include <ctype.h>
+
+struct sprinter_sexp {
+    struct sprinter vtable;
+    FILE *stream;
+    /* Top of the state stack, or NULL if the printer is not currently
+     * inside any aggregate types. */
+    struct sexp_state *state;
+
+    /* A flag to signify that a separator should be inserted in the
+     * output as soon as possible. */
+    bool insert_separator;
+};
+
+struct sexp_state {
+    struct sexp_state *parent;
+
+    /* True if nothing has been printed in this aggregate yet.
+     * Suppresses the space before a value. */
+    bool first;
+};
+
+/* Helper function to set up the stream to print a value.  If this
+ * value follows another value, prints a space. */
+static struct sprinter_sexp *
+sexp_begin_value (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    if (sps->state) {
+       if (! sps->state->first) {
+           if (sps->insert_separator) {
+               fputc ('\n', sps->stream);
+               sps->insert_separator = false;
+           } else {
+               fputc (' ', sps->stream);
+           }
+       } else {
+           sps->state->first = false;
+       }
+    }
+    return sps;
+}
+
+/* Helper function to begin an aggregate type.  Prints the open
+ * character and pushes a new state frame. */
+static void
+sexp_begin_aggregate (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+    struct sexp_state *state = talloc (sps, struct sexp_state);
+
+    fputc ('(', sps->stream);
+    state->parent = sps->state;
+    state->first = true;
+    sps->state = state;
+}
+
+static void
+sexp_begin_map (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp);
+}
+
+static void
+sexp_begin_list (struct sprinter *sp)
+{
+    sexp_begin_aggregate (sp);
+}
+
+static void
+sexp_end (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    struct sexp_state *state = sps->state;
+
+    fputc (')', sps->stream);
+    sps->state = state->parent;
+    talloc_free (state);
+    if (sps->state == NULL)
+       fputc ('\n', sps->stream);
+}
+
+static void
+sexp_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    /* Some characters need escaping. " and \ work fine in all Lisps,
+     * \n is not supported in CL, but all others work fine.
+     * Characters below 32 are printed as \123o (three-digit
+     * octals), which work fine in most Schemes and Emacs. */
+    static const char *const escapes[] = {
+       ['\"'] = "\\\"", ['\\'] = "\\\\",  ['\n'] = "\\n"
+    };
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputc ('"', sps->stream);
+    for (; len; ++val, --len) {
+       unsigned char ch = *val;
+       if (ch < ARRAY_SIZE (escapes) && escapes[ch])
+           fputs (escapes[ch], sps->stream);
+       else if (ch >= 32)
+           fputc (ch, sps->stream);
+       else
+           fprintf (sps->stream, "\\%03o", ch);
+    }
+    fputc ('"', sps->stream);
+}
+
+static void
+sexp_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+       val = "";
+    sexp_string_len (sp, val, strlen (val));
+}
+
+/* Prints a symbol, i.e. the name preceded by a colon. This should work
+ * in all Lisps, at least as a symbol, if not as a proper keyword */
+static void
+sexp_keyword (struct sprinter *sp, const char *val)
+{
+    unsigned int i = 0;
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+    char ch;
+
+    if (val == NULL)
+       INTERNAL_ERROR ("illegal symbol NULL");
+
+    for (i = 0; i < strlen (val); i++) {
+       ch = val[i];
+       if (! (isalnum (ch) || (ch == '-') || (ch == '_'))) {
+           INTERNAL_ERROR ("illegal character in symbol %s: %c", val, ch);
+       }
+    }
+    fputc (':', sps->stream);
+    fputs (val, sps->stream);
+}
+
+static void
+sexp_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fprintf (sps->stream, "%d", val);
+}
+
+static void
+sexp_boolean (struct sprinter *sp, bool val)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs (val ? "t" : "nil", sps->stream);
+}
+
+static void
+sexp_null (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = sexp_begin_value (sp);
+
+    fputs ("nil", sps->stream);
+}
+
+static void
+sexp_map_key (struct sprinter *sp, const char *key)
+{
+    sexp_begin_value (sp);
+
+    sexp_keyword (sp, key);
+}
+
+static void
+sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
+{
+}
+
+static void
+sexp_separator (struct sprinter *sp)
+{
+    struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
+
+    sps->insert_separator = true;
+}
+
+struct sprinter *
+sprinter_sexp_create (const void *ctx, FILE *stream)
+{
+    static const struct sprinter_sexp template = {
+       .vtable = {
+           .begin_map = sexp_begin_map,
+           .begin_list = sexp_begin_list,
+           .end = sexp_end,
+           .string = sexp_string,
+           .string_len = sexp_string_len,
+           .integer = sexp_integer,
+           .boolean = sexp_boolean,
+           .null = sexp_null,
+           .map_key = sexp_map_key,
+           .separator = sexp_separator,
+           .set_prefix = sexp_set_prefix,
+           .is_text_printer = false,
+       }
+    };
+    struct sprinter_sexp *res;
+
+    res = talloc (ctx, struct sprinter_sexp);
+    if (! res)
+       return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
diff --git a/sprinter-text.c b/sprinter-text.c
new file mode 100644 (file)
index 0000000..648b54b
--- /dev/null
@@ -0,0 +1,157 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <talloc.h>
+#include "sprinter.h"
+
+/* "Structured printer" interface for unstructured text printing.
+ * Note that --output=summary is dispatched and formatted in
+ * notmuch-search.c, the code in this file is only used for all other
+ * output types.
+ */
+
+struct sprinter_text {
+    struct sprinter vtable;
+    FILE *stream;
+
+    /* The current prefix to be printed with string/integer/boolean
+     * data.
+     */
+    const char *current_prefix;
+
+    /* A flag to indicate if this is the first tag. Used in list of tags
+     * for summary.
+     */
+    bool first_tag;
+};
+
+static void
+text_string_len (struct sprinter *sp, const char *val, size_t len)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    if (sptxt->current_prefix != NULL)
+       fprintf (sptxt->stream, "%s:", sptxt->current_prefix);
+
+    fwrite (val, len, 1, sptxt->stream);
+}
+
+static void
+text_string (struct sprinter *sp, const char *val)
+{
+    if (val == NULL)
+       val = "";
+    text_string_len (sp, val, strlen (val));
+}
+
+static void
+text_integer (struct sprinter *sp, int val)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    fprintf (sptxt->stream, "%d", val);
+}
+
+static void
+text_boolean (struct sprinter *sp, bool val)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    fputs (val ? "true" : "false", sptxt->stream);
+}
+
+static void
+text_separator (struct sprinter *sp)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    fputc ('\n', sptxt->stream);
+}
+
+static void
+text0_separator (struct sprinter *sp)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    fputc ('\0', sptxt->stream);
+}
+
+static void
+text_set_prefix (struct sprinter *sp, const char *prefix)
+{
+    struct sprinter_text *sptxt = (struct sprinter_text *) sp;
+
+    sptxt->current_prefix = prefix;
+}
+
+/* The structure functions begin_map, begin_list, end and map_key
+ * don't do anything in the text formatter.
+ */
+
+static void
+text_begin_map (unused (struct sprinter *sp))
+{
+}
+
+static void
+text_begin_list (unused (struct sprinter *sp))
+{
+}
+
+static void
+text_end (unused (struct sprinter *sp))
+{
+}
+
+static void
+text_null (unused (struct sprinter *sp))
+{
+}
+
+static void
+text_map_key (unused (struct sprinter *sp), unused (const char *key))
+{
+}
+
+struct sprinter *
+sprinter_text_create (const void *ctx, FILE *stream)
+{
+    static const struct sprinter_text template = {
+       .vtable = {
+           .begin_map = text_begin_map,
+           .begin_list = text_begin_list,
+           .end = text_end,
+           .string = text_string,
+           .string_len = text_string_len,
+           .integer = text_integer,
+           .boolean = text_boolean,
+           .null = text_null,
+           .map_key = text_map_key,
+           .separator = text_separator,
+           .set_prefix = text_set_prefix,
+           .is_text_printer = true,
+       },
+    };
+    struct sprinter_text *res;
+
+    res = talloc (ctx, struct sprinter_text);
+    if (! res)
+       return NULL;
+
+    *res = template;
+    res->stream = stream;
+    return &res->vtable;
+}
+
+struct sprinter *
+sprinter_text0_create (const void *ctx, FILE *stream)
+{
+    struct sprinter *sp;
+
+    sp = sprinter_text_create (ctx, stream);
+    if (! sp)
+       return NULL;
+
+    sp->separator = text0_separator;
+
+    return sp;
+}
diff --git a/sprinter.h b/sprinter.h
new file mode 100644 (file)
index 0000000..9d2e9b6
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef NOTMUCH_SPRINTER_H
+#define NOTMUCH_SPRINTER_H
+
+/* Necessary for bool */
+#include "notmuch-client.h"
+
+/* Structure printer interface. This is used to create output
+ * structured as maps (with key/value pairs), lists and primitives
+ * (strings, integers and booleans).
+ */
+typedef struct sprinter {
+    /* Start a new map/dictionary structure. This should be followed by
+     * a sequence of alternating calls to map_key and one of the
+     * value-printing functions until the map is ended by end.
+     */
+    void (*begin_map) (struct sprinter *);
+
+    /* Start a new list/array structure.
+     */
+    void (*begin_list) (struct sprinter *);
+
+    /* End the last opened list or map structure.
+     */
+    void (*end) (struct sprinter *);
+
+    /* Print one string/integer/boolean/null element (possibly inside
+     * a list or map, followed or preceded by separators).  For string
+     * and string_len, the char * must be UTF-8 encoded.  string_len
+     * allows non-terminated strings and strings with embedded NULs
+     * (though the handling of the latter is format-dependent). For
+     * string (but not string_len) the string pointer passed may be
+     * NULL.
+     */
+    void (*string) (struct sprinter *, const char *);
+    void (*string_len) (struct sprinter *, const char *, size_t);
+    void (*integer) (struct sprinter *, int);
+    void (*boolean) (struct sprinter *, bool);
+    void (*null) (struct sprinter *);
+
+    /* Print the key of a map's key/value pair. The char * must be UTF-8
+     * encoded.
+     */
+    void (*map_key) (struct sprinter *, const char *);
+
+    /* Insert a separator (usually extra whitespace). For the text
+     * printers, this is a syntactic separator. For the structured
+     * printers, this is for improved readability without affecting
+     * the abstract syntax of the structure being printed. For JSON,
+     * this could simply be a line break.
+     */
+    void (*separator) (struct sprinter *);
+
+    /* Set the current string prefix. This only affects the text
+     * printer, which will print this string, followed by a colon,
+     * before any string. For other printers, this does nothing.
+     */
+    void (*set_prefix) (struct sprinter *, const char *);
+
+    /* True if this is the special-cased plain text printer.
+     */
+    bool is_text_printer;
+} sprinter_t;
+
+
+/* Create a new unstructured printer that emits the default text format
+ * for "notmuch search". */
+struct sprinter *
+sprinter_text_create (const void *ctx, FILE *stream);
+
+/* Create a new unstructured printer that emits the text format for
+ * "notmuch search", with each field separated by a null character
+ * instead of the newline character. */
+struct sprinter *
+sprinter_text0_create (const void *ctx, FILE *stream);
+
+/* Create a new structure printer that emits JSON. */
+struct sprinter *
+sprinter_json_create (const void *ctx, FILE *stream);
+
+/* Create a new structure printer that emits S-Expressions. */
+struct sprinter *
+sprinter_sexp_create (const void *ctx, FILE *stream);
+
+#endif // NOTMUCH_SPRINTER_H
diff --git a/status.c b/status.c
new file mode 100644 (file)
index 0000000..01fd8c9
--- /dev/null
+++ b/status.c
@@ -0,0 +1,74 @@
+#include "notmuch-client.h"
+
+notmuch_status_t
+print_status_query (const char *loc,
+                   const notmuch_query_t *query,
+                   notmuch_status_t status)
+{
+    if (status) {
+       const char *msg;
+       notmuch_database_t *notmuch;
+
+       fprintf (stderr, "%s: %s\n", loc,
+                notmuch_status_to_string (status));
+
+       notmuch = notmuch_query_get_database (query);
+       msg = notmuch_database_status_string (notmuch);
+       if (msg)
+           fputs (msg, stderr);
+    }
+    return status;
+}
+
+notmuch_status_t
+print_status_message (const char *loc,
+                     const notmuch_message_t *message,
+                     notmuch_status_t status)
+{
+    if (status) {
+       const char *msg;
+       notmuch_database_t *notmuch;
+
+       fprintf (stderr, "%s: %s\n", loc,
+                notmuch_status_to_string (status));
+
+       notmuch = notmuch_message_get_database (message);
+       msg = notmuch_database_status_string (notmuch);
+       if (msg)
+           fputs (msg, stderr);
+    }
+    return status;
+}
+
+notmuch_status_t
+print_status_database (const char *loc,
+                   const notmuch_database_t *notmuch,
+                   notmuch_status_t status)
+{
+    if (status) {
+       const char *msg;
+
+       fprintf (stderr, "%s: %s\n", loc,
+                notmuch_status_to_string (status));
+       msg = notmuch_database_status_string (notmuch);
+       if (msg)
+           fputs (msg, stderr);
+    }
+    return status;
+}
+
+int
+status_to_exit (notmuch_status_t status)
+{
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+       return EXIT_SUCCESS;
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+    case NOTMUCH_STATUS_FILE_ERROR:
+       return EX_TEMPFAIL;
+    default:
+       return EXIT_FAILURE;
+    }
+}
diff --git a/tag-util.c b/tag-util.c
new file mode 100644 (file)
index 0000000..1837b1a
--- /dev/null
@@ -0,0 +1,426 @@
+#include <assert.h>
+#include "string-util.h"
+#include "tag-util.h"
+#include "hex-escape.h"
+
+#define TAG_OP_LIST_INITIAL_SIZE 10
+
+struct _tag_operation_t {
+    const char *tag;
+    bool remove;
+};
+
+struct _tag_op_list_t {
+    tag_operation_t *ops;
+    size_t count;
+    size_t size;
+};
+
+static tag_parse_status_t
+line_error (tag_parse_status_t status,
+           const char *line,
+           const char *format, ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    fprintf (stderr, status < 0 ? "Error: " : "Warning: ");
+    vfprintf (stderr, format, va_args);
+    fprintf (stderr, " [%s]\n", line);
+
+    va_end (va_args);
+
+    return status;
+}
+
+const char *
+illegal_tag (const char *tag, bool remove)
+{
+    if (*tag == '\0' && ! remove)
+       return "empty tag forbidden";
+
+    /* This disallows adding tags starting with "-", in particular the
+     * non-removable tag "-" and enables notmuch tag to take long
+     * options more easily.
+     */
+
+    if (*tag == '-' && ! remove)
+       return "tag starting with '-' forbidden";
+
+    return NULL;
+}
+
+tag_parse_status_t
+parse_tag_line (void *ctx, char *line,
+               tag_op_flag_t flags,
+               char **query_string,
+               tag_op_list_t *tag_ops)
+{
+    char *tok = line;
+    size_t tok_len = 0;
+    char *line_for_error;
+    tag_parse_status_t ret = TAG_PARSE_SUCCESS;
+
+    chomp_newline (line);
+
+    line_for_error = talloc_strdup (ctx, line);
+    if (line_for_error == NULL) {
+       fprintf (stderr, "Error: out of memory\n");
+       return TAG_PARSE_OUT_OF_MEMORY;
+    }
+
+    /* remove leading space */
+    while (*tok == ' ' || *tok == '\t')
+       tok++;
+
+    /* Skip empty and comment lines. */
+    if (*tok == '\0' || *tok == '#') {
+       ret = TAG_PARSE_SKIPPED;
+       goto DONE;
+    }
+
+    tag_op_list_reset (tag_ops);
+
+    /* Parse tags. */
+    while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {
+       bool remove;
+       char *tag;
+
+       /* Optional explicit end of tags marker. */
+       if (tok_len == 2 && strncmp (tok, "--", tok_len) == 0) {
+           tok = strtok_len (tok + tok_len, " ", &tok_len);
+           if (tok == NULL) {
+               ret = line_error (TAG_PARSE_INVALID, line_for_error,
+                                 "no query string after --");
+               goto DONE;
+           }
+           break;
+       }
+
+       /* Implicit end of tags. */
+       if (*tok != '-' && *tok != '+')
+           break;
+
+       /* If tag is terminated by NUL, there's no query string. */
+       if (*(tok + tok_len) == '\0') {
+           ret = line_error (TAG_PARSE_INVALID, line_for_error,
+                             "no query string");
+           goto DONE;
+       }
+
+       /* Terminate, and start next token after terminator. */
+       *(tok + tok_len++) = '\0';
+
+       remove = (*tok == '-');
+       tag = tok + 1;
+
+       /* Maybe refuse illegal tags. */
+       if (! (flags & TAG_FLAG_BE_GENEROUS)) {
+           const char *msg = illegal_tag (tag, remove);
+           if (msg) {
+               ret = line_error (TAG_PARSE_INVALID, line_for_error, msg);
+               goto DONE;
+           }
+       }
+
+       /* Decode tag. */
+       if (hex_decode_inplace (tag) != HEX_SUCCESS) {
+           ret = line_error (TAG_PARSE_INVALID, line_for_error,
+                             "hex decoding of tag %s failed", tag);
+           goto DONE;
+       }
+
+       if (tag_op_list_append (tag_ops, tag, remove)) {
+           ret = line_error (TAG_PARSE_OUT_OF_MEMORY, line_for_error,
+                             "aborting");
+           goto DONE;
+       }
+    }
+
+    if (tok == NULL) {
+       /* use a different error message for testing */
+       ret = line_error (TAG_PARSE_INVALID, line_for_error,
+                         "missing query string");
+       goto DONE;
+    }
+
+    /* tok now points to the query string */
+    *query_string = tok;
+
+  DONE:
+    talloc_free (line_for_error);
+    return ret;
+}
+
+tag_parse_status_t
+parse_tag_command_line (void *ctx, int argc, char **argv,
+                       char **query_str, tag_op_list_t *tag_ops)
+{
+    int i;
+
+    for (i = 0; i < argc; i++) {
+       if (strcmp (argv[i], "--") == 0) {
+           i++;
+           break;
+       }
+
+       if (argv[i][0] != '+' && argv[i][0] != '-')
+           break;
+
+       bool is_remove = argv[i][0] == '-';
+       const char *msg;
+
+       msg = illegal_tag (argv[i] + 1, is_remove);
+       if (msg) {
+           fprintf (stderr, "Error: %s\n", msg);
+           return TAG_PARSE_INVALID;
+       }
+
+       tag_op_list_append (tag_ops, argv[i] + 1, is_remove);
+    }
+
+    *query_str = query_string_from_args (ctx, argc - i, &argv[i]);
+
+    if (*query_str == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       return TAG_PARSE_OUT_OF_MEMORY;
+    }
+
+    return TAG_PARSE_SUCCESS;
+}
+
+
+static inline void
+message_error (notmuch_message_t *message,
+              notmuch_status_t status,
+              const char *format, ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    vfprintf (stderr, format, va_args);
+    fprintf (stderr, "Message-ID: %s\n", notmuch_message_get_message_id (message));
+    fprintf (stderr, "Status: %s\n", notmuch_status_to_string (status));
+
+    va_end (va_args);
+}
+
+static int
+makes_changes (notmuch_message_t *message,
+              tag_op_list_t *list,
+              tag_op_flag_t flags)
+{
+    size_t i;
+
+    notmuch_tags_t *tags;
+    bool changes = false;
+
+    /* First, do we delete an existing tag? */
+    for (tags = notmuch_message_get_tags (message);
+        ! changes && notmuch_tags_valid (tags);
+        notmuch_tags_move_to_next (tags)) {
+       const char *cur_tag = notmuch_tags_get (tags);
+       int last_op =  (flags & TAG_FLAG_REMOVE_ALL) ? -1 : 0;
+
+       /* scan backwards to get last operation */
+       i = list->count;
+       while (i > 0) {
+           i--;
+           if (strcmp (cur_tag, list->ops[i].tag) == 0) {
+               last_op = list->ops[i].remove ? -1 : 1;
+               break;
+           }
+       }
+
+       changes = (last_op == -1);
+    }
+    notmuch_tags_destroy (tags);
+
+    if (changes)
+       return true;
+
+    /* Now check for adding new tags */
+    for (i = 0; i < list->count; i++) {
+       bool exists = false;
+
+       if (list->ops[i].remove)
+           continue;
+
+       for (tags = notmuch_message_get_tags (message);
+            notmuch_tags_valid (tags);
+            notmuch_tags_move_to_next (tags)) {
+           const char *cur_tag = notmuch_tags_get (tags);
+           if (strcmp (cur_tag, list->ops[i].tag) == 0) {
+               exists = true;
+               break;
+           }
+       }
+       notmuch_tags_destroy (tags);
+
+       /* the following test is conservative,
+        * in the sense it ignores cases like +foo ... -foo
+        * but this is OK from a correctness point of view
+        */
+       if (! exists)
+           return true;
+    }
+    return false;
+
+}
+
+notmuch_status_t
+tag_op_list_apply (notmuch_message_t *message,
+                  tag_op_list_t *list,
+                  tag_op_flag_t flags)
+{
+    size_t i;
+    notmuch_status_t status = 0;
+    tag_operation_t *tag_ops = list->ops;
+
+    if (! (flags & TAG_FLAG_PRE_OPTIMIZED) && ! makes_changes (message, list, flags))
+       return NOTMUCH_STATUS_SUCCESS;
+
+    status = notmuch_message_freeze (message);
+    if (status) {
+       message_error (message, status, "freezing message");
+       return status;
+    }
+
+    if (flags & TAG_FLAG_REMOVE_ALL) {
+       status = notmuch_message_remove_all_tags (message);
+       if (status) {
+           message_error (message, status, "removing all tags");
+           return status;
+       }
+    }
+
+    for (i = 0; i < list->count; i++) {
+       if (tag_ops[i].remove) {
+           status = notmuch_message_remove_tag (message, tag_ops[i].tag);
+           if (status) {
+               message_error (message, status, "removing tag %s", tag_ops[i].tag);
+               return status;
+           }
+       } else {
+           status = notmuch_message_add_tag (message, tag_ops[i].tag);
+           if (status) {
+               message_error (message, status, "adding tag %s", tag_ops[i].tag);
+               return status;
+           }
+
+       }
+    }
+
+    status = notmuch_message_thaw (message);
+    if (status) {
+       message_error (message, status, "thawing message");
+       return status;
+    }
+
+
+    if (flags & TAG_FLAG_MAILDIR_SYNC) {
+       status = notmuch_message_tags_to_maildir_flags (message);
+       if (status) {
+           message_error (message, status, "synching tags to maildir");
+           return status;
+       }
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+
+}
+
+
+/* Array of tagging operations (add or remove.  Size will be increased
+ * as necessary. */
+
+tag_op_list_t *
+tag_op_list_create (void *ctx)
+{
+    tag_op_list_t *list;
+
+    list = talloc (ctx, tag_op_list_t);
+    if (list == NULL)
+       return NULL;
+
+    list->size = TAG_OP_LIST_INITIAL_SIZE;
+    list->count = 0;
+
+    list->ops = talloc_array (list, tag_operation_t, list->size);
+    if (list->ops == NULL)
+       return NULL;
+
+    return list;
+}
+
+
+int
+tag_op_list_append (tag_op_list_t *list,
+                   const char *tag,
+                   bool remove)
+{
+    /* Make room if current array is full.  This should be a fairly
+     * rare case, considering the initial array size.
+     */
+
+    if (list->count == list->size) {
+       list->size *= 2;
+       list->ops = talloc_realloc (list, list->ops, tag_operation_t,
+                                   list->size);
+       if (list->ops == NULL) {
+           fprintf (stderr, "Out of memory.\n");
+           return 1;
+       }
+    }
+
+    /* add the new operation */
+
+    list->ops[list->count].tag = tag;
+    list->ops[list->count].remove = remove;
+    list->count++;
+    return 0;
+}
+
+/*
+ *   Is the i'th tag operation a remove?
+ */
+
+bool
+tag_op_list_isremove (const tag_op_list_t *list, size_t i)
+{
+    assert (i < list->count);
+    return list->ops[i].remove;
+}
+
+/*
+ * Reset a list to contain no operations
+ */
+
+void
+tag_op_list_reset (tag_op_list_t *list)
+{
+    list->count = 0;
+}
+
+/*
+ * Return the number of operations in a list
+ */
+
+size_t
+tag_op_list_size (const tag_op_list_t *list)
+{
+    return list->count;
+}
+
+/*
+ *   return the i'th tag in the list
+ */
+
+const char *
+tag_op_list_tag (const tag_op_list_t *list, size_t i)
+{
+    assert (i < list->count);
+    return list->ops[i].tag;
+}
diff --git a/tag-util.h b/tag-util.h
new file mode 100644 (file)
index 0000000..ba0d98c
--- /dev/null
@@ -0,0 +1,163 @@
+#ifndef _TAG_UTIL_H
+#define _TAG_UTIL_H
+
+#include "notmuch-client.h"
+
+typedef struct _tag_operation_t tag_operation_t;
+typedef struct _tag_op_list_t tag_op_list_t;
+
+/* Use powers of 2 */
+typedef enum {
+    TAG_FLAG_NONE = 0,
+
+    /* Operations are synced to maildir, if possible.
+     */
+    TAG_FLAG_MAILDIR_SYNC = (1 << 0),
+
+    /* Remove all tags from message before applying list.
+     */
+    TAG_FLAG_REMOVE_ALL = (1 << 1),
+
+    /* Don't try to avoid database operations. Useful when we
+     * know that message passed needs these operations.
+     */
+    TAG_FLAG_PRE_OPTIMIZED = (1 << 2),
+
+    /* Accept strange tags that might be user error;
+     * intended for use by notmuch-restore.
+     */
+    TAG_FLAG_BE_GENEROUS = (1 << 3)
+
+} tag_op_flag_t;
+
+/* These should obey the convention that fatal errors are negative,
+ * skipped lines are positive.
+ */
+typedef enum {
+    TAG_PARSE_OUT_OF_MEMORY = -1,
+
+    /* Line parsed successfully. */
+    TAG_PARSE_SUCCESS = 0,
+
+    /* Line has a syntax error */
+    TAG_PARSE_INVALID = 1,
+
+    /* Line was blank or a comment */
+    TAG_PARSE_SKIPPED = 2
+
+} tag_parse_status_t;
+
+/* Parse a string of the following format:
+ *
+ * +<tag>|-<tag> [...] [--] <search-terms>
+ *
+ * Each line is interpreted similarly to "notmuch tag" command line
+ * arguments. The delimiter is one or more spaces ' '. Any characters
+ * in <tag> and <search-terms> MAY be hex encoded with %NN where NN is
+ * the hexadecimal value of the character. Any ' ' and '%' characters
+ * in <tag> and <search-terms> MUST be hex encoded (using %20 and %25,
+ * respectively). Any characters that are not part of <tag> or
+ * <search-terms> MUST NOT be hex encoded.
+ *
+ * Leading and trailing space ' ' is ignored. Empty lines and lines
+ * beginning with '#' are ignored.
+ *
+ *
+ * Output Parameters:
+ *     ops     contains a list of tag operations
+ *     query_str the search terms.
+ */
+tag_parse_status_t
+parse_tag_line (void *ctx, char *line,
+               tag_op_flag_t flags,
+               char **query_str, tag_op_list_t *ops);
+
+
+
+/* Parse a command line of the following format:
+ *
+ * +<tag>|-<tag> [...] [--] <search-terms>
+ *
+ * Output Parameters:
+ *     ops     contains a list of tag operations
+ *     query_str the search terms.
+ *
+ * The ops argument is not cleared.
+ */
+
+tag_parse_status_t
+parse_tag_command_line (void *ctx, int argc, char **argv,
+                       char **query_str, tag_op_list_t *ops);
+
+/*
+ * Test tags for some forbidden cases.
+ *
+ * Relax the checks if 'remove' is true to allow removal of previously
+ * added forbidden tags.
+ *
+ * return: NULL if OK,
+ *        explanatory message otherwise.
+ */
+const char *
+illegal_tag (const char *tag, bool remove);
+
+/*
+ * Create an empty list of tag operations
+ *
+ * ctx is passed to talloc
+ */
+
+tag_op_list_t *
+tag_op_list_create (void *ctx);
+
+/*
+ * Add a tag operation (delete iff remove == true) to a list.
+ * The list is expanded as necessary.
+ */
+
+int
+tag_op_list_append (tag_op_list_t *list,
+                   const char *tag,
+                   bool remove);
+
+/*
+ * Apply a list of tag operations, in order, to a given message.
+ *
+ * Flags can be bitwise ORed; see enum above for possibilies.
+ */
+
+notmuch_status_t
+tag_op_list_apply (notmuch_message_t *message,
+                  tag_op_list_t *tag_ops,
+                  tag_op_flag_t flags);
+
+/*
+ * Return the number of operations in a list
+ */
+
+size_t
+tag_op_list_size (const tag_op_list_t *list);
+
+/*
+ * Reset a list to contain no operations
+ */
+
+void
+tag_op_list_reset (tag_op_list_t *list);
+
+
+/*
+ *   return the i'th tag in the list
+ */
+
+const char *
+tag_op_list_tag (const tag_op_list_t *list, size_t i);
+
+/*
+ *   Is the i'th tag operation a remove?
+ */
+
+bool
+tag_op_list_isremove (const tag_op_list_t *list, size_t i);
+
+#endif
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644 (file)
index 0000000..73fe7e2
--- /dev/null
@@ -0,0 +1,11 @@
+/arg-test
+/corpora.mail
+/hex-xcode
+/parse-time
+/random-corpus
+/smtp-dummy
+/symbol-test
+/make-db-version
+/test-results
+/ghost-report
+/tmp.*
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..de492a7
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/test/Makefile.local b/test/Makefile.local
new file mode 100644 (file)
index 0000000..1cf0977
--- /dev/null
@@ -0,0 +1,84 @@
+# -*- makefile -*-
+
+dir := test
+
+# save against changes in $(dir)
+test_src_dir := $(dir)
+extra_cflags += -I$(srcdir)
+
+smtp_dummy_srcs =              \
+       $(notmuch_compat_srcs)  \
+       $(dir)/smtp-dummy.c
+
+smtp_dummy_modules = $(smtp_dummy_srcs:.c=.o)
+
+$(dir)/arg-test: $(dir)/arg-test.o command-line-arguments.o util/libnotmuch_util.a
+       $(call quiet,CC) $^ -o $@ $(LDFLAGS)
+
+$(dir)/message-id-parse: $(dir)/message-id-parse.o lib/libnotmuch.a util/libnotmuch_util.a
+       $(call quiet,CC) $^ -o $@ $(LDFLAGS) $(TALLOC_LDFLAGS)
+
+$(dir)/hex-xcode: $(dir)/hex-xcode.o command-line-arguments.o util/libnotmuch_util.a
+       $(call quiet,CC) $^ -o $@ $(LDFLAGS) $(TALLOC_LDFLAGS)
+
+random_corpus_deps =  $(dir)/random-corpus.o  $(dir)/database-test.o \
+                       notmuch-config.o status.o command-line-arguments.o \
+                       lib/libnotmuch.a util/libnotmuch_util.a \
+                       parse-time-string/libparse-time-string.a
+
+$(dir)/random-corpus: $(random_corpus_deps)
+       $(call quiet,CXX) $^ -o $@ $(LDFLAGS) $(CONFIGURE_LDFLAGS)
+
+$(dir)/smtp-dummy: $(smtp_dummy_modules)
+       $(call quiet,CC) $^ -o $@ $(LDFLAGS)
+
+$(dir)/symbol-test: $(dir)/symbol-test.o lib/$(LINKER_NAME)
+       $(call quiet,CXX) $^ -o $@ $(LDFLAGS) -Llib -lnotmuch $(XAPIAN_LDFLAGS)
+
+$(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o
+       $(call quiet,CC) $^ -o $@ $(LDFLAGS)
+
+$(dir)/make-db-version: $(dir)/make-db-version.o
+       $(call quiet,CXX) $^ -o $@ $(LDFLAGS) $(XAPIAN_LDFLAGS)
+
+$(dir)/ghost-report: $(dir)/ghost-report.o
+       $(call quiet,CXX) $^ -o $@ $(LDFLAGS) $(XAPIAN_LDFLAGS)
+
+.PHONY: test check
+
+test_main_srcs=$(dir)/arg-test.c \
+             $(dir)/hex-xcode.c \
+             $(dir)/random-corpus.c \
+             $(dir)/parse-time.c \
+             $(dir)/smtp-dummy.c \
+             $(dir)/symbol-test.cc \
+             $(dir)/make-db-version.cc \
+             $(dir)/ghost-report.cc \
+             $(dir)/message-id-parse.c
+
+test_srcs=$(test_main_srcs) $(dir)/database-test.c
+
+TEST_BINARIES := $(test_main_srcs:.c=)
+TEST_BINARIES := $(TEST_BINARIES:.cc=)
+
+test-binaries: $(TEST_BINARIES)
+
+test:  all test-binaries
+ifeq ($V,)
+       @echo 'Use "$(MAKE) V=1" to see the details for passing and known broken tests.'
+       @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
+else
+# The user has explicitly enabled quiet execution.
+ifeq ($V,0)
+       @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
+else
+       @$(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
+endif
+endif
+
+check: test
+
+SRCS := $(SRCS) $(test_srcs)
+CLEAN += $(TEST_BINARIES) $(addsuffix .o,$(TEST_BINARIES)) \
+        $(dir)/database-test.o \
+        $(dir)/corpora.mail $(dir)/test-results $(dir)/tmp.*
diff --git a/test/README b/test/README
new file mode 100644 (file)
index 0000000..b378c3f
--- /dev/null
@@ -0,0 +1,324 @@
+Notmuch test suite
+==================
+This directory contains the test suite for notmuch.
+
+When fixing bugs or enhancing notmuch, you are strongly encouraged to
+add tests in this directory to cover what you are trying to fix or
+enhance.
+
+Prerequisites
+-------------
+The test system itself requires:
+
+  - bash(1) version 4.0 or newer
+
+Without bash 4.0+ the tests just refuse to run.
+
+Some tests require external dependencies to run. Without them, they
+will be skipped, or (rarely) marked failed. Please install these, so
+that you know if you break anything.
+
+  - GNU tar(1)
+  - dtach(1)
+  - emacs(1)
+  - emacsclient(1)
+  - gdb(1)
+  - gpg(1)
+  - python(1)
+
+If your system lacks these tools or have older, non-upgradable versions
+of these, please (possibly compile and) install these to some other
+path, for example /usr/local/bin or /opt/gnu/bin. Then prepend the
+chosen directory to your PATH before running the tests.
+
+e.g. env PATH=/opt/gnu/bin:$PATH make test
+
+For FreeBSD you need to install latest gdb from ports or packages and
+provide path to it in TEST_GDB environment variable before executing
+the tests, native FreeBSD gdb does not not work.  If you install
+coreutils, which provides GNU versions of basic utils like 'date' and
+'base64' on FreeBSD, the test suite will use these instead of the
+native ones. This provides robustness against portability issues with
+these system tools. Most often the tests are written, reviewed and
+tested on Linux system so such portability issues arise from time to
+time.
+
+
+Running Tests
+-------------
+The easiest way to run tests is to say "make test", (or simply run the
+notmuch-test script). Either command will run all available tests.
+
+Alternately, you can run a specific subset of tests by simply invoking
+one of the executable scripts in this directory, (such as ./T*-search.sh,
+./T*-reply.sh, etc). Note that you will probably want "make test-binaries"
+before running individual tests.
+
+The following command-line options are available when running tests:
+
+--debug::
+       This may help the person who is developing a new test.
+       It causes the command defined with test_debug to run.
+
+--immediate::
+       This causes the test to immediately exit upon the first
+       failed test.
+
+--valgrind::
+       Execute notmuch with valgrind and exit with status
+       126 on errors (just like regular tests, this will only stop
+       the test script when running under -i).  Valgrind errors
+       go to stderr, so you might want to pass the -v option, too.
+
+       Since it makes no sense to run the tests with --valgrind and
+       not see any output, this option implies --verbose.  For
+       convenience, it also implies --tee.
+
+--tee::
+       In addition to printing the test output to the terminal,
+       write it to files named 't/test-results/$TEST_NAME.out'.
+       As the names depend on the tests' file names, it is safe to
+       run the tests with this option in parallel.
+
+Certain tests require precomputed databases to complete. You can fetch these
+databases with
+
+       make download-test-databases
+
+If you do not download the test databases, the relevant tests will be
+skipped.
+
+When invoking the test suite via "make test" any of the above options
+can be specified as follows:
+
+       make test OPTIONS="--verbose"
+
+You can choose an emacs binary (and corresponding emacsclient) to run
+the tests in one of the following ways.
+
+       TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient make test
+       TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient ./T*-emacs.sh
+       make test TEST_EMACS=my-emacs TEST_EMACSCLIENT=my-emacsclient
+
+Some tests may require a c compiler. You can choose the name and flags similarly
+to with emacs, e.g.
+
+     make test TEST_CC=gcc TEST_CFLAGS="-g -O2"
+
+Quiet Execution
+---------------
+
+Normally, when new script starts and when test PASSes you get a message
+printed on screen. This printing can be disabled by setting the
+NOTMUCH_TEST_QUIET variable to a non-null value. Message on test
+failures and skips are still printed.
+
+Skipping Tests
+--------------
+If, for any reason, you need to skip one or more tests, you can do so
+by setting the NOTMUCH_SKIP_TESTS variable to the name of one or more
+sections of tests.
+
+For example:
+
+    $ NOTMUCH_SKIP_TESTS="search reply" make test
+
+Even more fine-grained skipping is possible by appending a test number
+(or glob pattern) after the section name. For example, the first
+search test and the second reply test could be skipped with:
+
+    $ NOTMUCH_SKIP_TESTS="search.1 reply.2" make test
+
+Note that some tests in the existing test suite rely on previous test
+items, so you cannot arbitrarily skip any test and expect the
+remaining tests to be unaffected.
+
+Currently we do not consider skipped tests as build failures. For
+maximum robustness, when setting up automated build processes, you
+should explicitly skip tests, rather than relying on notmuch's
+detection of missing prerequisites. In the future we may treat tests
+unable to run because of missing prerequisites, but not explicitly
+skipped by the user, as failures.
+
+Writing Tests
+-------------
+The test script is written as a shell script. It is to be named as
+Tddd-testname.sh where 'ddd' is three digits and 'testname' the "bare"
+name of your test. Tests will be run in order the 'ddd' part determines.
+
+The test script should start with the standard "#!/usr/bin/env bash"
+and an assignment to variable 'test_description', like this:
+
+       #!/usr/bin/env bash
+
+       test_description='xxx test (option --frotz)
+
+       This test exercises the "notmuch xxx" command when
+       given the option --frotz.'
+
+Source 'test-lib.sh'
+--------------------
+After assigning test_description, the test script should source
+test-lib.sh like this:
+
+       . ./test-lib.sh || exit 1
+
+This test harness library does the following things:
+
+ - If the script is invoked with command line argument --help
+   (or -h), it shows the test_description and exits.
+
+ - Creates a temporary directory with default notmuch-config and a
+   mail store with a corpus of mail, (initially, 50 early messages
+   sent to the notmuch list). This directory is
+   test/tmp.<test-basename>. The path to notmuch-config is exported in
+   NOTMUCH_CONFIG environment variable and mail store path is stored
+   in MAIL_DIR variable.
+
+ - Defines standard test helper functions for your scripts to
+   use.  These functions are designed to make all scripts behave
+   consistently when command line arguments --verbose (or -v),
+   --debug (or -d), and --immediate (or -i) is given.
+
+End with test_done
+------------------
+Your script will be a sequence of tests, using helper functions
+from the test harness library.  At the end of the script, call
+'test_done'.
+
+Test harness library
+--------------------
+There are a handful helper functions defined in the test harness
+library for your script to use.
+
+ test_begin_subtest <message>
+
+   Set the test description message for a subsequent test_expect_*
+   invocation (see below).
+
+ test_expect_success <script>
+
+   This takes a string as parameter, and evaluates the
+   <script>.  If it yields success, test is considered
+   successful.
+
+ test_expect_code <code> <script>
+
+   This takes two strings as parameter, and evaluates the <script>.
+   If it yields <code> exit status, test is considered successful.
+
+ test_subtest_known_broken
+
+   Mark the current test as broken.  Such tests are expected to fail.
+   Unlike the normal tests, which say "PASS" on success and "FAIL" on
+   failure, these will say "FIXED" on success and "BROKEN" on failure.
+   Failures from these tests won't cause -i (immediate) to stop.  A
+   test must call this before any test_expect_* function.
+
+ test_expect_equal <output> <expected>
+
+   This is an often-used convenience function built on top of
+   test_expect_success. It uses the message from the last
+   test_begin_subtest call, so call before calling
+   test_expect_equal. This function generates a successful test if
+   both the <output> and <expected> strings are identical. If not, it
+   will generate a failure and print the difference of the two
+   strings.
+
+ test_expect_equal_file <file1> <file2>
+
+   Identical to test_expect_equal, except that <file1> and <file2>
+   are files instead of strings.  This is a much more robust method to
+   compare formatted textual information, since it also notices
+   whitespace and closing newline differences.
+
+ test_expect_equal_json <output> <expected>
+
+   Identical to test_expect_equal, except that the two strings are
+   treated as JSON and canonicalized before equality testing.  This is
+   useful to abstract away from whitespace differences in the expected
+   output and that generated by running a notmuch command.
+
+ test_debug <script>
+
+   This takes a single argument, <script>, and evaluates it only
+   when the test script is started with --debug command line
+   argument.  This is primarily meant for use during the
+   development of a new test script.
+
+ test_emacs <emacs-lisp-expressions>
+
+   This function executes the provided emacs lisp script within
+   emacs. The script can be a sequence of emacs lisp expressions,
+   (that is, they will be evaluated within a progn form). Emacs
+   stdout and stderr is not available, the common way to get output
+   is to save it to a file. There are some auxiliary functions
+   useful in emacs tests provided in test-lib.el. Do not use `setq'
+   for setting variables in Emacs tests because it affects other
+   tests that may run in the same Emacs instance.  Use `let' instead
+   so the scope of the changed variables is limited to a single test.
+
+ test_emacs_expect_t <emacs-lisp-expressions>
+
+  This function executes the provided emacs lisp script within
+  emacs in a manner similar to 'test_emacs'. The expressions should
+  return the value `t' to indicate that the test has passed. If the
+  test does not return `t' then it is considered failed and all data
+  returned by the test is reported to the tester.
+
+ test_done
+
+   Your test script must have test_done at the end.  Its purpose
+   is to summarize successes and failures in the test script and
+   exit with an appropriate error code.
+
+There are also a number of notmuch-specific auxiliary functions and
+variables which are useful in writing tests:
+
+  generate_message
+
+    Generates a message with an optional template. Most tests will
+    actually prefer to call add_message. See below.
+
+  add_message
+
+    Generate a message and add it to the database (by calling "notmuch
+    new"). It is sufficient to simply call add_message with no
+    arguments if you don't care about the content of the message. If
+    more control is needed, arguments can be provide to specify many
+    different header values for the new message. See the documentation
+    within test-lib.sh or refer to many example calls within existing
+    tests.
+
+  add_email_corpus
+
+    This function should be called at the beginning of a test file
+    when a test needs to operate on a non-empty body of messages. It
+    will initialize the mail database to a known state of 50 sample
+    messages, (culled from the early history of the notmuch mailing
+    list).
+
+  notmuch_counter_reset
+  $notmuch_counter_command
+  notmuch_counter_value
+
+    These allow to count how many times notmuch binary is called.
+    notmuch_counter_reset() function generates a script that counts
+    how many times it is called and resets the counter to zero.  The
+    function sets $notmuch_counter_command variable to the path to the
+    generated script that should be called instead of notmuch to do
+    the counting.  The notmuch_counter_value() function prints the
+    current counter value.
+
+There are also functions which remove various environment-dependent
+values from notmuch output; these are useful to ensure that test
+results remain consistent across different machines.
+
+ notmuch_search_sanitize
+ notmuch_show_sanitize
+ notmuch_show_sanitize_all
+ notmuch_json_show_sanitize
+
+   All these functions should receive the text to be sanitized as the
+   input of a pipe, e.g.
+   output=`notmuch search "..." | notmuch_search_sanitize`
diff --git a/test/T000-basic.sh b/test/T000-basic.sh
new file mode 100755 (executable)
index 0000000..7fbdcfa
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='the test framework itself.'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+################################################################
+# Test harness
+test_begin_subtest 'success is reported like this'
+test_expect_success ':'
+
+test_begin_subtest 'test runs if prerequisite is satisfied'
+test_set_prereq HAVEIT
+test_expect_success 'test_have_prereq HAVEIT'
+
+test_begin_subtest 'tests clean up after themselves'
+clean=no
+test_expect_success 'test_when_finished clean=yes'
+
+test_begin_subtest 'tests clean up even after a failure'
+cleaner=no
+test_expect_code 1 'test_when_finished cleaner=yes && (exit 1)'
+
+if test $clean$cleaner != yesyes
+then
+       say "bug in test framework: cleanup commands do not work reliably"
+       exit 1
+fi
+
+test_begin_subtest 'failure to clean up causes the test to fail'
+test_expect_code 2 'test_when_finished "(exit 2)"'
+
+EXPECTED=$NOTMUCH_SRCDIR/test/test.expected-output
+suppress_diff_date() {
+    sed -e 's/\(.*\-\-\- test-verbose\.4\.\expected\).*/\1/' \
+       -e 's/\(.*\+\+\+ test-verbose\.4\.\output\).*/\1/'
+}
+
+test_begin_subtest "Ensure that test output is suppressed unless the test fails"
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose 2>&1 | suppress_diff_date)
+expected=$(cat $EXPECTED/test-verbose-no | suppress_diff_date)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "Ensure that -v does not suppress test output"
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose -v 2>&1 | suppress_diff_date)
+expected=$(cat $EXPECTED/test-verbose-yes | suppress_diff_date)
+# Do not include the results of test-verbose in totals
+rm $TEST_DIRECTORY/test-results/test-verbose
+rm -r $TEST_DIRECTORY/tmp.test-verbose
+test_expect_equal "$output" "$expected"
+
+
+################################################################
+# Test mail store prepared in test-lib.sh
+
+test_begin_subtest 'test that mail store was created'
+test_expect_success 'test -d "${MAIL_DIR}"'
+
+test_begin_subtest 'mail store should be empty'
+find "${MAIL_DIR}" -type f -print >should-be-empty
+test_expect_success 'cmp -s /dev/null should-be-empty'
+
+test_begin_subtest 'NOTMUCH_CONFIG is set and points to an existing file'
+test_expect_success 'test -f "${NOTMUCH_CONFIG}"'
+
+test_begin_subtest 'PATH is set to build directory'
+test_expect_equal \
+    "$(dirname ${TEST_DIRECTORY})" \
+    "$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')"
+
+test_begin_subtest 'notmuch is compiled with debugging symbols'
+readelf --sections $(command -v notmuch) | grep \.debug
+test_expect_equal 0 $?
+
+test_done
diff --git a/test/T010-help-test.sh b/test/T010-help-test.sh
new file mode 100755 (executable)
index 0000000..da45d3a
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+test_description="online help"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest 'notmuch --help'
+test_expect_success 'notmuch --help'
+
+test_begin_subtest 'notmuch help'
+test_expect_success 'notmuch help'
+
+test_begin_subtest 'notmuch --version'
+test_expect_success 'notmuch --version'
+
+if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
+    test_begin_subtest 'notmuch --help tag'
+    test_expect_success 'notmuch --help tag'
+
+    test_begin_subtest 'notmuch help tag'
+    test_expect_success 'notmuch help tag'
+else
+    test_begin_subtest 'notmuch --help tag (man pages not available)'
+    test_expect_success 'test_must_fail notmuch --help tag >/dev/null'
+
+    test_begin_subtest 'notmuch help tag (man pages not available)'
+    test_expect_success 'test_must_fail notmuch help tag >/dev/null'
+fi
+
+test_done
diff --git a/test/T020-compact.sh b/test/T020-compact.sh
new file mode 100755 (executable)
index 0000000..58cd2ba
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+test_description='"notmuch compact"'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[subject]=One'
+add_message '[subject]=Two'
+add_message '[subject]=Three'
+
+notmuch tag +tag1 \*
+notmuch tag +tag2 subject:Two
+notmuch tag -tag1 +tag3 subject:Three
+
+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.
+Compaction failed: Unsupported operation"
+
+    test_begin_subtest "Compact unsupported: status code"
+    test_expect_code 1 "notmuch compact"
+
+    test_done
+fi
+
+test_begin_subtest "Running compact"
+test_expect_success "notmuch compact --backup=${TEST_DIRECTORY}/xapian.old"
+
+test_begin_subtest "Compact preserves database"
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag2 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Three (inbox tag3 unread)"
+
+test_begin_subtest "Restoring Backup"
+test_expect_success 'rm -Rf ${MAIL_DIR}/.notmuch/xapian &&
+     mv ${TEST_DIRECTORY}/xapian.old ${MAIL_DIR}/.notmuch/xapian'
+
+test_begin_subtest "Checking restored backup"
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag2 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Three (inbox tag3 unread)"
+
+test_done
diff --git a/test/T030-config.sh b/test/T030-config.sh
new file mode 100755 (executable)
index 0000000..f36695c
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+
+test_description='"notmuch config"'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Get string value"
+test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
+
+test_begin_subtest "Get list value"
+test_expect_equal "$(notmuch config get new.tags)" "\
+unread
+inbox"
+
+test_begin_subtest "Set string value"
+notmuch config set foo.string "this is a string value"
+test_expect_equal "$(notmuch config get foo.string)" "this is a string value"
+
+test_begin_subtest "Set string value again"
+notmuch config set foo.string "this is another string value"
+test_expect_equal "$(notmuch config get foo.string)" "this is another string value"
+
+test_begin_subtest "Set list value"
+notmuch config set foo.list this "is a" "list value"
+test_expect_equal "$(notmuch config get foo.list)" "\
+this
+is a
+list value"
+
+test_begin_subtest "Set list value again"
+notmuch config set foo.list this "is another" "list value"
+test_expect_equal "$(notmuch config get foo.list)" "\
+this
+is another
+list value"
+
+test_begin_subtest "Remove key"
+notmuch config set foo.remove baz
+notmuch config set foo.remove
+test_expect_equal "$(notmuch config get foo.remove)" ""
+
+test_begin_subtest "Remove non-existent key"
+notmuch config set foo.nonexistent
+test_expect_equal "$(notmuch config get foo.nonexistent)" ""
+
+test_begin_subtest "List all items"
+notmuch config list 2>&1 | notmuch_config_sanitize > OUTPUT
+
+if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
+    config_gpg_path="crypto.gpg_path=gpg
+"
+fi
+
+cat <<EOF > EXPECTED
+Error opening database at MAIL_DIR/.notmuch: No such file or directory
+database.path=MAIL_DIR
+user.name=Notmuch Test Suite
+user.primary_email=test_suite@notmuchmail.org
+user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+new.tags=unread;inbox;
+new.ignore=
+search.exclude_tags=
+maildir.synchronize_flags=true
+${config_gpg_path}foo.string=this is another string value
+foo.list=this;is another;list value;
+built_with.compact=something
+built_with.field_processor=something
+built_with.retry_lock=something
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Top level --config=FILE option"
+cp "${NOTMUCH_CONFIG}" alt-config
+notmuch --config=alt-config config set user.name "Another Name"
+test_expect_equal "$(notmuch --config=alt-config config get user.name)" \
+    "Another Name"
+
+test_begin_subtest "Top level --config:FILE option"
+test_expect_equal "$(notmuch --config:alt-config config get user.name)" \
+    "Another Name"
+
+test_begin_subtest "Top level --config<space>FILE option"
+test_expect_equal "$(notmuch --config  alt-config config get user.name)" \
+    "Another Name"
+
+test_begin_subtest "Top level --config=FILE option changed the right file"
+test_expect_equal "$(notmuch config get user.name)" \
+    "Notmuch Test Suite"
+
+test_begin_subtest "Read config file through a symlink"
+ln -s alt-config alt-config-link
+test_expect_equal "$(notmuch --config=alt-config-link config get user.name)" \
+    "Another Name"
+
+test_begin_subtest "Write config file through a symlink"
+notmuch --config=alt-config-link config set user.name "Link Name"
+test_expect_equal "$(notmuch --config=alt-config-link config get user.name)" \
+    "Link Name"
+
+test_begin_subtest "Writing config file through symlink follows symlink"
+test_expect_equal "$(readlink alt-config-link)" "alt-config"
+
+test_begin_subtest "Absolute database path returned"
+notmuch config set database.path ${HOME}/Maildir
+test_expect_equal "$(notmuch config get database.path)" \
+                 "${HOME}/Maildir"
+
+test_begin_subtest "Relative database path properly expanded"
+notmuch config set database.path Maildir
+test_expect_equal "$(notmuch config get database.path)" \
+                 "${HOME}/Maildir"
+
+test_done
diff --git a/test/T040-setup.sh b/test/T040-setup.sh
new file mode 100755 (executable)
index 0000000..56efe1d
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+test_description='"notmuch setup"'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Notmuch new without a config suggests notmuch setup"
+output=$(notmuch --config=new-notmuch-config new 2>&1)
+test_expect_equal "$output" "\
+Configuration file new-notmuch-config not found.
+Try running 'notmuch setup' to create a configuration."
+
+test_begin_subtest "Create a new config interactively"
+notmuch --config=new-notmuch-config > /dev/null <<EOF
+Test Suite
+test.suite@example.com
+another.suite@example.com
+
+/path/to/maildir
+foo bar
+baz
+EOF
+
+if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
+    config_gpg_path="crypto.gpg_path=gpg
+"
+fi
+
+output=$(notmuch --config=new-notmuch-config config list | notmuch_built_with_sanitize)
+test_expect_equal "$output" "\
+database.path=/path/to/maildir
+user.name=Test Suite
+user.primary_email=test.suite@example.com
+user.other_email=another.suite@example.com;
+new.tags=foo;bar;
+new.ignore=
+search.exclude_tags=baz;
+maildir.synchronize_flags=true
+""${config_gpg_path}""\
+built_with.compact=something
+built_with.field_processor=something
+built_with.retry_lock=something"
+
+test_done
diff --git a/test/T050-new.sh b/test/T050-new.sh
new file mode 100755 (executable)
index 0000000..dfc8508
--- /dev/null
@@ -0,0 +1,396 @@
+#!/usr/bin/env bash
+test_description='"notmuch new" in several variations'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "No new messages"
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "No new mail."
+
+
+test_begin_subtest "Single new message"
+generate_message
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Single message (full-scan)"
+generate_message
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Multiple new messages"
+generate_message
+generate_message
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "Multiple new messages (full-scan)"
+generate_message
+generate_message
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "Added 2 new messages to the database."
+
+test_begin_subtest "No new messages (non-empty DB)"
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "No new mail."
+
+test_begin_subtest "No new messages (full-scan)"
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" "No new mail."
+
+test_begin_subtest "New directories"
+rm -rf "${MAIL_DIR}"/* "${MAIL_DIR}"/.notmuch
+mkdir "${MAIL_DIR}"/def
+mkdir "${MAIL_DIR}"/ghi
+generate_message [dir]=def
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+
+test_begin_subtest "Alternate inode order"
+
+rm -rf "${MAIL_DIR}"/.notmuch
+mv "${MAIL_DIR}"/ghi "${MAIL_DIR}"/abc
+rm "${MAIL_DIR}"/def/*
+generate_message [dir]=abc
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+
+test_begin_subtest "Message moved in"
+rm -rf "${MAIL_DIR}"/* "${MAIL_DIR}"/.notmuch
+generate_message
+tmp_msg_filename=tmp/"$gen_msg_filename"
+mkdir -p "$(dirname "$tmp_msg_filename")"
+mv "$gen_msg_filename" "$tmp_msg_filename"
+notmuch new > /dev/null
+mv "$tmp_msg_filename" "$gen_msg_filename"
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+
+test_begin_subtest "Renamed message"
+
+generate_message
+notmuch new > /dev/null
+mv "$gen_msg_filename" "${gen_msg_filename}"-renamed
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed file ${gen_msg_filename} for deletion from database
+No new mail. Detected 1 file rename."
+
+
+test_begin_subtest "Deleted message"
+
+rm "${gen_msg_filename}"-renamed
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover file ${gen_msg_filename}-renamed for deletion from database
+No new mail. Removed 1 message."
+
+
+
+test_begin_subtest "Renamed directory"
+
+generate_message [dir]=dir
+generate_message [dir]=dir
+generate_message [dir]=dir
+
+notmuch new > /dev/null
+
+mv "${MAIL_DIR}"/dir "${MAIL_DIR}"/dir-renamed
+
+output=$(NOTMUCH_NEW --debug --full-scan)
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed directory ${MAIL_DIR}/dir for deletion from database
+No new mail. Detected 3 file renames."
+
+
+test_begin_subtest "Deleted directory"
+rm -rf "${MAIL_DIR}"/dir-renamed
+
+output=$(NOTMUCH_NEW --debug --full-scan)
+test_expect_equal "$output" "(D) add_files, pass 2: queuing passed directory ${MAIL_DIR}/dir-renamed for deletion from database
+No new mail. Removed 3 messages."
+
+
+test_begin_subtest "New directory (at end of list)"
+
+generate_message [dir]=zzz
+generate_message [dir]=zzz
+generate_message [dir]=zzz
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 3 new messages to the database."
+
+
+test_begin_subtest "Deleted directory (end of list)"
+
+rm -rf "${MAIL_DIR}"/zzz
+
+output=$(NOTMUCH_NEW --debug --full-scan)
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover directory ${MAIL_DIR}/zzz for deletion from database
+No new mail. Removed 3 messages."
+
+
+test_begin_subtest "New symlink to directory"
+
+rm -rf "${MAIL_DIR}"/.notmuch
+mv "${MAIL_DIR}" "${TMP_DIRECTORY}"/actual_maildir
+
+mkdir "${MAIL_DIR}"
+ln -s "${TMP_DIRECTORY}"/actual_maildir "${MAIL_DIR}"/symlink
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+
+test_begin_subtest "New symlink to a file"
+generate_message
+external_msg_filename="${TMP_DIRECTORY}"/external/"$(basename "$gen_msg_filename")"
+mkdir -p "$(dirname "$external_msg_filename")"
+mv "$gen_msg_filename" "$external_msg_filename"
+ln -s "$external_msg_filename" "$gen_msg_filename"
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+
+test_begin_subtest "Broken symlink aborts"
+ln -s does-not-exist "${MAIL_DIR}/broken"
+output=$(NOTMUCH_NEW --debug 2>&1)
+test_expect_equal "$output" \
+"Error reading file ${MAIL_DIR}/broken: No such file or directory
+Note: A fatal error was encountered: Something went wrong trying to read or write a file
+No new mail."
+rm "${MAIL_DIR}/broken"
+
+
+test_begin_subtest "New two-level directory"
+
+generate_message [dir]=two/levels
+generate_message [dir]=two/levels
+generate_message [dir]=two/levels
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 3 new messages to the database."
+
+
+test_begin_subtest "Deleted two-level directory"
+
+rm -rf "${MAIL_DIR}"/two
+
+output=$(NOTMUCH_NEW --debug --full-scan)
+test_expect_equal "$output" "(D) add_files, pass 3: queuing leftover directory ${MAIL_DIR}/two for deletion from database
+No new mail. Removed 3 messages."
+
+test_begin_subtest "One character directory at top level"
+
+generate_message [dir]=A
+generate_message [dir]=A/B
+generate_message [dir]=A/B/C
+
+output=$(NOTMUCH_NEW --debug)
+test_expect_equal "$output" "Added 3 new messages to the database."
+
+test_begin_subtest "Support single-message mbox"
+cat > "${MAIL_DIR}"/mbox_file1 <<EOF
+From test_suite@notmuchmail.org Fri Jan  5 15:43:57 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Test mbox message 1
+
+Body.
+EOF
+output=$(NOTMUCH_NEW --debug 2>&1)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+# This test requires that notmuch new has been run at least once.
+test_begin_subtest "Skip and report non-mail files"
+generate_message
+mkdir -p "${MAIL_DIR}"/.git && touch "${MAIL_DIR}"/.git/config
+touch "${MAIL_DIR}"/ignored_file
+touch "${MAIL_DIR}"/.ignored_hidden_file
+cat > "${MAIL_DIR}"/mbox_file <<EOF
+From test_suite@notmuchmail.org Fri Jan  5 15:43:57 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Test mbox message 1
+
+Body.
+
+From test_suite@notmuchmail.org Fri Jan  5 15:43:57 2001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Test mbox message 2
+
+Body 2.
+EOF
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1)
+test_expect_equal "$output" \
+"Note: Ignoring non-mail file: ${MAIL_DIR}/.git/config
+Note: Ignoring non-mail file: ${MAIL_DIR}/.ignored_hidden_file
+Note: Ignoring non-mail file: ${MAIL_DIR}/ignored_file
+Note: Ignoring non-mail file: ${MAIL_DIR}/mbox_file
+Added 1 new message to the database."
+rm "${MAIL_DIR}"/mbox_file
+
+test_begin_subtest "Ignore files and directories specified in new.ignore"
+generate_message
+notmuch config set new.ignore .git ignored_file .ignored_hidden_file
+touch "${MAIL_DIR}"/.git # change .git's mtime for notmuch new to rescan.
+NOTMUCH_NEW --debug 2>&1 | sort > OUTPUT
+cat <<EOF > EXPECTED
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
+Added 1 new message to the database.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Ignore files and directories specified in new.ignore (full-scan)"
+generate_message
+notmuch config set new.ignore .git ignored_file .ignored_hidden_file
+NOTMUCH_NEW --debug --full-scan 2>&1 | sort > OUTPUT
+# reuse EXPECTED from previous test
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Ignore files and directories specified in new.ignore (multiple occurrences)"
+notmuch config set new.ignore .git ignored_file .ignored_hidden_file
+notmuch new > /dev/null # ensure that files/folders will be printed in ASCII order.
+mkdir -p "${MAIL_DIR}"/one/two/three/.git
+touch "${MAIL_DIR}"/{one,one/two,one/two/three}/ignored_file
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1 | sort)
+test_expect_equal "$output" \
+"(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+No new mail."
+
+
+test_begin_subtest "Don't stop for ignored broken symlinks"
+notmuch config set new.ignore .git ignored_file .ignored_hidden_file broken_link
+ln -s i_do_not_exist "${MAIL_DIR}"/broken_link
+output=$(NOTMUCH_NEW 2>&1)
+test_expect_equal "$output" "No new mail."
+
+test_begin_subtest "Ignore files and directories specified in new.ignore (regexp)"
+notmuch config set new.ignore ".git" "/^bro.*ink\$/" "/ignored.*file/"
+output=$(NOTMUCH_NEW --debug --full-scan 2>&1 | sort)
+test_expect_equal "$output" \
+"(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/broken_link
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 1: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/.ignored_hidden_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/broken_link
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/ignored_file
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/.git
+(D) add_files, pass 2: explicitly ignoring ${MAIL_DIR}/one/two/three/ignored_file
+No new mail."
+
+test_begin_subtest "Quiet: No new mail."
+output=$(NOTMUCH_NEW --quiet)
+test_expect_equal "$output" ""
+
+test_begin_subtest "Quiet: new, removed and renamed messages."
+# new
+generate_message
+# deleted
+notmuch search --format=text0 --output=files --limit=1 '*' | xargs -0 rm
+# moved
+mkdir "${MAIL_DIR}"/moved_messages
+notmuch search --format=text0 --output=files --offset=1 --limit=1 '*' | xargs -0 -I {} mv {} "${MAIL_DIR}"/moved_messages
+output=$(NOTMUCH_NEW --quiet)
+test_expect_equal "$output" ""
+
+OLDCONFIG=$(notmuch config get new.tags)
+
+test_begin_subtest "Empty tags in new.tags are forbidden"
+notmuch config set new.tags "foo;;bar"
+output=$(NOTMUCH_NEW --debug 2>&1)
+test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
+
+test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
+notmuch config set new.tags "-foo;bar"
+output=$(NOTMUCH_NEW --debug 2>&1)
+test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden"
+
+test_begin_subtest "Invalid tags set exit code"
+test_expect_code 1 "NOTMUCH_NEW --debug 2>&1"
+
+notmuch config set new.tags $OLDCONFIG
+
+
+test_begin_subtest "Xapian exception: read only files"
+chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+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 notmuch_database_index_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
+
+add_email_corpus broken
+test_begin_subtest "reference loop does not crash"
+test_expect_code 0 "notmuch show --format=json id:mid-loop-12@example.org id:mid-loop-21@example.org > OUTPUT"
+
+test_begin_subtest "reference loop ordered by date"
+threadid=$(notmuch search --output=threads  id:mid-loop-12@example.org)
+notmuch show --format=mbox $threadid | grep '^Date'  > OUTPUT
+cat <<EOF > EXPECTED
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+Date: Fri, 17 Jun 2016 22:14:41 -0400
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T060-count.sh b/test/T060-count.sh
new file mode 100755 (executable)
index 0000000..0c0bf47
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+test_description='"notmuch count" for messages and threads'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+# Note: The 'wc -l' results below are wrapped in arithmetic evaluation
+# $((...)) to strip whitespace. This is for portability, as 'wc -l'
+# emits whitespace on some BSD variants.
+
+test_begin_subtest "message count is the default for notmuch count"
+test_expect_equal \
+    "$((`notmuch search --output=messages '*' | wc -l`))" \
+    "`notmuch count '*'`"
+
+test_begin_subtest "message count with --output=messages"
+test_expect_equal \
+    "$((`notmuch search --output=messages '*' | wc -l`))" \
+    "`notmuch count --output=messages '*'`"
+
+test_begin_subtest "thread count with --output=threads"
+test_expect_equal \
+    "$((`notmuch search --output=threads '*' | wc -l`))" \
+    "`notmuch count --output=threads '*'`"
+
+test_begin_subtest "thread count is the default for notmuch search"
+test_expect_equal \
+    "$((`notmuch search '*' | wc -l`))" \
+    "`notmuch count --output=threads '*'`"
+
+test_begin_subtest "files count"
+test_expect_equal \
+    "$((`notmuch search --output=files '*' | wc -l`))" \
+    "`notmuch count --output=files '*'`"
+
+test_begin_subtest "files count for a duplicate message-id"
+test_expect_equal \
+    "2" \
+    "`notmuch count --output=files id:20091117232137.GA7669@griffis1.net`"
+
+test_begin_subtest "count with no matching messages"
+test_expect_equal \
+    "0" \
+    "`notmuch count --output=messages from:cworth and not from:cworth`"
+
+test_begin_subtest "count with no matching threads"
+test_expect_equal \
+    "0" \
+    "`notmuch count --output=threads from:cworth and not from:cworth`"
+
+test_begin_subtest "message count is the default for batch count"
+notmuch count --batch >OUTPUT <<EOF
+
+from:cworth
+EOF
+notmuch count --output=messages >EXPECTED
+notmuch count --output=messages from:cworth >>EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "batch message count"
+notmuch count --batch --output=messages >OUTPUT <<EOF
+from:cworth
+
+tag:inbox
+EOF
+notmuch count --output=messages from:cworth >EXPECTED
+notmuch count --output=messages >>EXPECTED
+notmuch count --output=messages tag:inbox >>EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "batch thread count"
+notmuch count --batch --output=threads >OUTPUT <<EOF
+
+from:cworth
+from:cworth and not from:cworth
+foo
+EOF
+notmuch count --output=threads >EXPECTED
+notmuch count --output=threads from:cworth >>EXPECTED
+notmuch count --output=threads from:cworth and not from:cworth >>EXPECTED
+notmuch count --output=threads foo >>EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "batch message count with input file"
+cat >INPUT <<EOF
+from:cworth
+
+tag:inbox
+EOF
+notmuch count --input=INPUT --output=messages >OUTPUT
+notmuch count --output=messages from:cworth >EXPECTED
+notmuch count --output=messages >>EXPECTED
+notmuch count --output=messages tag:inbox >>EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+backup_database
+test_begin_subtest "error message for database open"
+dd if=/dev/zero of="${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}" count=3
+notmuch count '*' 2>OUTPUT 1>/dev/null
+output=$(sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' OUTPUT)
+test_expect_equal "${output}" "A Xapian exception occurred opening database"
+restore_database
+
+cat <<EOF > count-files.gdb
+set breakpoint pending on
+set logging file count-files-gdb.log
+set logging on
+break count_files
+commands
+shell cp /dev/null ${MAIL_DIR}/.notmuch/xapian/postlist.${db_ending}
+continue
+end
+run
+EOF
+
+backup_database
+test_begin_subtest "error message from query_search_messages"
+${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
+A Xapian exception occurred performing query
+Query string was: *
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+test_begin_subtest "count library function is non-destructive"
+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
diff --git a/test/T070-insert.sh b/test/T070-insert.sh
new file mode 100755 (executable)
index 0000000..05be473
--- /dev/null
@@ -0,0 +1,294 @@
+#!/usr/bin/env bash
+test_description='"notmuch insert"'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_require_external_prereq gdb
+
+# subtests about file permissions assume that we're working with umask
+# 022 by default.
+umask 022
+
+# Create directories and database before inserting.
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+mkdir -p "$MAIL_DIR"/Drafts/{cur,new,tmp}
+notmuch new > /dev/null
+
+# We use generate_message to create the temporary message files.
+# They happen to be in the mail directory already but that is okay
+# since we do not call notmuch new hereafter.
+
+gen_insert_msg() {
+    generate_message \
+       "[subject]=\"insert-subject\"" \
+       "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" \
+       "[body]=\"insert-message\""
+}
+
+test_begin_subtest "Insert zero-length file"
+test_expect_code 1 "notmuch insert < /dev/null"
+
+# This test is a proxy for other errors that may occur while trying to
+# add a message to the notmuch database, e.g. database locked.
+test_begin_subtest "Insert non-message"
+test_expect_code 1 "echo bad_message | notmuch insert"
+
+test_begin_subtest "Database empty so far"
+test_expect_equal "0" "`notmuch count --output=messages '*'`"
+
+test_begin_subtest "Insert message"
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "subject:insert-subject")
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Permissions on inserted message should be 0600"
+test_expect_equal "600" "$(stat -c %a "$cur_msg_filename")"
+
+test_begin_subtest "Insert message adds default tags"
+output=$(notmuch show --format=json "subject:insert-subject")
+expected='[[[{
+ "id": "'"${gen_msg_id}"'",
+ "match": true,
+ "excluded": false,
+ "filename": ["'"${cur_msg_filename}"'"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","unread"],
+ "headers": {
+  "Subject": "insert-subject",
+  "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": "insert-message\n"}]},
+ []]]]'
+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`))
+test_expect_equal "$output" 2
+
+test_begin_subtest "Duplicate message does not change tags"
+output=$(notmuch search --format=json --output=tags "subject:insert-subject")
+test_expect_equal_json "$output" '["inbox", "unread"]'
+
+test_begin_subtest "Insert message, add tag"
+gen_insert_msg
+notmuch insert +custom < "$gen_msg_filename"
+output=$(notmuch search --output=messages tag:custom)
+test_expect_equal "$output" "id:$gen_msg_id"
+
+test_begin_subtest "Insert tagged world-readable message"
+gen_insert_msg
+notmuch insert --world-readable +world-readable-test < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "tag:world-readable-test")
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Permissions on inserted world-readable message should be 0644"
+test_expect_equal "644" "$(stat -c %a "$cur_msg_filename")"
+
+test_begin_subtest "Insert tagged world-readable message with group-only umask"
+oldumask=$(umask)
+umask 027
+gen_insert_msg
+notmuch insert --world-readable +world-readable-umask-test < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "tag:world-readable-umask-test")
+umask "$oldumask"
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Permissions on inserted world-readable message with funny umask should be 0640"
+test_expect_equal "640" "$(stat -c %a "$cur_msg_filename")"
+
+test_begin_subtest "Insert message, add/remove tags"
+gen_insert_msg
+notmuch insert +custom -unread < "$gen_msg_filename"
+output=$(notmuch search --output=messages tag:custom NOT tag:unread)
+test_expect_equal "$output" "id:$gen_msg_id"
+
+test_begin_subtest "Insert message with default tags stays in new/"
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message with non-maildir synced tags stays in new/"
+gen_insert_msg
+notmuch insert +custom -inbox < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message with custom new.tags goes to cur/"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags test
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+notmuch config set new.tags $OLDCONFIG
+test_expect_equal "$dirname" "$MAIL_DIR/cur"
+
+# additional check on the previous message
+test_begin_subtest "Insert message with custom new.tags actually gets the tags"
+output=$(notmuch search --output=tags id:$gen_msg_id)
+test_expect_equal "$output" "test"
+
+test_begin_subtest "Insert message with maildir synced tags goes to cur/"
+gen_insert_msg
+notmuch insert +flagged < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/cur"
+
+test_begin_subtest "Insert message with maildir sync off goes to new/"
+OLDCONFIG=$(notmuch config get maildir.synchronize_flags)
+notmuch config set maildir.synchronize_flags false
+gen_insert_msg
+notmuch insert +flagged < "$gen_msg_filename"
+output=$(notmuch search --output=files id:$gen_msg_id)
+dirname=$(dirname "$output")
+notmuch config set maildir.synchronize_flags $OLDCONFIG
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message into folder"
+gen_insert_msg
+notmuch insert --folder=Drafts < "$gen_msg_filename"
+output=$(notmuch search --output=files path:Drafts/new)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
+
+test_begin_subtest "Insert message into top level folder"
+gen_insert_msg
+notmuch insert --folder="" < "$gen_msg_filename"
+output=$(notmuch search --output=files id:${gen_msg_id})
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/new"
+
+test_begin_subtest "Insert message into folder with trailing /"
+gen_insert_msg
+notmuch insert --folder=Drafts/ < "$gen_msg_filename"
+output=$(notmuch search --output=files id:${gen_msg_id})
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
+
+test_begin_subtest "Insert message into folder, add/remove tags"
+gen_insert_msg
+notmuch insert --folder=Drafts +draft -unread < "$gen_msg_filename"
+output=$(notmuch search --output=messages path:Drafts/cur tag:draft NOT tag:unread)
+test_expect_equal "$output" "id:$gen_msg_id"
+
+test_begin_subtest "Insert message into non-existent folder"
+gen_insert_msg
+test_expect_code 1 "notmuch insert --folder=nonesuch < $gen_msg_filename"
+
+test_begin_subtest "Insert message, create folder"
+gen_insert_msg
+notmuch insert --folder=F --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch search --output=files path:F/new tag:folder)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "$MAIL_DIR/F/new/${basename}"
+
+test_begin_subtest "Insert message, create subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch search --output=files path:F/G/H/I/J/new tag:folder)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/new/${basename}"
+
+test_begin_subtest "Created subfolder should have permissions 0700"
+test_expect_equal "700" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J")"
+test_begin_subtest "Created subfolder new/ should also have permissions 0700"
+test_expect_equal "700" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/new")"
+
+test_begin_subtest "Insert message, create world-readable subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J/K --create-folder --world-readable +folder-world-readable < "$gen_msg_filename"
+output=$(notmuch search --output=files path:F/G/H/I/J/K/new tag:folder-world-readable)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/K/new/${basename}"
+
+test_begin_subtest "Created world-readable subfolder should have permissions 0755"
+test_expect_equal "755" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/K")"
+test_begin_subtest "Created world-readable subfolder new/ should also have permissions 0755"
+test_expect_equal "755" "$(stat -c %a "${MAIL_DIR}/F/G/H/I/J/K/new")"
+
+test_begin_subtest "Insert message, create existing subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch count path:F/G/H/I/J/new tag:folder)
+test_expect_equal "$output" "2"
+
+test_begin_subtest "Insert message, create invalid subfolder"
+gen_insert_msg
+test_expect_code 1 "notmuch insert --folder=../G --create-folder $gen_msg_filename"
+
+OLDCONFIG=$(notmuch config get new.tags)
+
+test_begin_subtest "Empty tags in new.tags are forbidden"
+notmuch config set new.tags "foo;;bar"
+gen_insert_msg
+output=$(notmuch insert $gen_msg_filename 2>&1)
+test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
+
+test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
+notmuch config set new.tags "-foo;bar"
+gen_insert_msg
+output=$(notmuch insert $gen_msg_filename 2>&1)
+test_expect_equal "$output" "Error: tag '-foo' in new.tags: tag starting with '-' forbidden"
+
+test_begin_subtest "Invalid tags set exit code"
+test_expect_code 1 "notmuch insert $gen_msg_filename 2>&1"
+
+notmuch config set new.tags $OLDCONFIG
+
+# DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass.
+
+for code in OUT_OF_MEMORY XAPIAN_EXCEPTION FILE_NOT_EMAIL \
+    READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
+cat <<EOF > index-file-$code.gdb
+set breakpoint pending on
+set logging file index-file-$code.log
+set logging on
+break notmuch_database_index_file
+commands
+return NOTMUCH_STATUS_$code
+continue
+end
+run
+EOF
+done
+
+gen_insert_msg
+
+for code in  FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
+    test_begin_subtest "EXIT_FAILURE when index_file returns $code"
+    test_expect_code 1 \
+         "${TEST_GDB} --batch-silent --return-child-result \
+            -ex 'set args insert < $gen_msg_filename' \
+            -x index-file-$code.gdb notmuch"
+
+    test_begin_subtest "success exit with --keep when index_file returns $code"
+    test_expect_code 0 \
+         "${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_begin_subtest "EX_TEMPFAIL when index_file returns $code"
+    test_expect_code 75 \
+         "${TEST_GDB} --batch-silent --return-child-result \
+            -ex 'set args insert < $gen_msg_filename' \
+            -x index-file-$code.gdb notmuch"
+
+    test_begin_subtest "success exit with --keep when index_file returns $code"
+    test_expect_code 0 \
+         "${TEST_GDB} --batch-silent --return-child-result \
+            -ex 'set args insert --keep < $gen_msg_filename' \
+            -x index-file-$code.gdb notmuch"
+done
+
+test_done
diff --git a/test/T080-search.sh b/test/T080-search.sh
new file mode 100755 (executable)
index 0000000..a3f0dea
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/env bash
+test_description='"notmuch search" in several variations'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "Search body"
+add_message '[subject]="body search"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [body]=bodysearchtest
+output=$(notmuch search bodysearchtest | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; body search (inbox unread)"
+
+test_begin_subtest "Search by from:"
+add_message '[subject]="search by from"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom
+output=$(notmuch search from:searchbyfrom | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] searchbyfrom; search by from (inbox unread)"
+
+test_begin_subtest "Search by to:"
+add_message '[subject]="search by to"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto
+output=$(notmuch search to:searchbyto | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)"
+
+test_begin_subtest "Search by subject:"
+add_message [subject]=subjectsearchtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search subject:subjectsearchtest | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)"
+
+test_begin_subtest "Search by subject (utf-8):"
+add_message [subject]=utf8-sübjéct '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search subject:utf8-sübjéct | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)"
+
+test_begin_subtest "Search by id:"
+add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search id:${gen_msg_id} | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
+
+test_begin_subtest "Search by mid:"
+add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search mid:${gen_msg_id} | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)"
+
+test_begin_subtest "Search by tag:"
+add_message '[subject]="search by tag"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+notmuch tag +searchbytag id:${gen_msg_id}
+output=$(notmuch search tag:searchbytag | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)"
+
+test_begin_subtest "Search by thread:"
+add_message '[subject]="search by thread"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+thread_id=$(notmuch search id:${gen_msg_id} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")
+output=$(notmuch search thread:${thread_id} | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)"
+
+test_begin_subtest "Search body (phrase)"
+add_message '[subject]="body search (phrase)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="body search (phrase)"'
+add_message '[subject]="negative result"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="This phrase should not match the body search"'
+output=$(notmuch search '"body search (phrase)"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; body search (phrase) (inbox unread)"
+
+test_begin_subtest "Search by from: (address)"
+add_message '[subject]="search by from (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [from]=searchbyfrom@example.com
+output=$(notmuch search from:searchbyfrom@example.com | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] searchbyfrom@example.com; search by from (address) (inbox unread)"
+
+test_begin_subtest "Search by from: (name)"
+add_message '[subject]="search by from (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[from]="Search By From Name <test@example.com>"'
+output=$(notmuch search 'from:"Search By From Name"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
+
+test_begin_subtest "Search by from: (name and address)"
+output=$(notmuch search 'from:"Search By From Name <test@example.com>"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
+
+test_begin_subtest "Search by from: without prefix (name and address)"
+output=$(notmuch search '"Search By From Name <test@example.com>"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)"
+
+test_begin_subtest "Search by to: (address)"
+add_message '[subject]="search by to (address)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' [to]=searchbyto@example.com
+output=$(notmuch search to:searchbyto@example.com | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)"
+
+test_begin_subtest "Search by to: (name)"
+add_message '[subject]="search by to (name)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[to]="Search By To Name <test@example.com>"'
+output=$(notmuch search 'to:"Search By To Name"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
+
+test_begin_subtest "Search by to: (name and address)"
+output=$(notmuch search 'to:"Search By To Name <test@example.com>"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
+
+test_begin_subtest "Search by to: without prefix (name and address)"
+output=$(notmuch search '"Search By To Name <test@example.com>"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)"
+
+test_begin_subtest "Search by subject: (phrase)"
+add_message '[subject]="subject search test (phrase)"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+add_message '[subject]="this phrase should not match the subject search test"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
+output=$(notmuch search 'subject:"subject search test (phrase)"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subject search test (phrase) (inbox unread)"
+
+test_begin_subtest 'Search for all messages ("*")'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+cat <<EOF >EXPECTED
+thread:XXX   2010-12-29 [1/1] François Boulogne; [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)
+thread:XXX   2009-11-18 [1/1] Chris Wilson; [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+thread:XXX   2009-11-18 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+thread:XXX   2009-11-18 [2/2] Ingmar Vanhassel, Carl Worth; [notmuch] [PATCH] Typsos (inbox unread)
+thread:XXX   2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth; [notmuch] Introducing myself (inbox signed unread)
+thread:XXX   2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth; [notmuch] New to the list (inbox unread)
+thread:XXX   2009-11-18 [3/3] Jan Janak, Carl Worth; [notmuch] What a great idea! (inbox unread)
+thread:XXX   2009-11-18 [2/2] Jan Janak, Carl Worth; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+thread:XXX   2009-11-18 [3/3(4)] Aron Griffis, Keith Packard, Carl Worth; [notmuch] archive (inbox unread)
+thread:XXX   2009-11-18 [2/2] Keith Packard, Carl Worth; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+thread:XXX   2009-11-18 [5/5] Mikhail Gusarov, Carl Worth, Keith Packard; [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+thread:XXX   2009-11-18 [2/2] Keith Packard, Alexander Botero-Lowry; [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+thread:XXX   2009-11-18 [1/1] Alexander Botero-Lowry; [notmuch] request for pull (inbox unread)
+thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+thread:XXX   2009-11-18 [1/1] Rolland Santimano; [notmuch] Link to mailing list archives ? (inbox unread)
+thread:XXX   2009-11-18 [1/1] Jan Janak; [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+thread:XXX   2009-11-18 [1/1] Stewart Smith; [notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+thread:XXX   2009-11-18 [1/1] Stewart Smith; [notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+thread:XXX   2009-11-18 [1/1] Stewart Smith; [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+thread:XXX   2009-11-18 [2/2] Lars Kellogg-Stedman; [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+thread:XXX   2009-11-17 [1/1] Mikhail Gusarov; [notmuch] [PATCH] Handle rename of message file (inbox unread)
+thread:XXX   2009-11-17 [2/2] Alex Botero-Lowry, Carl Worth; [notmuch] preliminary FreeBSD support (attachment inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; body search (inbox unread)
+thread:XXX   2000-01-01 [1/1] searchbyfrom; search by from (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; body search (phrase) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; negative result (inbox unread)
+thread:XXX   2000-01-01 [1/1] searchbyfrom@example.com; search by from (address) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Search By From Name; search by from (name) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (address) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; search by to (name) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; subject search test (phrase) (inbox unread)
+thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; this phrase should not match the subject search test (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Search body (utf-8):"
+add_message '[subject]="utf8-message-body-subject"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' '[body]="message body utf8: bödý"'
+output=$(notmuch search "bödý" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; utf8-message-body-subject (inbox unread)"
+
+
+cat <<EOF > ${MAIL_DIR}/termpos
+From: Source <source@example.com>
+To: Dest <dest@example.com>
+Subject: part overlap test
+Date: Sat, 01 January 2000 00:00:00 +0000
+Message-ID: <termpos>
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="==-=="
+
+--==-==
+Content-Type: text/plain
+
+a b c
+
+--==-==
+Content-Type: text/plain
+
+x y z
+
+--==-==--
+EOF
+notmuch new > /dev/null
+
+test_begin_subtest "headers do not have adjacent term positions"
+# Regression test for a bug where term positions for non-prefixed
+# terms weren't updated
+output=$(notmuch search id:termpos and '"com dest"')
+test_expect_equal "$output" ""
+
+test_begin_subtest "parts have non-overlapping term positions"
+output=$(notmuch search id:termpos and '"a y c"')
+test_expect_equal "$output" ""
+
+test_begin_subtest "parts do not have adjacent term positions"
+output=$(notmuch search id:termpos and '"c x"')
+test_expect_equal "$output" ""
+
+test_done
diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh
new file mode 100755 (executable)
index 0000000..bf28d22
--- /dev/null
@@ -0,0 +1,446 @@
+#!/usr/bin/env bash
+test_description='various settings for "notmuch search --output="'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "--output=threads"
+notmuch search --output=threads '*' | sed -e s/thread:.*/thread:THREADID/ >OUTPUT
+cat <<EOF >EXPECTED
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+thread:THREADID
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=threads --format=json"
+notmuch search --format=json --output=threads '*' | sed -e s/\".*\"/\"THREADID\"/ >OUTPUT
+cat <<EOF >EXPECTED
+["THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID",
+"THREADID"]
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--output=messages"
+notmuch search --output=messages '*' >OUTPUT
+cat <<EOF >EXPECTED
+id:4EFC743A.3060609@april.org
+id:877h1wv7mg.fsf@inf-8657.int-evry.fr
+id:1258544095-16616-1-git-send-email-chris@chris-wilson.co.uk
+id:877htoqdbo.fsf@yoom.home.cworth.org
+id:878we4qdqf.fsf@yoom.home.cworth.org
+id:87aaykqe24.fsf@yoom.home.cworth.org
+id:87bpj0qeng.fsf@yoom.home.cworth.org
+id:87fx8cqf8v.fsf@yoom.home.cworth.org
+id:87hbssqfix.fsf@yoom.home.cworth.org
+id:87iqd8qgiz.fsf@yoom.home.cworth.org
+id:87k4xoqgnl.fsf@yoom.home.cworth.org
+id:87ocn0qh6d.fsf@yoom.home.cworth.org
+id:87pr7gqidx.fsf@yoom.home.cworth.org
+id:867hto2p0t.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me
+id:1258532999-9316-1-git-send-email-keithp@keithp.com
+id:86aayk2rbj.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me
+id:86d43g2w3y.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me
+id:ddd65cda0911172214t60d22b63hcfeb5a19ab54a39b@mail.gmail.com
+id:86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me
+id:736613.51770.qm@web113505.mail.gq1.yahoo.com
+id:1258520223-15328-1-git-send-email-jan@ryngle.com
+id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com
+id:1258510940-7018-1-git-send-email-stewart@flamingspork.com
+id:yunzl6kd1w0.fsf@aiko.keithp.com
+id:yun1vjwegii.fsf@aiko.keithp.com
+id:yun3a4cegoa.fsf@aiko.keithp.com
+id:1258509400-32511-1-git-send-email-stewart@flamingspork.com
+id:1258506353-20352-1-git-send-email-stewart@flamingspork.com
+id:20091118010116.GC25380@dottiness.seas.harvard.edu
+id:20091118005829.GB25380@dottiness.seas.harvard.edu
+id:20091118005040.GA25380@dottiness.seas.harvard.edu
+id:cf0c4d610911171623q3e27a0adx802e47039b57604b@mail.gmail.com
+id:1258500222-32066-1-git-send-email-ingmar@exherbo.org
+id:20091117232137.GA7669@griffis1.net
+id:20091118002059.067214ed@hikari
+id:1258498485-sup-142@elly
+id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com
+id:f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com
+id:1258496327-12086-1-git-send-email-jan@ryngle.com
+id:1258493565-13508-1-git-send-email-keithp@keithp.com
+id:yunaayketfm.fsf@aiko.keithp.com
+id:yunbpj0etua.fsf@aiko.keithp.com
+id:1258491078-29658-1-git-send-email-dottedmag@dottedmag.net
+id:87fx8can9z.fsf@vertex.dottedmag
+id:20091117203301.GV3165@dottiness.seas.harvard.edu
+id:87lji4lx9v.fsf@yoom.home.cworth.org
+id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com
+id:87iqd9rn3l.fsf@vertex.dottedmag
+id:20091117190054.GU3165@dottiness.seas.harvard.edu
+id:87lji5cbwo.fsf@yoom.home.cworth.org
+id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
+id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --duplicate=1"
+notmuch search --output=messages --duplicate=1 '*' >OUTPUT
+# reuse same EXPECTED as above
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --duplicate=2"
+notmuch search --output=messages --duplicate=2 '*' >OUTPUT
+cat <<EOF >EXPECTED
+id:20091117232137.GA7669@griffis1.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --duplicate=3"
+notmuch search --output=messages --duplicate=3 '*' >OUTPUT
+cat <<EOF >EXPECTED
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --format=json"
+notmuch search --format=json --output=messages '*' >OUTPUT
+cat <<EOF >EXPECTED
+["4EFC743A.3060609@april.org",
+"877h1wv7mg.fsf@inf-8657.int-evry.fr",
+"1258544095-16616-1-git-send-email-chris@chris-wilson.co.uk",
+"877htoqdbo.fsf@yoom.home.cworth.org",
+"878we4qdqf.fsf@yoom.home.cworth.org",
+"87aaykqe24.fsf@yoom.home.cworth.org",
+"87bpj0qeng.fsf@yoom.home.cworth.org",
+"87fx8cqf8v.fsf@yoom.home.cworth.org",
+"87hbssqfix.fsf@yoom.home.cworth.org",
+"87iqd8qgiz.fsf@yoom.home.cworth.org",
+"87k4xoqgnl.fsf@yoom.home.cworth.org",
+"87ocn0qh6d.fsf@yoom.home.cworth.org",
+"87pr7gqidx.fsf@yoom.home.cworth.org",
+"867hto2p0t.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me",
+"1258532999-9316-1-git-send-email-keithp@keithp.com",
+"86aayk2rbj.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me",
+"86d43g2w3y.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me",
+"ddd65cda0911172214t60d22b63hcfeb5a19ab54a39b@mail.gmail.com",
+"86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me",
+"736613.51770.qm@web113505.mail.gq1.yahoo.com",
+"1258520223-15328-1-git-send-email-jan@ryngle.com",
+"ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com",
+"1258510940-7018-1-git-send-email-stewart@flamingspork.com",
+"yunzl6kd1w0.fsf@aiko.keithp.com",
+"yun1vjwegii.fsf@aiko.keithp.com",
+"yun3a4cegoa.fsf@aiko.keithp.com",
+"1258509400-32511-1-git-send-email-stewart@flamingspork.com",
+"1258506353-20352-1-git-send-email-stewart@flamingspork.com",
+"20091118010116.GC25380@dottiness.seas.harvard.edu",
+"20091118005829.GB25380@dottiness.seas.harvard.edu",
+"20091118005040.GA25380@dottiness.seas.harvard.edu",
+"cf0c4d610911171623q3e27a0adx802e47039b57604b@mail.gmail.com",
+"1258500222-32066-1-git-send-email-ingmar@exherbo.org",
+"20091117232137.GA7669@griffis1.net",
+"20091118002059.067214ed@hikari",
+"1258498485-sup-142@elly",
+"f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com",
+"f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com",
+"1258496327-12086-1-git-send-email-jan@ryngle.com",
+"1258493565-13508-1-git-send-email-keithp@keithp.com",
+"yunaayketfm.fsf@aiko.keithp.com",
+"yunbpj0etua.fsf@aiko.keithp.com",
+"1258491078-29658-1-git-send-email-dottedmag@dottedmag.net",
+"87fx8can9z.fsf@vertex.dottedmag",
+"20091117203301.GV3165@dottiness.seas.harvard.edu",
+"87lji4lx9v.fsf@yoom.home.cworth.org",
+"cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com",
+"87iqd9rn3l.fsf@vertex.dottedmag",
+"20091117190054.GU3165@dottiness.seas.harvard.edu",
+"87lji5cbwo.fsf@yoom.home.cworth.org",
+"1258471718-6781-2-git-send-email-dottedmag@dottedmag.net",
+"1258471718-6781-1-git-send-email-dottedmag@dottedmag.net"]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --format=json --duplicate=1"
+notmuch search --output=messages --format=json --duplicate=1 '*' >OUTPUT
+# reuse same EXPECTED as above
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --format=json --duplicate=2"
+notmuch search --output=messages --format=json --duplicate=2 '*' >OUTPUT
+cat <<EOF >EXPECTED
+["20091117232137.GA7669@griffis1.net"]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=messages --format=json --duplicate=3"
+notmuch search --output=messages --format=json --duplicate=3 '*' >OUTPUT
+cat <<EOF >EXPECTED
+[]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=files"
+notmuch search --output=files '*' | notmuch_search_files_sanitize | sort >OUTPUT
+cat <<EOF >EXPECTED
+MAIL_DIR/01:2,
+MAIL_DIR/02:2,
+MAIL_DIR/bar/17:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/bar/baz/23:2,
+MAIL_DIR/bar/baz/24:2,
+MAIL_DIR/bar/baz/cur/25:2,
+MAIL_DIR/bar/baz/cur/26:2,
+MAIL_DIR/bar/baz/new/27:2,
+MAIL_DIR/bar/baz/new/28:2,
+MAIL_DIR/bar/cur/19:2,
+MAIL_DIR/bar/cur/20:2,
+MAIL_DIR/bar/new/21:2,
+MAIL_DIR/bar/new/22:2,
+MAIL_DIR/cur/29:2,
+MAIL_DIR/cur/30:2,
+MAIL_DIR/cur/31:2,
+MAIL_DIR/cur/32:2,
+MAIL_DIR/cur/33:2,
+MAIL_DIR/cur/34:2,
+MAIL_DIR/cur/35:2,
+MAIL_DIR/cur/36:2,
+MAIL_DIR/cur/37:2,
+MAIL_DIR/cur/38:2,
+MAIL_DIR/cur/39:2,
+MAIL_DIR/cur/40:2,
+MAIL_DIR/cur/41:2,
+MAIL_DIR/cur/42:2,
+MAIL_DIR/cur/43:2,
+MAIL_DIR/cur/44:2,
+MAIL_DIR/cur/45:2,
+MAIL_DIR/cur/46:2,
+MAIL_DIR/cur/47:2,
+MAIL_DIR/cur/48:2,
+MAIL_DIR/cur/49:2,
+MAIL_DIR/cur/50:2,
+MAIL_DIR/cur/51:2,
+MAIL_DIR/cur/52:2,
+MAIL_DIR/cur/53:2,
+MAIL_DIR/foo/06:2,
+MAIL_DIR/foo/baz/11:2,
+MAIL_DIR/foo/baz/12:2,
+MAIL_DIR/foo/baz/cur/13:2,
+MAIL_DIR/foo/baz/cur/14:2,
+MAIL_DIR/foo/baz/new/15:2,
+MAIL_DIR/foo/baz/new/16:2,
+MAIL_DIR/foo/cur/07:2,
+MAIL_DIR/foo/cur/08:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/foo/new/09:2,
+MAIL_DIR/foo/new/10:2,
+MAIL_DIR/new/04:2,
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+dup1=$(notmuch search --output=files id:20091117232137.GA7669@griffis1.net | head -n 1 | sed -e "s,$MAIL_DIR,MAIL_DIR,")
+dup2=$(notmuch search --output=files id:20091117232137.GA7669@griffis1.net | tail -n 1 | sed -e "s,$MAIL_DIR,MAIL_DIR,")
+
+test_begin_subtest "--output=files --duplicate=1"
+notmuch search --output=files --duplicate=1 '*' | notmuch_search_files_sanitize | sort >OUTPUT
+cat <<EOF | sort >EXPECTED
+$dup1
+MAIL_DIR/cur/52:2,
+MAIL_DIR/cur/53:2,
+MAIL_DIR/cur/50:2,
+MAIL_DIR/cur/49:2,
+MAIL_DIR/cur/48:2,
+MAIL_DIR/cur/47:2,
+MAIL_DIR/cur/46:2,
+MAIL_DIR/cur/45:2,
+MAIL_DIR/cur/44:2,
+MAIL_DIR/cur/43:2,
+MAIL_DIR/cur/42:2,
+MAIL_DIR/cur/41:2,
+MAIL_DIR/cur/40:2,
+MAIL_DIR/cur/39:2,
+MAIL_DIR/cur/38:2,
+MAIL_DIR/cur/37:2,
+MAIL_DIR/cur/36:2,
+MAIL_DIR/cur/35:2,
+MAIL_DIR/cur/34:2,
+MAIL_DIR/cur/33:2,
+MAIL_DIR/cur/32:2,
+MAIL_DIR/cur/31:2,
+MAIL_DIR/cur/30:2,
+MAIL_DIR/cur/29:2,
+MAIL_DIR/bar/baz/new/28:2,
+MAIL_DIR/bar/baz/new/27:2,
+MAIL_DIR/bar/baz/cur/26:2,
+MAIL_DIR/bar/baz/cur/25:2,
+MAIL_DIR/bar/baz/24:2,
+MAIL_DIR/bar/baz/23:2,
+MAIL_DIR/bar/new/22:2,
+MAIL_DIR/bar/new/21:2,
+MAIL_DIR/bar/cur/19:2,
+MAIL_DIR/bar/cur/20:2,
+MAIL_DIR/bar/17:2,
+MAIL_DIR/foo/baz/new/16:2,
+MAIL_DIR/foo/baz/new/15:2,
+MAIL_DIR/foo/baz/cur/14:2,
+MAIL_DIR/foo/baz/cur/13:2,
+MAIL_DIR/foo/baz/12:2,
+MAIL_DIR/foo/baz/11:2,
+MAIL_DIR/foo/new/10:2,
+MAIL_DIR/foo/new/09:2,
+MAIL_DIR/foo/cur/08:2,
+MAIL_DIR/foo/06:2,
+MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/new/04:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/foo/cur/07:2,
+MAIL_DIR/02:2,
+MAIL_DIR/01:2,
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=files --format=json"
+notmuch search --format=json --output=files '*' | notmuch_search_files_sanitize \
+    | test_sort_json >OUTPUT
+cat <<EOF | test_sort_json >EXPECTED
+["MAIL_DIR/cur/52:2,",
+"MAIL_DIR/cur/53:2,",
+"MAIL_DIR/cur/50:2,",
+"MAIL_DIR/cur/49:2,",
+"MAIL_DIR/cur/48:2,",
+"MAIL_DIR/cur/47:2,",
+"MAIL_DIR/cur/46:2,",
+"MAIL_DIR/cur/45:2,",
+"MAIL_DIR/cur/44:2,",
+"MAIL_DIR/cur/43:2,",
+"MAIL_DIR/cur/42:2,",
+"MAIL_DIR/cur/41:2,",
+"MAIL_DIR/cur/40:2,",
+"MAIL_DIR/cur/39:2,",
+"MAIL_DIR/cur/38:2,",
+"MAIL_DIR/cur/37:2,",
+"MAIL_DIR/cur/36:2,",
+"MAIL_DIR/cur/35:2,",
+"MAIL_DIR/cur/34:2,",
+"MAIL_DIR/cur/33:2,",
+"MAIL_DIR/cur/32:2,",
+"MAIL_DIR/cur/31:2,",
+"MAIL_DIR/cur/30:2,",
+"MAIL_DIR/cur/29:2,",
+"MAIL_DIR/bar/baz/new/28:2,",
+"MAIL_DIR/bar/baz/new/27:2,",
+"MAIL_DIR/bar/baz/cur/26:2,",
+"MAIL_DIR/bar/baz/cur/25:2,",
+"MAIL_DIR/bar/baz/24:2,",
+"MAIL_DIR/bar/baz/23:2,",
+"MAIL_DIR/bar/new/22:2,",
+"MAIL_DIR/bar/new/21:2,",
+"MAIL_DIR/bar/cur/19:2,",
+"MAIL_DIR/bar/18:2,",
+"MAIL_DIR/cur/51:2,",
+"MAIL_DIR/bar/cur/20:2,",
+"MAIL_DIR/bar/17:2,",
+"MAIL_DIR/foo/baz/new/16:2,",
+"MAIL_DIR/foo/baz/new/15:2,",
+"MAIL_DIR/foo/baz/cur/14:2,",
+"MAIL_DIR/foo/baz/cur/13:2,",
+"MAIL_DIR/foo/baz/12:2,",
+"MAIL_DIR/foo/baz/11:2,",
+"MAIL_DIR/foo/new/10:2,",
+"MAIL_DIR/foo/new/09:2,",
+"MAIL_DIR/foo/cur/08:2,",
+"MAIL_DIR/foo/06:2,",
+"MAIL_DIR/bar/baz/05:2,",
+"MAIL_DIR/new/04:2,",
+"MAIL_DIR/foo/new/03:2,",
+"MAIL_DIR/foo/cur/07:2,",
+"MAIL_DIR/02:2,",
+"MAIL_DIR/01:2,"]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=files --format=json --duplicate=2"
+notmuch search --format=json --output=files --duplicate=2 '*' | notmuch_search_files_sanitize >OUTPUT
+cat <<EOF >EXPECTED
+["$dup2"]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=tags"
+notmuch search --output=tags '*' >OUTPUT
+cat <<EOF >EXPECTED
+attachment
+inbox
+signed
+unread
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=tags --format=json"
+notmuch search --format=json --output=tags '*' >OUTPUT
+cat <<EOF >EXPECTED
+["attachment",
+"inbox",
+"signed",
+"unread"]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "sanitize output for quoted-printable line-breaks in author and subject"
+add_message "[subject]='two =?ISO-8859-1?Q?line=0A_subject?=
+       headers'"
+notmuch search id:"$gen_msg_id" | notmuch_search_sanitize >OUTPUT
+cat <<EOF >EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; two line  subject headers (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search for non-existent message prints nothing"
+notmuch search "no-message-matches-this" > OUTPUT
+echo -n >EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search --format=json for non-existent message prints proper empty json"
+notmuch search --format=json "no-message-matches-this" > OUTPUT
+echo "[]" >EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T095-address.sh b/test/T095-address.sh
new file mode 100755 (executable)
index 0000000..817be53
--- /dev/null
@@ -0,0 +1,328 @@
+#!/usr/bin/env bash
+test_description='"notmuch address" in several variants'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "--output=sender"
+notmuch address --output=sender '*' >OUTPUT
+cat <<EOF >EXPECTED
+François Boulogne <boulogne.f@gmail.com>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+Chris Wilson <chris@chris-wilson.co.uk>
+Carl Worth <cworth@cworth.org>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Jan Janak <jan@ryngle.com>
+Stewart Smith <stewart@flamingspork.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Aron Griffis <agriffis@n01se.net>
+Adrian Perez de Castro <aperez@igalia.com>
+Israel Herraiz <isra@herraiz.org>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "without --output"
+notmuch address '*' >OUTPUT
+# Use EXPECTED from previous subtest
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=sender --format=json"
+notmuch address --output=sender --format=json '*' >OUTPUT
+cat <<EOF >EXPECTED
+[{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "name-addr": "François Boulogne <boulogne.f@gmail.com>"},
+{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "name-addr": "Olivier Berger <olivier.berger@it-sudparis.eu>"},
+{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "name-addr": "Chris Wilson <chris@chris-wilson.co.uk>"},
+{"name": "Carl Worth", "address": "cworth@cworth.org", "name-addr": "Carl Worth <cworth@cworth.org>"},
+{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alexander Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Keith Packard", "address": "keithp@keithp.com", "name-addr": "Keith Packard <keithp@keithp.com>"},
+{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "name-addr": "Jjgod Jiang <gzjjgod@gmail.com>"},
+{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "name-addr": "Rolland Santimano <rollandsantimano@yahoo.com>"},
+{"name": "Jan Janak", "address": "jan@ryngle.com", "name-addr": "Jan Janak <jan@ryngle.com>"},
+{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "name-addr": "Stewart Smith <stewart@flamingspork.com>"},
+{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "name-addr": "Lars Kellogg-Stedman <lars@seas.harvard.edu>"},
+{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alex Botero-Lowry <alex.boterolowry@gmail.com>"},
+{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "name-addr": "Ingmar Vanhassel <ingmar@exherbo.org>"},
+{"name": "Aron Griffis", "address": "agriffis@n01se.net", "name-addr": "Aron Griffis <agriffis@n01se.net>"},
+{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "name-addr": "Adrian Perez de Castro <aperez@igalia.com>"},
+{"name": "Israel Herraiz", "address": "isra@herraiz.org", "name-addr": "Israel Herraiz <isra@herraiz.org>"},
+{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "name-addr": "Mikhail Gusarov <dottedmag@dottedmag.net>"}]
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=recipients"
+notmuch address --output=recipients '*' >OUTPUT
+cat <<EOF >EXPECTED
+Allan McRae <allan@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+olivier.berger@it-sudparis.eu
+notmuch@notmuchmail.org
+notmuch <notmuch@notmuchmail.org>
+Keith Packard <keithp@keithp.com>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=sender --output=recipients"
+notmuch address --output=sender --output=recipients '*' >OUTPUT
+cat <<EOF >EXPECTED
+François Boulogne <boulogne.f@gmail.com>
+Allan McRae <allan@archlinux.org>
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+olivier.berger@it-sudparis.eu
+Chris Wilson <chris@chris-wilson.co.uk>
+notmuch@notmuchmail.org
+Carl Worth <cworth@cworth.org>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Jan Janak <jan@ryngle.com>
+Stewart Smith <stewart@flamingspork.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+notmuch <notmuch@notmuchmail.org>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Aron Griffis <agriffis@n01se.net>
+Adrian Perez de Castro <aperez@igalia.com>
+Israel Herraiz <isra@herraiz.org>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=sender --output=count"
+notmuch address --output=sender --output=count '*' | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1      Adrian Perez de Castro <aperez@igalia.com>
+1      Aron Griffis <agriffis@n01se.net>
+1      Chris Wilson <chris@chris-wilson.co.uk>
+1      François Boulogne <boulogne.f@gmail.com>
+1      Ingmar Vanhassel <ingmar@exherbo.org>
+1      Israel Herraiz <isra@herraiz.org>
+1      Olivier Berger <olivier.berger@it-sudparis.eu>
+1      Rolland Santimano <rollandsantimano@yahoo.com>
+2      Alex Botero-Lowry <alex.boterolowry@gmail.com>
+2      Jjgod Jiang <gzjjgod@gmail.com>
+3      Stewart Smith <stewart@flamingspork.com>
+4      Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+4      Jan Janak <jan@ryngle.com>
+5      Lars Kellogg-Stedman <lars@seas.harvard.edu>
+5      Mikhail Gusarov <dottedmag@dottedmag.net>
+7      Keith Packard <keithp@keithp.com>
+12     Carl Worth <cworth@cworth.org>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=recipients --output=address"
+notmuch address --output=recipients --output=address '*' >OUTPUT
+cat <<EOF >EXPECTED
+allan@archlinux.org
+aur-general@archlinux.org
+olivier.berger@it-sudparis.eu
+notmuch@notmuchmail.org
+notmuch@notmuchmail.org
+keithp@keithp.com
+dottedmag@dottedmag.net
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=sender --output=address --output=count"
+notmuch address --output=sender --output=address --output=count '*' | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1      agriffis@n01se.net
+1      aperez@igalia.com
+1      boulogne.f@gmail.com
+1      chris@chris-wilson.co.uk
+1      ingmar@exherbo.org
+1      isra@herraiz.org
+1      olivier.berger@it-sudparis.eu
+1      rollandsantimano@yahoo.com
+2      alex.boterolowry@gmail.com
+2      gzjjgod@gmail.com
+3      stewart@flamingspork.com
+4      alex.boterolowry@gmail.com
+4      jan@ryngle.com
+5      dottedmag@dottedmag.net
+5      lars@seas.harvard.edu
+7      keithp@keithp.com
+12     cworth@cworth.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--output=count --format=json"
+# Since the iteration order of GHashTable is not specified, we
+# preprocess and sort the results to keep the order stable here.
+notmuch address --output=count --format=json '*' | \
+    sed -e 's/^\[//' -e 's/]$//' -e 's/,$//' | sort >OUTPUT
+cat <<EOF >EXPECTED
+{"name": "Adrian Perez de Castro", "address": "aperez@igalia.com", "name-addr": "Adrian Perez de Castro <aperez@igalia.com>", "count": 1}
+{"name": "Alex Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alex Botero-Lowry <alex.boterolowry@gmail.com>", "count": 2}
+{"name": "Alexander Botero-Lowry", "address": "alex.boterolowry@gmail.com", "name-addr": "Alexander Botero-Lowry <alex.boterolowry@gmail.com>", "count": 4}
+{"name": "Aron Griffis", "address": "agriffis@n01se.net", "name-addr": "Aron Griffis <agriffis@n01se.net>", "count": 1}
+{"name": "Carl Worth", "address": "cworth@cworth.org", "name-addr": "Carl Worth <cworth@cworth.org>", "count": 12}
+{"name": "Chris Wilson", "address": "chris@chris-wilson.co.uk", "name-addr": "Chris Wilson <chris@chris-wilson.co.uk>", "count": 1}
+{"name": "François Boulogne", "address": "boulogne.f@gmail.com", "name-addr": "François Boulogne <boulogne.f@gmail.com>", "count": 1}
+{"name": "Ingmar Vanhassel", "address": "ingmar@exherbo.org", "name-addr": "Ingmar Vanhassel <ingmar@exherbo.org>", "count": 1}
+{"name": "Israel Herraiz", "address": "isra@herraiz.org", "name-addr": "Israel Herraiz <isra@herraiz.org>", "count": 1}
+{"name": "Jan Janak", "address": "jan@ryngle.com", "name-addr": "Jan Janak <jan@ryngle.com>", "count": 4}
+{"name": "Jjgod Jiang", "address": "gzjjgod@gmail.com", "name-addr": "Jjgod Jiang <gzjjgod@gmail.com>", "count": 2}
+{"name": "Keith Packard", "address": "keithp@keithp.com", "name-addr": "Keith Packard <keithp@keithp.com>", "count": 7}
+{"name": "Lars Kellogg-Stedman", "address": "lars@seas.harvard.edu", "name-addr": "Lars Kellogg-Stedman <lars@seas.harvard.edu>", "count": 5}
+{"name": "Mikhail Gusarov", "address": "dottedmag@dottedmag.net", "name-addr": "Mikhail Gusarov <dottedmag@dottedmag.net>", "count": 5}
+{"name": "Olivier Berger", "address": "olivier.berger@it-sudparis.eu", "name-addr": "Olivier Berger <olivier.berger@it-sudparis.eu>", "count": 1}
+{"name": "Rolland Santimano", "address": "rollandsantimano@yahoo.com", "name-addr": "Rolland Santimano <rollandsantimano@yahoo.com>", "count": 1}
+{"name": "Stewart Smith", "address": "stewart@flamingspork.com", "name-addr": "Stewart Smith <stewart@flamingspork.com>", "count": 3}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--deduplicate=no --sort=oldest-first --output=sender"
+notmuch address --deduplicate=no --sort=oldest-first --output=sender '*' >OUTPUT
+cat <<EOF >EXPECTED
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Carl Worth <cworth@cworth.org>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Carl Worth <cworth@cworth.org>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Jan Janak <jan@ryngle.com>
+Jan Janak <jan@ryngle.com>
+Jan Janak <jan@ryngle.com>
+Israel Herraiz <isra@herraiz.org>
+Adrian Perez de Castro <aperez@igalia.com>
+Aron Griffis <agriffis@n01se.net>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Stewart Smith <stewart@flamingspork.com>
+Stewart Smith <stewart@flamingspork.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Keith Packard <keithp@keithp.com>
+Stewart Smith <stewart@flamingspork.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Jan Janak <jan@ryngle.com>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Keith Packard <keithp@keithp.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Carl Worth <cworth@cworth.org>
+Chris Wilson <chris@chris-wilson.co.uk>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+François Boulogne <boulogne.f@gmail.com>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--deduplicate=no --sort=newest-first --output=sender --output=recipients"
+notmuch address --deduplicate=no --sort=newest-first --output=sender --output=recipients path:foo/new >OUTPUT
+cat <<EOF >EXPECTED
+Mikhail Gusarov <dottedmag@dottedmag.net>
+notmuch@notmuchmail.org
+Mikhail Gusarov <dottedmag@dottedmag.net>
+notmuch@notmuchmail.org
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+notmuch@notmuchmail.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--deduplicate=address --output=sender --output=recipients"
+notmuch address --deduplicate=address --output=sender --output=recipients '*' | sort >OUTPUT
+cat <<EOF >EXPECTED
+"Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+Adrian Perez de Castro <aperez@igalia.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Allan McRae <allan@archlinux.org>
+Aron Griffis <agriffis@n01se.net>
+Carl Worth <cworth@cworth.org>
+Chris Wilson <chris@chris-wilson.co.uk>
+François Boulogne <boulogne.f@gmail.com>
+Ingmar Vanhassel <ingmar@exherbo.org>
+Israel Herraiz <isra@herraiz.org>
+Jan Janak <jan@ryngle.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+Keith Packard <keithp@keithp.com>
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Mikhail Gusarov <dottedmag@dottedmag.net>
+Olivier Berger <olivier.berger@it-sudparis.eu>
+Rolland Santimano <rollandsantimano@yahoo.com>
+Stewart Smith <stewart@flamingspork.com>
+notmuch@notmuchmail.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+generate_message '[from]="Foo Bar <foo.bar@example.com>"'
+generate_message '[from]="Foo Bar <Foo.Bar@Example.Com>"'
+generate_message '[from]="Foo Bar <foo.bar@example.com>"'
+generate_message '[from]="Bar <Foo.Bar@Example.Com>"'
+generate_message '[from]="Foo <foo.bar@example.com>"'
+generate_message '[from]="<foo.bar@example.com>"'
+generate_message '[from]="foo.bar@example.com"'
+generate_message '[from]="Baz <foo.bar+baz@example.com>"'
+generate_message '[from]="Foo Bar <foo.bar+baz@example.com>"'
+generate_message '[from]="Baz <foo.bar+baz@example.com>"'
+notmuch new > /dev/null
+
+test_begin_subtest "--deduplicate=no --output=sender"
+notmuch address --deduplicate=no --output=sender from:example.com | sort >OUTPUT
+cat <<EOF >EXPECTED
+Bar <Foo.Bar@Example.Com>
+Baz <foo.bar+baz@example.com>
+Baz <foo.bar+baz@example.com>
+Foo <foo.bar@example.com>
+Foo Bar <Foo.Bar@Example.Com>
+Foo Bar <foo.bar+baz@example.com>
+Foo Bar <foo.bar@example.com>
+Foo Bar <foo.bar@example.com>
+foo.bar@example.com
+foo.bar@example.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--deduplicate=mailbox --output=sender --output=count"
+notmuch address --deduplicate=mailbox --output=sender --output=count from:example.com | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+1      Bar <Foo.Bar@Example.Com>
+1      Foo <foo.bar@example.com>
+1      Foo Bar <Foo.Bar@Example.Com>
+1      Foo Bar <foo.bar+baz@example.com>
+2      Baz <foo.bar+baz@example.com>
+2      Foo Bar <foo.bar@example.com>
+2      foo.bar@example.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--deduplicate=address --output=sender --output=count"
+notmuch address --deduplicate=address --output=sender --output=count from:example.com | sort -n >OUTPUT
+cat <<EOF >EXPECTED
+3      Baz <foo.bar+baz@example.com>
+7      Foo Bar <foo.bar@example.com>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T100-search-by-folder.sh b/test/T100-search-by-folder.sh
new file mode 100755 (executable)
index 0000000..a090f3d
--- /dev/null
@@ -0,0 +1,149 @@
+#!/usr/bin/env bash
+test_description='"notmuch search" by folder: and path: (with variations)'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[dir]=bad' '[subject]="To the bone"'
+add_message '[dir]=.' '[subject]="Top level"'
+add_message '[dir]=bad/news' '[subject]="Bears"'
+mkdir -p "${MAIL_DIR}/duplicate/bad/news"
+cp "$gen_msg_filename" "${MAIL_DIR}/duplicate/bad/news"
+
+add_message '[dir]=things' '[subject]="These are a few"'
+add_message '[dir]=things/favorite' '[subject]="Raindrops, whiskers, kettles"'
+add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"'
+
+test_begin_subtest "Single-world folder: specification (multiple results)"
+output=$(notmuch search folder:bad folder:bad/news folder:things/bad | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
+
+test_begin_subtest "Top level folder"
+output=$(notmuch search folder:'""' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Top level (inbox unread)"
+
+test_begin_subtest "Two-word path to narrow results to one"
+output=$(notmuch search folder:bad/news | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)"
+
+test_begin_subtest "Folder search with --output=files"
+output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize)
+test_expect_equal "$output" "MAIL_DIR/bad/news/msg-003
+MAIL_DIR/duplicate/bad/news/msg-003"
+
+test_begin_subtest "After removing duplicate instance of matching path"
+rm -r "${MAIL_DIR}/bad/news"
+notmuch new
+output=$(notmuch search folder:bad/news | notmuch_search_sanitize)
+test_expect_equal "$output" ""
+
+test_begin_subtest "Folder search with --output=files part #2"
+output=$(notmuch search --output=files folder:duplicate/bad/news | notmuch_search_files_sanitize)
+test_expect_equal "$output" "MAIL_DIR/duplicate/bad/news/msg-003"
+
+test_begin_subtest "After removing duplicate instance of matching path part #2"
+output=$(notmuch search folder:duplicate/bad/news | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)"
+
+test_begin_subtest "After rename, old path returns nothing"
+mv "${MAIL_DIR}/duplicate/bad/news" "${MAIL_DIR}/duplicate/bad/olds"
+notmuch new
+output=$(notmuch search folder:duplicate/bad/news | notmuch_search_sanitize)
+test_expect_equal "$output" ""
+
+test_begin_subtest "After rename, new path returns result"
+output=$(notmuch search folder:duplicate/bad/olds | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)"
+
+# folder: and path: searches with full corpus
+rm -rf $MAIL_DIR
+add_email_corpus
+
+# add some more dupes
+cp $MAIL_DIR/foo/new/03:2, $MAIL_DIR/new
+cp $MAIL_DIR/bar/baz/05:2, $MAIL_DIR/foo
+notmuch new >/dev/null
+
+test_begin_subtest "folder: search"
+output=$(notmuch search --output=files folder:foo | notmuch_search_files_sanitize | sort)
+# bar/baz/05:2, is a duplicate of foo/05:2,
+# new/03:2, is a duplicate of foo/new/03:2,
+test_expect_equal "$output" "MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/foo/05:2,
+MAIL_DIR/foo/06:2,
+MAIL_DIR/foo/cur/07:2,
+MAIL_DIR/foo/cur/08:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/foo/new/09:2,
+MAIL_DIR/foo/new/10:2,
+MAIL_DIR/new/03:2,"
+
+test_begin_subtest "top level folder: search"
+output=$(notmuch search --output=files folder:'""' | notmuch_search_files_sanitize | sort)
+# bar/18:2, is a duplicate of cur/51:2,
+# foo/new/03:2, is a duplicate of new/03:2,
+test_expect_equal "$output" "MAIL_DIR/01:2,
+MAIL_DIR/02:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/cur/29:2,
+MAIL_DIR/cur/30:2,
+MAIL_DIR/cur/31:2,
+MAIL_DIR/cur/32:2,
+MAIL_DIR/cur/33:2,
+MAIL_DIR/cur/34:2,
+MAIL_DIR/cur/35:2,
+MAIL_DIR/cur/36:2,
+MAIL_DIR/cur/37:2,
+MAIL_DIR/cur/38:2,
+MAIL_DIR/cur/39:2,
+MAIL_DIR/cur/40:2,
+MAIL_DIR/cur/41:2,
+MAIL_DIR/cur/42:2,
+MAIL_DIR/cur/43:2,
+MAIL_DIR/cur/44:2,
+MAIL_DIR/cur/45:2,
+MAIL_DIR/cur/46:2,
+MAIL_DIR/cur/47:2,
+MAIL_DIR/cur/48:2,
+MAIL_DIR/cur/49:2,
+MAIL_DIR/cur/50:2,
+MAIL_DIR/cur/51:2,
+MAIL_DIR/cur/52:2,
+MAIL_DIR/cur/53:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/new/03:2,
+MAIL_DIR/new/04:2,"
+
+test_begin_subtest "path: search"
+output=$(notmuch search --output=files path:"bar" | notmuch_search_files_sanitize | sort)
+# cur/51:2, is a duplicate of bar/18:2,
+test_expect_equal "$output" "MAIL_DIR/bar/17:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/cur/51:2,"
+
+test_begin_subtest "top level path: search"
+output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort)
+test_expect_equal "$output" "MAIL_DIR/01:2,
+MAIL_DIR/02:2,"
+
+test_begin_subtest "recursive path: search"
+output=$(notmuch search --output=files path:"bar/**" | notmuch_search_files_sanitize | sort)
+# cur/51:2, is a duplicate of bar/18:2,
+# foo/05:2, is a duplicate of bar/baz/05:2,
+test_expect_equal "$output" "MAIL_DIR/bar/17:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/bar/baz/23:2,
+MAIL_DIR/bar/baz/24:2,
+MAIL_DIR/bar/baz/cur/25:2,
+MAIL_DIR/bar/baz/cur/26:2,
+MAIL_DIR/bar/baz/new/27:2,
+MAIL_DIR/bar/baz/new/28:2,
+MAIL_DIR/bar/cur/19:2,
+MAIL_DIR/bar/cur/20:2,
+MAIL_DIR/bar/new/21:2,
+MAIL_DIR/bar/new/22:2,
+MAIL_DIR/cur/51:2,
+MAIL_DIR/foo/05:2,"
+
+test_done
diff --git a/test/T110-search-position-overlap-bug.sh b/test/T110-search-position-overlap-bug.sh
new file mode 100755 (executable)
index 0000000..f4d5ee1
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+# Test to demonstrate a position overlap bug.
+#
+# At one point, notmuch would index terms incorrectly in the case of
+# calling index_terms multiple times for a single field. The term
+# generator was being reset to position 0 each time. This means that
+# with text such as:
+#
+#      To: a@b.c, x@y.z
+#
+# one could get a bogus match by searching for:
+#
+#      To: a@y.c
+#
+# Thanks to Mark Anderson for reporting the bug, (and providing a nice,
+# minimal test case that inspired what is used here), in
+# id:3wd4o8wa7fx.fsf@testarossa.amd.com
+
+test_description='that notmuch does not overlap term positions'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[to]="a@b.c, x@y.z"'
+
+test_begin_subtest "Search for a@b.c matches"
+output=$(notmuch search a@b.c | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Test message #1 (inbox unread)"
+
+test_begin_subtest "Search for x@y.z matches"
+output=$(notmuch search x@y.z | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Test message #1 (inbox unread)"
+
+test_begin_subtest "Search for a@y.c must not match"
+output=$(notmuch search a@y.c | notmuch_search_sanitize)
+test_expect_equal "$output" ""
+
+test_done
diff --git a/test/T120-search-insufficient-from-quoting.sh b/test/T120-search-insufficient-from-quoting.sh
new file mode 100755 (executable)
index 0000000..509fec8
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+test_description='messages with unquoted . in name'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message \
+  '[from]="Some.Name for Someone <bugs@quoting.com>"' \
+  '[subject]="This message needs more quoting on the From line"'
+
+add_message \
+  '[from]="\"Some.Name for Someone\" <bugs@quoting.com>"' \
+  '[subject]="This message has necessary quoting in place"'
+
+add_message \
+  '[from]="No.match Here <filler@mail.com>"' \
+  '[subject]="This message needs more quoting on the From line"'
+
+add_message \
+  '[from]="\"No.match Here\" <filler@mail.com>"' \
+  '[subject]="This message has necessary quoting in place"'
+
+
+test_begin_subtest "Search by first name"
+output=$(notmuch search from:Some.Name | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message needs more quoting on the From line (inbox unread)
+thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message has necessary quoting in place (inbox unread)"
+
+test_begin_subtest "Search by last name:"
+output=$(notmuch search from:Someone | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message needs more quoting on the From line (inbox unread)
+thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message has necessary quoting in place (inbox unread)"
+
+test_begin_subtest "Search by address:"
+output=$(notmuch search from:bugs@quoting.com | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message needs more quoting on the From line (inbox unread)
+thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message has necessary quoting in place (inbox unread)"
+
+test_begin_subtest "Search for all messages:"
+output=$(notmuch search '*' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message needs more quoting on the From line (inbox unread)
+thread:XXX   2001-01-05 [1/1] Some.Name for Someone; This message has necessary quoting in place (inbox unread)
+thread:XXX   2001-01-05 [1/1] No.match Here; This message needs more quoting on the From line (inbox unread)
+thread:XXX   2001-01-05 [1/1] No.match Here; This message has necessary quoting in place (inbox unread)"
+
+test_done
diff --git a/test/T130-search-limiting.sh b/test/T130-search-limiting.sh
new file mode 100755 (executable)
index 0000000..8a30e7a
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+test_description='"notmuch search" --offset and --limit parameters'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+for outp in messages threads; do
+    test_begin_subtest "${outp}: limit does the right thing"
+    notmuch search --output=${outp} "*" | head -n 20 >expected
+    notmuch search --output=${outp} --limit=20 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: concatenation of limited searches"
+    notmuch search --output=${outp} "*" | head -n 20 >expected
+    notmuch search --output=${outp} --limit=10 "*" >output
+    notmuch search --output=${outp} --limit=10 --offset=10 "*" >>output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: limit larger than result set"
+    N=`notmuch count --output=${outp} "*"`
+    notmuch search --output=${outp} "*" >expected
+    notmuch search --output=${outp} --limit=$((1 + ${N})) "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: limit = 0"
+    test_expect_equal "`notmuch search --output=${outp} --limit=0 "*"`" ""
+
+    test_begin_subtest "${outp}: offset does the right thing"
+    # note: tail -n +N is 1-based
+    notmuch search --output=${outp} "*" | tail -n +21 >expected
+    notmuch search --output=${outp} --offset=20 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: offset = 0"
+    notmuch search --output=${outp} "*" >expected
+    notmuch search --output=${outp} --offset=0 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset"
+    notmuch search --output=${outp} "*" | tail -n 20 >expected
+    notmuch search --output=${outp} --offset=-20 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset"
+    notmuch search --output=${outp} "*" | tail -n 1 >expected
+    notmuch search --output=${outp} --offset=-1 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset combined with limit"
+    notmuch search --output=${outp} "*" | tail -n 20 | head -n 10 >expected
+    notmuch search --output=${outp} --offset=-20 --limit=10 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset combined with equal limit"
+    notmuch search --output=${outp} "*" | tail -n 20 >expected
+    notmuch search --output=${outp} --offset=-20 --limit=20 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset combined with large limit"
+    notmuch search --output=${outp} "*" | tail -n 20 >expected
+    notmuch search --output=${outp} --offset=-20 --limit=50 "*" >output
+    test_expect_equal_file expected output
+
+    test_begin_subtest "${outp}: negative offset larger then results"
+    N=`notmuch count --output=${outp} "*"`
+    notmuch search --output=${outp} "*" >expected
+    notmuch search --output=${outp} --offset=-$((1 + ${N})) "*" >output
+    test_expect_equal_file expected output
+done
+
+test_done
diff --git a/test/T140-excludes.sh b/test/T140-excludes.sh
new file mode 100755 (executable)
index 0000000..0cf6997
--- /dev/null
@@ -0,0 +1,445 @@
+#!/usr/bin/env bash
+test_description='"notmuch search, count and show" with excludes in several variations'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# Generates a thread consisting of a top level message and 'length'
+# replies. The subject of the top message 'subject: top message"
+# and the subject of the nth reply in the thread is "subject: reply n"
+generate_thread ()
+{
+    local subject="$1"
+    local length="$2"
+    generate_message '[subject]="'"${subject}: top message"'"' '[body]="'"body of top message"'"'
+    parent_id=$gen_msg_id
+    gen_thread_msg_id[0]=$gen_msg_id
+    for i in `seq 1 $length`
+    do
+       generate_message '[subject]="'"${subject}: reply $i"'"' \
+                        "[in-reply-to]=\<$parent_id\>" \
+                        '[body]="'"body of reply $i"'"'
+       gen_thread_msg_id[$i]=$gen_msg_id
+       parent_id=$gen_msg_id
+    done
+    notmuch new > /dev/null
+    # We cannot retrieve the thread_id until after we have run notmuch new.
+    gen_thread_id=`notmuch search --output=threads id:${gen_thread_msg_id[0]}`
+}
+
+#############################################
+# These are the original search exclude tests.
+
+test_begin_subtest "Search, exclude \"deleted\" messages from search"
+notmuch config set search.exclude_tags deleted
+generate_message '[subject]="Not deleted"'
+not_deleted_id=$gen_msg_id
+generate_message '[subject]="Deleted"'
+notmuch new > /dev/null
+notmuch tag +deleted id:$gen_msg_id
+deleted_id=$gen_msg_id
+output=$(notmuch search subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from message search"
+output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "id:$not_deleted_id"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from message search --exclude=false"
+output=$(notmuch search --exclude=false --output=messages subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "id:$not_deleted_id
+id:$deleted_id"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from message search (non-existent exclude-tag)"
+notmuch config set search.exclude_tags deleted non_existent_tag
+output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "id:$not_deleted_id"
+notmuch config set search.exclude_tags deleted
+
+test_begin_subtest "Search, exclude \"deleted\" messages from search, overridden"
+output=$(notmuch search subject:deleted and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Deleted (deleted inbox unread)"
+
+test_begin_subtest "Search, exclude \"deleted\" messages from threads"
+add_message '[subject]="Not deleted reply"' '[in-reply-to]="<$gen_msg_id>"'
+output=$(notmuch search subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX   2001-01-05 [1/2] Notmuch Test Suite; Not deleted reply (deleted inbox unread)"
+
+test_begin_subtest "Search, don't exclude \"deleted\" messages when --exclude=flag specified"
+output=$(notmuch search --exclude=flag subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX   2001-01-05 [1/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
+
+test_begin_subtest "Search, don't exclude \"deleted\" messages from search if not configured"
+notmuch config set search.exclude_tags
+output=$(notmuch search subject:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+thread:XXX   2001-01-05 [2/2] Notmuch Test Suite; Deleted (deleted inbox unread)"
+
+
+########################################################
+# We construct some threads for the tests. We use the tag "test" to
+# indicate which messages we will search for.
+
+# A thread of deleted messages; test matches one of them.
+generate_thread "All messages excluded: single match" 5
+notmuch tag +deleted $gen_thread_id
+notmuch tag +test id:${gen_thread_msg_id[2]}
+
+# A thread of deleted messages; test matches two of them.
+generate_thread "All messages excluded: double match" 5
+notmuch tag +deleted $gen_thread_id
+notmuch tag +test id:${gen_thread_msg_id[2]}
+notmuch tag +test id:${gen_thread_msg_id[4]}
+
+# A thread some messages deleted; test only matches a deleted message.
+generate_thread "Some messages excluded: single excluded match" 5
+notmuch tag +deleted +test id:${gen_thread_msg_id[3]}
+
+# A thread some messages deleted; test only matches a non-deleted message.
+generate_thread "Some messages excluded: single non-excluded match" 5
+notmuch tag +deleted id:${gen_thread_msg_id[2]}
+notmuch tag +test id:${gen_thread_msg_id[4]}
+
+# A thread no messages deleted; test matches a message.
+generate_thread "No messages excluded: single match" 5
+notmuch tag +test id:${gen_thread_msg_id[3]}
+
+# Temporarily remove excludes to get list of matching messages
+notmuch config set search.exclude_tags
+matching_message_ids=( `notmuch search --output=messages tag:test` )
+notmuch config set search.exclude_tags deleted
+
+#########################################
+# Notmuch search tests
+
+test_begin_subtest "Search, default exclusion (thread summary)"
+output=$(notmuch search tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
+
+test_begin_subtest "Search, default exclusion (messages)"
+output=$(notmuch search --output=messages tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[4]}
+${matching_message_ids[5]}"
+
+test_begin_subtest "Search, exclude=true (thread summary)"
+output=$(notmuch search --exclude=true tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
+
+test_begin_subtest "Search, exclude=true (messages)"
+output=$(notmuch search --exclude=true --output=messages tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[4]}
+${matching_message_ids[5]}"
+
+test_begin_subtest "Search, exclude=false (thread summary)"
+output=$(notmuch search --exclude=false tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
+
+test_begin_subtest "Search, exclude=false (messages)"
+output=$(notmuch search --exclude=false --output=messages tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}
+${matching_message_ids[4]}
+${matching_message_ids[5]}"
+
+test_begin_subtest "Search, exclude=flag (thread summary)"
+output=$(notmuch search --exclude=flag tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [0/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
+
+test_begin_subtest "Search, exclude=flag (messages)"
+output=$(notmuch search --exclude=flag --output=messages tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}
+${matching_message_ids[4]}
+${matching_message_ids[5]}"
+
+test_begin_subtest "Search, exclude=all (thread summary)"
+output=$(notmuch search --exclude=all tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/5] Notmuch Test Suite; Some messages excluded: single non-excluded match: reply 4 (inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; No messages excluded: single match: reply 3 (inbox test unread)"
+
+test_begin_subtest "Search, exclude=all (messages)"
+output=$(notmuch search --exclude=all --output=messages tag:test | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[4]}
+${matching_message_ids[5]}"
+
+test_begin_subtest "Search, default exclusion: tag in query (thread summary)"
+output=$(notmuch search tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)"
+
+test_begin_subtest "Search, default exclusion: tag in query (messages)"
+output=$(notmuch search --output=messages tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}"
+
+test_begin_subtest "Search, exclude=true: tag in query (thread summary)"
+output=$(notmuch search --exclude=true tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)"
+
+test_begin_subtest "Search, exclude=true: tag in query (messages)"
+output=$(notmuch search --exclude=true --output=messages tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}"
+
+test_begin_subtest "Search, exclude=false: tag in query (thread summary)"
+output=$(notmuch search --exclude=false tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)"
+
+test_begin_subtest "Search, exclude=false: tag in query (messages)"
+output=$(notmuch search --exclude=false --output=messages tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}"
+
+test_begin_subtest "Search, exclude=flag: tag in query (thread summary)"
+output=$(notmuch search --exclude=flag tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)"
+
+test_begin_subtest "Search, exclude=flag: tag in query (messages)"
+output=$(notmuch search --exclude=flag --output=messages tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}"
+
+test_begin_subtest "Search, exclude=all: tag in query (thread summary)"
+output=$(notmuch search --exclude=all tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; All messages excluded: single match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [2/6] Notmuch Test Suite; All messages excluded: double match: reply 2 (deleted inbox test unread)
+thread:XXX   2001-01-05 [1/6] Notmuch Test Suite; Some messages excluded: single excluded match: reply 3 (deleted inbox test unread)"
+
+test_begin_subtest "Search, exclude=all: tag in query (messages)"
+output=$(notmuch search --exclude=all --output=messages tag:test and tag:deleted | notmuch_search_sanitize)
+test_expect_equal "$output" "${matching_message_ids[0]}
+${matching_message_ids[1]}
+${matching_message_ids[2]}
+${matching_message_ids[3]}"
+
+#########################################################
+# Notmuch count tests
+
+test_begin_subtest "Count, default exclusion (messages)"
+output=$(notmuch count tag:test)
+test_expect_equal "$output" "2"
+
+test_begin_subtest "Count, default exclusion (threads)"
+output=$(notmuch count --output=threads tag:test)
+test_expect_equal "$output" "2"
+
+test_begin_subtest "Count, exclude=true (messages)"
+output=$(notmuch count --exclude=true tag:test)
+test_expect_equal "$output" "2"
+
+test_begin_subtest "Count, exclude=true (threads)"
+output=$(notmuch count --output=threads --exclude=true tag:test)
+test_expect_equal "$output" "2"
+
+test_begin_subtest "Count, exclude=false (messages)"
+output=$(notmuch count --exclude=false tag:test)
+test_expect_equal "$output" "6"
+
+test_begin_subtest "Count, exclude=false (threads)"
+output=$(notmuch count --output=threads --exclude=false tag:test)
+test_expect_equal "$output" "5"
+
+test_begin_subtest "Count, default exclusion: tag in query (messages)"
+output=$(notmuch count tag:test and tag:deleted)
+test_expect_equal "$output" "4"
+
+test_begin_subtest "Count, default exclusion: tag in query (threads)"
+output=$(notmuch count --output=threads tag:test and tag:deleted)
+test_expect_equal "$output" "3"
+
+test_begin_subtest "Count, exclude=true: tag in query (messages)"
+output=$(notmuch count --exclude=true tag:test and tag:deleted)
+test_expect_equal "$output" "4"
+
+test_begin_subtest "Count, exclude=true: tag in query (threads)"
+output=$(notmuch count --output=threads --exclude=true tag:test and tag:deleted)
+test_expect_equal "$output" "3"
+
+test_begin_subtest "Count, exclude=false: tag in query (messages)"
+output=$(notmuch count --exclude=false tag:test and tag:deleted)
+test_expect_equal "$output" "4"
+
+test_begin_subtest "Count, exclude=false: tag in query (threads)"
+output=$(notmuch count --output=threads --exclude=false tag:test and tag:deleted)
+test_expect_equal "$output" "3"
+
+#############################################################
+# Show tests
+
+test_begin_subtest "Show, default exclusion"
+output=$(notmuch show tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3"
+
+test_begin_subtest "Show, default exclusion (entire-thread)"
+output=$(notmuch show --entire-thread tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:1 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 2
+\fmessage{ id:XXXXX depth:3 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 3
+\fmessage{ id:XXXXX depth:4 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 2
+\fmessage{ id:XXXXX depth:3 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3
+\fmessage{ id:XXXXX depth:4 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 5"
+
+test_begin_subtest "Show, exclude=true"
+output=$(notmuch show --exclude=true tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3"
+
+test_begin_subtest "Show, exclude=true (entire-thread)"
+output=$(notmuch show --entire-thread --exclude=true tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:1 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 2
+\fmessage{ id:XXXXX depth:3 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 3
+\fmessage{ id:XXXXX depth:4 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 2
+\fmessage{ id:XXXXX depth:3 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3
+\fmessage{ id:XXXXX depth:4 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 5"
+
+test_begin_subtest "Show, exclude=false"
+output=$(notmuch show --exclude=false tag:test | notmuch_show_sanitize_all  | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 2
+\fmessage{ id:XXXXX depth:0 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 2
+\fmessage{ id:XXXXX depth:1 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 4
+\fmessage{ id:XXXXX depth:0 match:1 excluded:1 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 3
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3"
+
+test_begin_subtest "Show, exclude=false (entire-thread)"
+output=$(notmuch show --entire-thread --exclude=false tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 1
+\fmessage{ id:XXXXX depth:2 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 2
+\fmessage{ id:XXXXX depth:3 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 3
+\fmessage{ id:XXXXX depth:4 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: single match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 1
+\fmessage{ id:XXXXX depth:2 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 2
+\fmessage{ id:XXXXX depth:3 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 3
+\fmessage{ id:XXXXX depth:4 match:1 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:1 filename:XXXXX
+Subject: All messages excluded: double match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single excluded match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 2
+\fmessage{ id:XXXXX depth:3 match:1 excluded:1 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 3
+\fmessage{ id:XXXXX depth:4 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single excluded match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:1 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 2
+\fmessage{ id:XXXXX depth:3 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 3
+\fmessage{ id:XXXXX depth:4 match:1 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: Some messages excluded: single non-excluded match: reply 5
+\fmessage{ id:XXXXX depth:0 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: top message
+\fmessage{ id:XXXXX depth:1 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 1
+\fmessage{ id:XXXXX depth:2 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 2
+\fmessage{ id:XXXXX depth:3 match:1 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 3
+\fmessage{ id:XXXXX depth:4 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 4
+\fmessage{ id:XXXXX depth:5 match:0 excluded:0 filename:XXXXX
+Subject: No messages excluded: single match: reply 5"
+
+
+test_done
diff --git a/test/T150-tagging.sh b/test/T150-tagging.sh
new file mode 100755 (executable)
index 0000000..208b4b9
--- /dev/null
@@ -0,0 +1,313 @@
+#!/usr/bin/env bash
+test_description='"notmuch tag"'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[subject]=One'
+add_message '[subject]=Two'
+
+test_begin_subtest "Adding tags"
+notmuch tag +tag1 +tag2 +tag3 \*
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 tag2 tag3 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag2 tag3 unread)"
+
+test_begin_subtest "Removing tags"
+notmuch tag -tag1 -tag2 \*
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag3 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag3 unread)"
+
+test_begin_subtest "No tag operations"
+test_expect_code 1 'notmuch tag One'
+
+test_begin_subtest "No query"
+test_expect_code 1 'notmuch tag +tag2'
+
+test_begin_subtest "Redundant tagging"
+notmuch tag +tag1 -tag3 One
+notmuch tag +tag1 -tag3 \*
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 unread)"
+
+test_begin_subtest "Remove all"
+notmuch tag --remove-all One
+notmuch tag --remove-all +tag5 +tag6 +unread Two
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One ()
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (tag5 tag6 unread)"
+
+test_begin_subtest "Remove all with batch"
+notmuch tag +tag1 One
+notmuch tag --remove-all --batch <<EOF
+-- One
++tag3 +tag4 +inbox -- Two
+EOF
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One ()
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag3 tag4)"
+
+test_begin_subtest "Remove all with a no-op"
+notmuch tag +inbox +tag1 +unread One
+notmuch tag --remove-all +foo +inbox +tag1 -foo +unread Two
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 unread)"
+
+test_begin_subtest "Special characters in tags"
+notmuch tag +':" ' \*
+notmuch tag -':" ' Two
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (:\"  inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 unread)"
+
+test_begin_subtest "Tagging order"
+notmuch tag +tag4 -tag4 One
+notmuch tag -tag4 +tag4 Two
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (:\"  inbox tag1 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag4 unread)"
+
+test_begin_subtest "--batch"
+notmuch tag --batch <<EOF
+# %20 is a space in tag
+-:"%20 -tag1 +tag5 +tag6 -- One
++tag1 -tag1 -tag4 +tag4 -- Two
+-tag6 One
++tag5 Two
+EOF
+output=$(notmuch search \* | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (inbox tag5 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag4 tag5 unread)"
+
+# generate a common input file for the next several tests.
+cat > batch.in  <<EOF
+# %40 is an @ in tag
++%40 -tag5 +tag6 -- One
++tag1 -tag1 -tag4 +tag4 -- Two
+-tag5 +tag6 Two
+EOF
+
+cat > batch.expected <<EOF
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (@ inbox tag6 unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag4 tag6 unread)
+EOF
+
+test_begin_subtest "--input"
+notmuch dump --format=batch-tag > backup.tags
+notmuch tag --input=batch.in
+notmuch search \* | notmuch_search_sanitize > OUTPUT
+notmuch restore --format=batch-tag < backup.tags
+test_expect_equal_file batch.expected OUTPUT
+
+test_begin_subtest "--batch --input"
+notmuch dump --format=batch-tag > backup.tags
+notmuch tag --batch --input=batch.in
+notmuch search \* | notmuch_search_sanitize > OUTPUT
+notmuch restore --format=batch-tag < backup.tags
+test_expect_equal_file batch.expected OUTPUT
+
+test_begin_subtest "--batch --input --remove-all"
+notmuch dump --format=batch-tag > backup.tags
+notmuch tag +foo +bar -- One
+notmuch tag +tag7 -- Two
+notmuch tag --batch --input=batch.in --remove-all
+notmuch search \* | notmuch_search_sanitize > OUTPUT
+notmuch restore --format=batch-tag < backup.tags
+cat > batch_removeall.expected <<EOF
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; One (@ tag6)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Two (tag6)
+EOF
+test_expect_equal_file batch_removeall.expected OUTPUT
+rm batch_removeall.expected
+
+test_begin_subtest "--batch, dependence on previous line"
+notmuch dump --format=batch-tag > backup.tags
+notmuch tag --batch<<EOF
++trigger -- One
++second_tag -- tag:trigger
+EOF
+NOTMUCH_DUMP_TAGS tag:second_tag > OUTPUT
+notmuch restore --format=batch-tag < backup.tags
+cat <<EOF >EXPECTED
++inbox +second_tag +tag5 +trigger +unread -- id:msg-001@notmuch-test-suite
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--batch, blank lines and comments"
+notmuch dump | sort > EXPECTED
+notmuch tag --batch <<EOF
+# this line is a comment; the next has only white space
+        
+
+# the previous line is empty
+EOF
+notmuch dump | sort > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest '--batch: checking error messages'
+notmuch dump --format=batch-tag > BACKUP
+notmuch tag --batch <<EOF 2>OUTPUT
+# the next line has a space
+# this line has no tag operations, but this is permitted in batch format.
+a
++0
++a +b
+# trailing whitespace
++a +b 
++c +d --
+# this is a harmless comment, do not yell about it.
+
+# the previous line was blank; also no yelling please
++%zz -- id:whatever
+# the next non-comment line should report an an empty tag error for
+# batch tagging, but not for restore
++ +e -- id:foo
++- -- id:foo
+EOF
+
+cat <<EOF > EXPECTED
+Warning: no query string [+0]
+Warning: no query string [+a +b]
+Warning: missing query string [+a +b ]
+Warning: no query string after -- [+c +d --]
+Warning: hex decoding of tag %zz failed [+%zz -- id:whatever]
+Warning: empty tag forbidden [+ +e -- id:foo]
+Warning: tag starting with '-' forbidden [+- -- id:foo]
+EOF
+
+notmuch restore --format=batch-tag < BACKUP
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest '--batch: tags with quotes'
+notmuch dump --format=batch-tag > BACKUP
+
+notmuch tag --batch <<EOF
++%22%27%22%27%22%22%27%27 -- One
+-%22%27%22%27%22%22%27%27 -- One
++%22%27%22%22%22%27 -- One
++%22%27%22%27%22%22%27%27 -- Two
+EOF
+
+cat <<EOF > EXPECTED
++%22%27%22%22%22%27 +inbox +tag5 +unread -- id:msg-001@notmuch-test-suite
++%22%27%22%27%22%22%27%27 +inbox +tag4 +tag5 +unread -- id:msg-002@notmuch-test-suite
+EOF
+
+NOTMUCH_DUMP_TAGS > OUTPUT
+notmuch restore --format=batch-tag < BACKUP
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest '--batch: tags with punctuation and space'
+notmuch dump --format=batch-tag > BACKUP
+
+notmuch tag --batch <<EOF
++%21@%23%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e -- One
+-%21@%23%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e -- One
++%21@%23%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%20%60%7e -- Two
+-%21@%23%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%20%60%7e -- Two
++%21@%23%20%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e -- One
++%21@%23%20%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e -- Two
+EOF
+
+cat <<EOF > EXPECTED
++%21@%23%20%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e +inbox +tag4 +tag5 +unread -- id:msg-002@notmuch-test-suite
++%21@%23%20%24%25%5e%26%2a%29-_=+%5b%7b%5c%20%7c%3b%3a%27%20%22,.%3c%60%7e +inbox +tag5 +unread -- id:msg-001@notmuch-test-suite
+EOF
+
+NOTMUCH_DUMP_TAGS > OUTPUT
+notmuch restore --format=batch-tag < BACKUP
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest '--batch: unicode tags'
+notmuch dump --format=batch-tag > BACKUP
+
+notmuch tag --batch <<EOF
++%2a@%7d%cf%b5%f4%85%80%adO3%da%a7 -- One
++=%e0%ac%95%c8%b3+%ef%aa%95%c8%a64w%c7%9d%c9%a2%cf%b3%d6%82%24B%c4%a9%c5%a1UX%ee%99%b0%27E7%ca%a4%d0%8b%5d -- One
++A%e1%a0%bc%de%8b%d5%b2V%d9%9b%f3%b5%a2%a3M%d8%a1u@%f0%a0%ac%948%7e%f0%ab%86%af%27 -- One
++R -- One
++%da%88=f%cc%b9I%ce%af%7b%c9%97%e3%b9%8bH%cb%92X%d2%8c6 -- One
++%dc%9crh%d2%86B%e5%97%a2%22t%ed%99%82d -- One
++L%df%85%ef%a1%a5m@%d3%96%c2%ab%d4%9f%ca%b8%f3%b3%a2%bf%c7%b1_u%d7%b4%c7%b1 -- One
++P%c4%98%2f -- One
++%7e%d1%8b%25%ec%a0%ae%d1%a0M%3b%e3%b6%b7%e9%a4%87%3c%db%9a%cc%a8%e1%96%9d -- One
++%c4%bf7%c7%ab9H%c4%99k%ea%91%bd%c3%8ck%e2%b3%8dk%c5%952V%e4%99%b2%d9%b3%e4%8b%bda%5b%24%c7%9b -- One
++%2a@%7d%cf%b5%f4%85%80%adO3%da%a7  +=%e0%ac%95%c8%b3+%ef%aa%95%c8%a64w%c7%9d%c9%a2%cf%b3%d6%82%24B%c4%a9%c5%a1UX%ee%99%b0%27E7%ca%a4%d0%8b%5d  +A%e1%a0%bc%de%8b%d5%b2V%d9%9b%f3%b5%a2%a3M%d8%a1u@%f0%a0%ac%948%7e%f0%ab%86%af%27  +R  +%da%88=f%cc%b9I%ce%af%7b%c9%97%e3%b9%8bH%cb%92X%d2%8c6  +%dc%9crh%d2%86B%e5%97%a2%22t%ed%99%82d  +L%df%85%ef%a1%a5m@%d3%96%c2%ab%d4%9f%ca%b8%f3%b3%a2%bf%c7%b1_u%d7%b4%c7%b1  +P%c4%98%2f  +%7e%d1%8b%25%ec%a0%ae%d1%a0M%3b%e3%b6%b7%e9%a4%87%3c%db%9a%cc%a8%e1%96%9d  +%c4%bf7%c7%ab9H%c4%99k%ea%91%bd%c3%8ck%e2%b3%8dk%c5%952V%e4%99%b2%d9%b3%e4%8b%bda%5b%24%c7%9b -- Two
+EOF
+
+cat <<EOF > EXPECTED
++%2a@%7d%cf%b5%f4%85%80%adO3%da%a7 +=%e0%ac%95%c8%b3+%ef%aa%95%c8%a64w%c7%9d%c9%a2%cf%b3%d6%82%24B%c4%a9%c5%a1UX%ee%99%b0%27E7%ca%a4%d0%8b%5d +A%e1%a0%bc%de%8b%d5%b2V%d9%9b%f3%b5%a2%a3M%d8%a1u@%f0%a0%ac%948%7e%f0%ab%86%af%27 +L%df%85%ef%a1%a5m@%d3%96%c2%ab%d4%9f%ca%b8%f3%b3%a2%bf%c7%b1_u%d7%b4%c7%b1 +P%c4%98%2f +R +inbox +tag4 +tag5 +unread +%7e%d1%8b%25%ec%a0%ae%d1%a0M%3b%e3%b6%b7%e9%a4%87%3c%db%9a%cc%a8%e1%96%9d +%c4%bf7%c7%ab9H%c4%99k%ea%91%bd%c3%8ck%e2%b3%8dk%c5%952V%e4%99%b2%d9%b3%e4%8b%bda%5b%24%c7%9b +%da%88=f%cc%b9I%ce%af%7b%c9%97%e3%b9%8bH%cb%92X%d2%8c6 +%dc%9crh%d2%86B%e5%97%a2%22t%ed%99%82d -- id:msg-002@notmuch-test-suite
++%2a@%7d%cf%b5%f4%85%80%adO3%da%a7 +=%e0%ac%95%c8%b3+%ef%aa%95%c8%a64w%c7%9d%c9%a2%cf%b3%d6%82%24B%c4%a9%c5%a1UX%ee%99%b0%27E7%ca%a4%d0%8b%5d +A%e1%a0%bc%de%8b%d5%b2V%d9%9b%f3%b5%a2%a3M%d8%a1u@%f0%a0%ac%948%7e%f0%ab%86%af%27 +L%df%85%ef%a1%a5m@%d3%96%c2%ab%d4%9f%ca%b8%f3%b3%a2%bf%c7%b1_u%d7%b4%c7%b1 +P%c4%98%2f +R +inbox +tag5 +unread +%7e%d1%8b%25%ec%a0%ae%d1%a0M%3b%e3%b6%b7%e9%a4%87%3c%db%9a%cc%a8%e1%96%9d +%c4%bf7%c7%ab9H%c4%99k%ea%91%bd%c3%8ck%e2%b3%8dk%c5%952V%e4%99%b2%d9%b3%e4%8b%bda%5b%24%c7%9b +%da%88=f%cc%b9I%ce%af%7b%c9%97%e3%b9%8bH%cb%92X%d2%8c6 +%dc%9crh%d2%86B%e5%97%a2%22t%ed%99%82d -- id:msg-001@notmuch-test-suite
+EOF
+
+NOTMUCH_DUMP_TAGS > OUTPUT
+notmuch restore --format=batch-tag < BACKUP
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--batch: only space and % needs to be encoded."
+notmuch dump --format=batch-tag > BACKUP
+
+notmuch tag --batch <<EOF
++winner *
++foo::bar%25 -- (One and Two) or (One and tag:winner)
++found::it -- tag:foo::bar%
+# ignore this line and the next
+
++space%20in%20tags -- Two
+# add tag '(tags)', among other stunts.
++crazy{ +(tags) +&are +#possible\ -- tag:"space in tags"
++match*crazy -- tag:crazy{
++some_tag -- id:"this is ""nauty)"""
+EOF
+
+cat <<EOF > EXPECTED
++%23possible%5c +%26are +%28tags%29 +crazy%7b +inbox +match%2acrazy +space%20in%20tags +tag4 +tag5 +unread +winner -- id:msg-002@notmuch-test-suite
++foo%3a%3abar%25 +found%3a%3ait +inbox +tag5 +unread +winner -- id:msg-001@notmuch-test-suite
+EOF
+
+NOTMUCH_DUMP_TAGS > OUTPUT
+notmuch restore --format=batch-tag < BACKUP
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest '--batch: unicode message-ids'
+
+${TEST_DIRECTORY}/random-corpus --config-path=${NOTMUCH_CONFIG} \
+     --num-messages=100
+
+notmuch dump --format=batch-tag | sed 's/^.* -- /+common_tag -- /' | \
+    sort > EXPECTED
+
+notmuch dump --format=batch-tag | sed 's/^.* -- /  -- /' > INTERMEDIATE_STEP
+notmuch restore --format=batch-tag < INTERMEDIATE_STEP
+
+notmuch tag --batch < EXPECTED
+
+notmuch dump --format=batch-tag| \
+    sort > OUTPUT
+
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Empty tag names"
+test_expect_code 1 'notmuch tag + One'
+
+test_begin_subtest "Tag name beginning with -"
+test_expect_code 1 'notmuch tag +- One'
+
+test_begin_subtest "Xapian exception: read only files"
+chmod u-w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
+chmod u+w  ${MAIL_DIR}/.notmuch/xapian/*.${db_ending}
+test_expect_equal "$output" "A Xapian exception occurred opening database"
+
+test_done
diff --git a/test/T160-json.sh b/test/T160-json.sh
new file mode 100755 (executable)
index 0000000..91b98e5
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+test_description="--format=json output"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Show message: json"
+add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"json-show-message\""
+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\"}]}, []]]]"
+
+# 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_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_begin_subtest "Search message: json"
+add_message "[subject]=\"json-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"json-search-message\""
+output=$(notmuch search --format=json "json-search-message" | notmuch_search_sanitize)
+test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
+ \"timestamp\": 946728000,
+ \"date_relative\": \"2000-01-01\",
+ \"matched\": 1,
+ \"total\": 1,
+ \"authors\": \"Notmuch Test Suite\",
+ \"subject\": \"json-search-subject\",
+ \"query\": [\"id:$gen_msg_id\", null],
+ \"tags\": [\"inbox\",
+ \"unread\"]}]"
+
+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_begin_subtest "Show message: json, inline attachment filename"
+subject='json-show-inline-attachment-filename'
+id="json-show-inline-attachment-filename@notmuchmail.org"
+emacs_fcc_message \
+    "$subject" \
+    'This is a test message with inline attachment with a filename' \
+    "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
+     (message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")"
+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 $NOTMUCH_SRCDIR/test/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\", \"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\""
+output=$(notmuch search --format=json "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
+ \"timestamp\": 946728000,
+ \"date_relative\": \"2000-01-01\",
+ \"matched\": 1,
+ \"total\": 1,
+ \"authors\": \"Notmuch Test Suite\",
+ \"subject\": \"json-search-utf8-body-sübjéct\",
+ \"query\": [\"id:$gen_msg_id\", null],
+ \"tags\": [\"inbox\",
+ \"unread\"]}]"
+
+test_begin_subtest "Format version: too low"
+test_expect_code 20 "notmuch search --format-version=0 \\*"
+
+test_begin_subtest "Format version: too high"
+test_expect_code 21 "notmuch search --format-version=999 \\*"
+
+test_begin_subtest "Show message: multiple filenames"
+add_message '[id]=message-id@example.com [filename]=copy1 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
+add_message '[id]=message-id@example.com [filename]=copy2 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
+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
diff --git a/test/T170-sexp.sh b/test/T170-sexp.sh
new file mode 100755 (executable)
index 0000000..c3dcf52
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+test_description="--format=sexp output"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Show message: sexp"
+add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
+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\"))) ())))"
+
+# 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_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_begin_subtest "Search message: sexp"
+add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
+output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
+
+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_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\""
+output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000004\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
+
+test_begin_subtest "Show message: sexp, inline attachment filename"
+subject='sexp-show-inline-attachment-filename'
+id="sexp-show-inline-attachment-filename@notmuchmail.org"
+emacs_fcc_message \
+    "$subject" \
+    'This is a test message with inline attachment with a filename' \
+    "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
+     (message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")"
+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 $NOTMUCH_SRCDIR/test/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\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length))))) ())))"
+
+test_done
diff --git a/test/T180-text.sh b/test/T180-text.sh
new file mode 100755 (executable)
index 0000000..ad2cb1f
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+test_description="--format=text output"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Show message: text"
+add_message "[subject]=\"text-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"text-show-message\""
+output=$(notmuch show --format=text "text-show-message" | notmuch_show_sanitize_all)
+test_expect_equal "$output" "\
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2000-01-01) (inbox unread)
+Subject: text-show-subject
+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
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+text-show-message
+\fpart}
+\fbody}
+\fmessage}"
+
+test_begin_subtest "Search message: text"
+add_message "[subject]=\"text-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"text-search-message\""
+output=$(notmuch search --format=text "text-search-message" | notmuch_search_sanitize)
+test_expect_equal "$output" \
+"thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; text-search-subject (inbox unread)"
+
+test_begin_subtest "Show message: text, utf-8"
+add_message "[subject]=\"text-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"tëxt-show-méssage\""
+output=$(notmuch show --format=text "tëxt-show-méssage" | notmuch_show_sanitize_all)
+test_expect_equal "$output" "\
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2000-01-01) (inbox unread)
+Subject: text-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
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+tëxt-show-méssage
+\fpart}
+\fbody}
+\fmessage}"
+
+test_begin_subtest "Search message: text, utf-8"
+add_message "[subject]=\"text-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"tëxt-search-méssage\""
+output=$(notmuch search --format=text "tëxt-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" \
+"thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; text-search-utf8-body-sübjéct (inbox unread)"
+
+add_email_corpus
+
+test_begin_subtest "Search message tags: text0"
+cat <<EOF > EXPECTED
+attachment inbox signed unread
+EOF
+notmuch search --format=text0 --output=tags '*' | xargs -0 | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+# Use tr(1) to convert --output=text0 to --output=text for
+# comparison. Also translate newlines to spaces to fail with more
+# noise if they are present as delimiters instead of null
+# characters. This assumes there are no newlines in the data.
+test_begin_subtest "Compare text vs. text0 for threads"
+notmuch search --format=text --output=threads '*' | notmuch_search_sanitize > EXPECTED
+notmuch search --format=text0 --output=threads '*' | tr "\n\0" " \n" | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Compare text vs. text0 for messages"
+notmuch search --format=text --output=messages '*' | notmuch_search_sanitize > EXPECTED
+notmuch search --format=text0 --output=messages '*' | tr "\n\0" " \n" | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Compare text vs. text0 for files"
+notmuch search --format=text --output=files '*' | notmuch_search_sanitize > EXPECTED
+notmuch search --format=text0 --output=files '*' | tr "\n\0" " \n" | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Compare text vs. text0 for tags"
+notmuch search --format=text --output=tags '*' | notmuch_search_sanitize > EXPECTED
+notmuch search --format=text0 --output=tags '*' | tr "\n\0" " \n" | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T190-multipart.sh b/test/T190-multipart.sh
new file mode 100755 (executable)
index 0000000..3eeac1d
--- /dev/null
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+test_description="output of multipart message"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+cat <<EOF > embedded_message_body
+Content-Type: multipart/alternative; boundary="==-=-=="
+
+--==-=-==
+Content-Type: text/html
+
+<p>This is an embedded message, with a multipart/alternative part.</p>
+
+--==-=-==
+Content-Type: text/plain
+
+This is an embedded message, with a multipart/alternative part.
+
+--==-=-==--
+EOF
+cat <<EOF > embedded_message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Subject: html message
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org>
+MIME-Version: 1.0
+EOF
+
+cat embedded_message_body >> embedded_message
+
+cat <<EOF > multipart_body
+Content-Type: multipart/signed; boundary="==-=-=";
+       micalg=pgp-sha1; protocol="application/pgp-signature"
+
+--==-=-=
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+Content-Type: message/rfc822
+Content-Disposition: inline
+
+EOF
+
+cat embedded_message >> multipart_body
+cat <<EOF >> multipart_body
+
+--=-=-=
+Content-Disposition: attachment; filename=attachment
+
+This is a text attachment.
+
+--=-=-=
+
+And this message is signed.
+
+-Carl
+
+--=-=-=--
+
+--==-=-=
+Content-Type: application/pgp-signature
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+iEYEARECAAYFAk3SA/gACgkQ6JDdNq8qSWj0sACghqVJEQJUs3yV8zbTzhgnSIcD
+W6cAmQE4dcYrx/LPLtYLZm1jsGauE5hE
+=zkga
+-----END PGP SIGNATURE-----
+--==-=-=--
+EOF
+
+cat <<EOF > ${MAIL_DIR}/multipart
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Subject: Multipart message
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+Message-ID: <87liy5ap00.fsf@yoom.home.cworth.org>
+MIME-Version: 1.0
+EOF
+
+cat multipart_body >> ${MAIL_DIR}/multipart
+
+cat <<EOF > ${MAIL_DIR}/base64-part-with-crlf
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Subject: Test message with a BASE64 encoded binary containing CRLF pair
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+Message-ID: <base64-part-with-crlf>
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="==-=-=";
+
+--==-=-=
+
+The attached BASE64-encoded part expands to a binary containing a CRLF
+pair (that is one bye of 0x0D followed by one byte of 0x0A). This is
+designed to ensure that notmuch is not corrupting the output of this
+part by converting the CRLF pair to an LF only (as would be appropriate
+for display of a text part on a Linux system, for example).
+
+The part should be a 3-byte file with the following sequence of 3
+hexadecimal bytes:
+
+       EF 0D 0A
+
+--==-=-=
+Content-Type: application/octet-stream
+Content-Disposition: attachment; filename=crlf.bin
+Content-Transfer-Encoding: base64
+
+7w0K
+--==-=-=--
+EOF
+
+cat <<EOF > content_types
+From: Todd <todd@example.com>
+To: todd@example.com
+Subject: odd content types
+Date: Mon, 12 Jan 2014 18:12:32 +0000
+User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+Message-ID: <KfjfO2WJBw2hrV2p0gjT@example.com>
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="==-=-=="
+
+--==-=-==
+Content-Type: application/unique_identifier
+
+<p>This is an embedded message, with a multipart/alternative part.</p>
+
+--==-=-==
+Content-Type: text/some_other_identifier
+
+This is an embedded message, with a multipart/alternative part.
+
+--==-=-==--
+EOF
+cat content_types >> ${MAIL_DIR}/odd_content_type
+notmuch new > /dev/null
+
+test_begin_subtest "--format=text --part=0, full message"
+notmuch show --format=text --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fmessage{ id:87liy5ap00.fsf@yoom.home.cworth.org depth:0 match:1 excluded:0 filename:${MAIL_DIR}/multipart
+\fheader{
+Carl Worth <cworth@cworth.org> (2001-01-05) (attachment inbox signed unread)
+Subject: Multipart message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: multipart/signed
+\fpart{ ID: 2, Content-type: multipart/mixed
+\fpart{ ID: 3, Content-type: message/rfc822
+\fheader{
+Subject: html message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 4, Content-type: multipart/alternative
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+\fpart}
+\fbody}
+\fpart}
+\fattachment{ ID: 7, Filename: attachment, Content-type: text/plain
+This is a text attachment.
+\fattachment}
+\fpart{ ID: 8, Content-type: text/plain
+And this message is signed.
+
+-Carl
+\fpart}
+\fpart}
+\fpart{ ID: 9, Content-type: application/pgp-signature
+Non-text part: application/pgp-signature
+\fpart}
+\fpart}
+\fbody}
+\fmessage}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=1, message body"
+notmuch show --format=text --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 1, Content-type: multipart/signed
+\fpart{ ID: 2, Content-type: multipart/mixed
+\fpart{ ID: 3, Content-type: message/rfc822
+\fheader{
+Subject: html message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 4, Content-type: multipart/alternative
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+\fpart}
+\fbody}
+\fpart}
+\fattachment{ ID: 7, Filename: attachment, Content-type: text/plain
+This is a text attachment.
+\fattachment}
+\fpart{ ID: 8, Content-type: text/plain
+And this message is signed.
+
+-Carl
+\fpart}
+\fpart}
+\fpart{ ID: 9, Content-type: application/pgp-signature
+Non-text part: application/pgp-signature
+\fpart}
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=2, multipart/mixed"
+notmuch show --format=text --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 2, Content-type: multipart/mixed
+\fpart{ ID: 3, Content-type: message/rfc822
+\fheader{
+Subject: html message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 4, Content-type: multipart/alternative
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+\fpart}
+\fbody}
+\fpart}
+\fattachment{ ID: 7, Filename: attachment, Content-type: text/plain
+This is a text attachment.
+\fattachment}
+\fpart{ ID: 8, Content-type: text/plain
+And this message is signed.
+
+-Carl
+\fpart}
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=3, rfc822 part"
+notmuch show --format=text --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 3, Content-type: message/rfc822
+\fheader{
+Subject: html message
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 4, Content-type: multipart/alternative
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+\fpart}
+\fbody}
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=4, rfc822's multipart"
+notmuch show --format=text --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 4, Content-type: multipart/alternative
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=5, rfc822's html part"
+notmuch show --format=text --part=5 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 5, Content-type: text/html
+Non-text part: text/html
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=6, rfc822's text part"
+notmuch show --format=text --part=6 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 6, Content-type: text/plain
+This is an embedded message, with a multipart/alternative part.
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=7, inline attachment"
+notmuch show --format=text --part=7 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fattachment{ ID: 7, Filename: attachment, Content-type: text/plain
+This is a text attachment.
+\fattachment}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=8, plain text part"
+notmuch show --format=text --part=8 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 8, Content-type: text/plain
+And this message is signed.
+
+-Carl
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=9, pgp signature (unverified)"
+notmuch show --format=text --part=9 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+\fpart{ ID: 9, Content-type: application/pgp-signature
+Non-text part: application/pgp-signature
+\fpart}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=text --part=8, no part, expect error"
+test_expect_success "notmuch show --format=text --part=8 'id:87liy5ap00.fsf@yoom.home.cworth.org'"
+
+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": 1, "content-type": "multipart/signed", "content": [
+{"id": 2, "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", "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", "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
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=1, message body"
+notmuch show --format=json --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"id": 1, "content-type": "multipart/signed", "content": [
+{"id": 2, "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", "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", "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
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+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-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", "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)"
+
+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-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"}]}]}]}
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=4, rfc822's multipart/alternative"
+notmuch show --format=json --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"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"}]}
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=5, rfc822's html part"
+notmuch show --format=json --part=5 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"id": 5, "content-type": "text/html", "content-length": 71}
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=6, rfc822's text part"
+notmuch show --format=json --part=6 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"id": 6, "content-type": "text/plain", "content": "This is an embedded message, with a multipart/alternative part.\n"}
+EOF
+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",
+ "content-disposition": "attachment"}
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=8, plain text part"
+notmuch show --format=json --part=8 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"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)"
+
+test_begin_subtest "--format=json --part=9, pgp signature (unverified)"
+notmuch show --format=json --part=9 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+{"id": 9, "content-type": "application/pgp-signature", "content-length": 197}
+EOF
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "--format=json --part=10, no part, expect error"
+test_expect_success "notmuch show --format=json --part=10 'id:87liy5ap00.fsf@yoom.home.cworth.org'"
+
+test_begin_subtest "--format=raw"
+notmuch show --format=raw 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+test_expect_equal_file "${MAIL_DIR}"/multipart  OUTPUT
+
+test_begin_subtest "--format=raw --part=0, full message"
+notmuch show --format=raw --part=0 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+test_expect_equal_file "${MAIL_DIR}"/multipart OUTPUT
+
+test_begin_subtest "--format=raw --part=1, message body"
+test_subtest_broken_gmime_2
+notmuch show --format=raw --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+test_expect_equal_file multipart_body OUTPUT
+
+test_begin_subtest "--format=raw --part=2, multipart/mixed"
+notmuch show --format=raw --part=2 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+Content-Type: message/rfc822
+Content-Disposition: inline
+
+From: Carl Worth <cworth@cworth.org>
+To: cworth@cworth.org
+Subject: html message
+Date: Fri, 05 Jan 2001 15:42:57 +0000
+User-Agent: Notmuch/0.5 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+Message-ID: <87liy5ap01.fsf@yoom.home.cworth.org>
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="==-=-=="
+
+--==-=-==
+Content-Type: text/html
+
+<p>This is an embedded message, with a multipart/alternative part.</p>
+
+--==-=-==
+Content-Type: text/plain
+
+This is an embedded message, with a multipart/alternative part.
+
+--==-=-==--
+
+--=-=-=
+Content-Disposition: attachment; filename=attachment
+
+This is a text attachment.
+
+--=-=-=
+
+And this message is signed.
+
+-Carl
+
+--=-=-=--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=3, rfc822 part"
+notmuch show --format=raw --part=3 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+test_expect_equal_file embedded_message OUTPUT
+
+test_begin_subtest "--format=raw --part=4, rfc822's multipart"
+test_subtest_broken_gmime_2
+notmuch show --format=raw --part=4 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+test_expect_equal_file embedded_message_body OUTPUT
+
+test_begin_subtest "--format=raw --part=5, rfc822's html part"
+notmuch show --format=raw --part=5 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+<p>This is an embedded message, with a multipart/alternative part.</p>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=6, rfc822's text part"
+notmuch show --format=raw --part=6 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+This is an embedded message, with a multipart/alternative part.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=7, inline attachment"
+notmuch show --format=raw --part=7 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+This is a text attachment.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=8, plain text part"
+notmuch show --format=raw --part=8 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+And this message is signed.
+
+-Carl
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=9, pgp signature (unverified)"
+notmuch show --format=raw --part=9 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+# output should *not* include newline
+echo >>OUTPUT
+cat <<EOF >EXPECTED
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+iEYEARECAAYFAk3SA/gACgkQ6JDdNq8qSWj0sACghqVJEQJUs3yV8zbTzhgnSIcD
+W6cAmQE4dcYrx/LPLtYLZm1jsGauE5hE
+=zkga
+-----END PGP SIGNATURE-----
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=raw --part=10, no part, expect error"
+test_expect_success "notmuch show --format=raw --part=8 'id:87liy5ap00.fsf@yoom.home.cworth.org'"
+
+test_begin_subtest "--format=mbox"
+notmuch show --format=mbox 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+printf "From cworth@cworth.org Fri Jan  5 15:43:57 2001\n" >EXPECTED
+cat "${MAIL_DIR}"/multipart >>EXPECTED
+# mbox output is expected to include a blank line
+echo >>EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--format=mbox --part=1, incompatible, expect error"
+test_expect_success "! notmuch show --format=mbox --part=1 'id:87liy5ap00.fsf@yoom.home.cworth.org'"
+
+test_begin_subtest "'notmuch reply' to a multipart message"
+notmuch reply 'id:87liy5ap00.fsf@yoom.home.cworth.org' >OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: Multipart message
+To: Carl Worth <cworth@cworth.org>, cworth@cworth.org
+In-Reply-To: <87liy5ap00.fsf@yoom.home.cworth.org>
+References: <87liy5ap00.fsf@yoom.home.cworth.org>
+
+On Fri, 05 Jan 2001 15:43:57 +0000, Carl Worth <cworth@cworth.org> wrote:
+> From: Carl Worth <cworth@cworth.org>
+> To: cworth@cworth.org
+> Subject: html message
+> Date: Fri, 05 Jan 2001 15:42:57 +0000
+>
+Non-text part: text/html
+> This is an embedded message, with a multipart/alternative part.
+> This is a text attachment.
+> And this message is signed.
+> 
+> -Carl
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "'notmuch reply' to a multipart message with json format"
+notmuch reply --format=json 'id:87liy5ap00.fsf@yoom.home.cworth.org' | notmuch_json_show_sanitize >OUTPUT
+notmuch_json_show_sanitize <<EOF >EXPECTED
+{"reply-headers": {"Subject": "Re: Multipart message",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Carl Worth <cworth@cworth.org>, cworth@cworth.org",
+ "In-reply-to": "<87liy5ap00.fsf@yoom.home.cworth.org>",
+ "References": "<87liy5ap00.fsf@yoom.home.cworth.org>"},
+ "original": {"id": "XXXXX",
+ "match": false,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "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-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",
+ "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
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED)"
+
+test_begin_subtest "'notmuch show --part' does not corrupt a part with CRLF pair"
+notmuch show --format=raw --part=3 id:base64-part-with-crlf > crlf.out
+echo -n -e "\xEF\x0D\x0A" > crlf.expected
+test_expect_equal_file crlf.out crlf.expected
+
+
+# The ISO-8859-1 encoding of U+00BD is a single byte: octal 275
+# (Portability note: Dollar-Single ($'...', ANSI C-style escape sequences)
+# quoting works on bash, ksh, zsh, *BSD sh but not on dash, ash nor busybox sh)
+readonly u_00bd_latin1=$'\275'
+
+# The Unicode fraction symbol 1/2 is U+00BD and is encoded
+# in UTF-8 as two bytes: octal 302 275
+readonly u_00bd_utf8=$'\302\275'
+
+cat <<EOF > ${MAIL_DIR}/include-html
+From: A <a@example.com>
+To: B <b@example.com>
+Subject: html message
+Date: Sat, 01 January 2000 00:00:00 +0000
+Message-ID: <htmlmessage>
+MIME-Version: 1.0
+Content-Type: multipart/alternative; boundary="==-=="
+
+--==-==
+Content-Type: text/html; charset=UTF-8
+
+<p>0.5 equals ${u_00bd_utf8}</p>
+
+--==-==
+Content-Type: text/html; charset=ISO-8859-1
+
+<p>0.5 equals ${u_00bd_latin1}</p>
+
+--==-==
+Content-Type: text/plain; charset=UTF-8
+
+0.5 equals ${u_00bd_utf8}
+
+--==-==--
+EOF
+
+notmuch new > /dev/null
+
+cat_expected_head ()
+{
+        cat <<EOF
+[[[{"id": "htmlmessage", "match":true, "excluded": false, "date_relative":"2000-01-01",
+   "timestamp": 946684800,
+   "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>"},
+   "body": [{
+     "content-type": "multipart/alternative", "id": 1,
+EOF
+}
+
+cat_expected_head > EXPECTED.nohtml
+cat <<EOF >> EXPECTED.nohtml
+"content": [
+  { "id": 2, "content-charset": "UTF-8", "content-length": 21, "content-type": "text/html"},
+  { "id": 3, "content-charset": "ISO-8859-1", "content-length": 20, "content-type": "text/html"},
+  { "id": 4, "content-type": "text/plain", "content": "0.5 equals \\u00bd\\n"}
+]}]},[]]]]
+EOF
+
+# Both the UTF-8 and ISO-8859-1 part should have U+00BD
+cat_expected_head > EXPECTED.withhtml
+cat <<EOF >> EXPECTED.withhtml
+"content": [
+  { "id": 2, "content-type": "text/html", "content": "<p>0.5 equals \\u00bd</p>\\n"},
+  { "id": 3, "content-type": "text/html", "content": "<p>0.5 equals \\u00bd</p>\\n"},
+  { "id": 4, "content-type": "text/plain", "content": "0.5 equals \\u00bd\\n"}
+]}]},[]]]]
+EOF
+
+test_begin_subtest "html parts excluded by default"
+notmuch show --format=json id:htmlmessage > OUTPUT
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED.nohtml)"
+
+test_begin_subtest "html parts included"
+notmuch show --format=json --include-html id:htmlmessage > OUTPUT
+test_expect_equal_json "$(cat OUTPUT)" "$(cat EXPECTED.withhtml)"
+
+test_begin_subtest "indexes mime-type #1"
+output=$(notmuch search mimetype:application/unique_identifier | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2014-01-12 [1/1] Todd; odd content types (inbox unread)"
+
+test_begin_subtest "indexes mime-type #2"
+output=$(notmuch search mimetype:text/some_other_identifier | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2014-01-12 [1/1] Todd; odd content types (inbox unread)"
+
+test_begin_subtest "indexes mime-type #3"
+output=$(notmuch search from:todd and mimetype:multipart/alternative | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2014-01-12 [1/1] Todd; odd content types (inbox unread)"
+
+test_begin_subtest "case of Content-Disposition doesn't matter for indexing"
+cat <<EOF > ${MAIL_DIR}/content-disposition
+Return-path: <david@tethera.net>
+Envelope-to: david@tethera.net
+Delivery-date: Sun, 04 Oct 2015 09:16:03 -0300
+Received: from gitolite.debian.net ([87.98.215.224])
+       by yantan.tethera.net with esmtps (TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128)
+       (Exim 4.80)
+       (envelope-from <david@tethera.net>)
+       id 1ZiiCx-0007iz-RK
+       for david@tethera.net; Sun, 04 Oct 2015 09:16:03 -0300
+Received: from remotemail by gitolite.debian.net with local (Exim 4.80)
+       (envelope-from <david@tethera.net>)
+       id 1ZiiC8-0002Rz-Uf; Sun, 04 Oct 2015 12:15:12 +0000
+Received: (nullmailer pid 28621 invoked by uid 1000); Sun, 04 Oct 2015
+ 12:14:53 -0000
+From: David Bremner <david@tethera.net>
+To: David Bremner <david@tethera.net>
+Subject: test attachment
+User-Agent: Notmuch/0.20.2+93~g33c8777 (http://notmuchmail.org) Emacs/24.5.1
+ (x86_64-pc-linux-gnu)
+Date: Sun, 04 Oct 2015 09:14:53 -0300
+Message-ID: <87io6m96f6.fsf@zancas.localnet>
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+Content-Type: text/plain
+Content-Disposition: ATTACHMENT; filename=hello.txt
+Content-Description: this is a very exciting file
+
+hello
+
+--=-=-=
+Content-Type: text/plain
+
+
+world
+
+--=-=-=--
+
+EOF
+NOTMUCH_NEW
+
+cat <<EOF > EXPECTED
+attachment
+inbox
+unread
+EOF
+
+notmuch search --output=tags id:87io6m96f6.fsf@zancas.localnet > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+test_done
diff --git a/test/T200-thread-naming.sh b/test/T200-thread-naming.sh
new file mode 100755 (executable)
index 0000000..594d301
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/env bash
+test_description="naming of threads with changing subject"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Initial thread name (oldest-first search)"
+add_message '[subject]="thread-naming: Initial thread subject"' \
+           '[date]="Fri, 05 Jan 2001 15:43:56 -0000"'
+first=${gen_msg_cnt}
+parent=${gen_msg_id}
+add_message '[subject]="thread-naming: Older changed subject"' \
+           '[date]="Sat, 06 Jan 2001 15:43:56 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+add_message '[subject]="thread-naming: Newer changed subject"' \
+           '[date]="Sun, 07 Jan 2001 15:43:56 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+add_message '[subject]="thread-naming: Final thread subject"' \
+           '[date]="Mon, 08 Jan 2001 15:43:56 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+final=${gen_msg_id}
+output=$(notmuch search --sort=oldest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [4/4] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)"
+
+test_begin_subtest "Initial thread name (newest-first search)"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-08 [4/4] Notmuch Test Suite; thread-naming: Final thread subject (inbox unread)"
+
+# Remove oldest and newest messages from search results
+notmuch tag -inbox id:$parent or id:$final
+
+test_begin_subtest "Changed thread name (oldest-first search)"
+output=$(notmuch search --sort=oldest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-06 [2/4] Notmuch Test Suite; thread-naming: Older changed subject (inbox unread)"
+
+test_begin_subtest "Changed thread name (newest-first search)"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-07 [2/4] Notmuch Test Suite; thread-naming: Newer changed subject (inbox unread)"
+
+test_begin_subtest "Ignore added reply prefix (Re:)"
+add_message '[subject]="Re: thread-naming: Initial thread subject"' \
+           '[date]="Tue, 09 Jan 2001 15:43:45 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-09 [3/5] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)"
+
+test_begin_subtest "Ignore added reply prefix (Aw:)"
+add_message '[subject]="Aw: thread-naming: Initial thread subject"' \
+           '[date]="Wed, 10 Jan 2001 15:43:45 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-10 [4/6] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)"
+
+test_begin_subtest "Ignore added reply prefix (Vs:)"
+add_message '[subject]="Vs: thread-naming: Initial thread subject"' \
+           '[date]="Thu, 11 Jan 2001 15:43:45 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-11 [5/7] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)"
+
+test_begin_subtest "Ignore added reply prefix (Sv:)"
+add_message '[subject]="Sv: thread-naming: Initial thread subject"' \
+           '[date]="Fri, 12 Jan 2001 15:43:45 -0000"' \
+           "[in-reply-to]=\<$parent\>"
+output=$(notmuch search --sort=newest-first thread-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-12 [6/8] Notmuch Test Suite; thread-naming: Initial thread subject (inbox unread)"
+
+test_begin_subtest "Use empty subjects if necessary."
+add_message '[subject]="@FORCE_EMPTY"' \
+           '[date]="Sat, 13 Jan 2001 15:43:45 -0000"' \
+            '[from]="Empty Sender <empty_test@notmuchmail.org>"'
+empty_parent=${gen_msg_id}
+add_message '[subject]="@FORCE_EMPTY"' \
+           '[date]="Sun, 14 Jan 2001 15:43:45 -0000"' \
+            '[from]="Empty Sender <empty_test@notmuchmail.org>"' \
+            "[in-reply-to]=\<$empty_parent\>"
+output=$(notmuch search --sort=newest-first from:empty_test@notmuchmail.org | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-14 [2/2] Empty Sender;  (inbox unread)"
+
+test_begin_subtest "Avoid empty subjects if possible (newest-first)."
+add_message '[subject]="Non-empty subject (1)"' \
+           '[date]="Mon, 15 Jan 2001 15:43:45 -0000"' \
+            '[from]="Empty Sender <empty_test@notmuchmail.org>"' \
+            "[in-reply-to]=\<$empty_parent\>"
+add_message '[subject]="Non-empty subject (2)"' \
+           '[date]="Mon, 16 Jan 2001 15:43:45 -0000"' \
+            '[from]="Empty Sender <empty_test@notmuchmail.org>"' \
+            "[in-reply-to]=\<$empty_parent\>"
+add_message '[subject]="@FORCE_EMPTY"' \
+           '[date]="Tue, 17 Jan 2001 15:43:45 -0000"' \
+            '[from]="Empty Sender <empty_test@notmuchmail.org>"' \
+            "[in-reply-to]=\<$empty_parent\>"
+output=$(notmuch search --sort=newest-first from:Empty | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-17 [5/5] Empty Sender; Non-empty subject (2) (inbox unread)"
+
+test_begin_subtest "Avoid empty subjects if possible (oldest-first)."
+output=$(notmuch search --sort=oldest-first from:Empty | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-13 [5/5] Empty Sender; Non-empty subject (1) (inbox unread)"
+
+test_begin_subtest 'Test order of messages in "notmuch show"'
+output=$(notmuch show thread-naming | notmuch_show_sanitize)
+test_expect_equal "$output" "\fmessage{ id:msg-$(printf "%03d" $first)@notmuch-test-suite depth:0 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $first)
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (unread)
+Subject: thread-naming: Initial thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:56 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$first)
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 1)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 1)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-06) (inbox unread)
+Subject: thread-naming: Older changed subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Sat, 06 Jan 2001 15:43:56 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 1)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 2)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 2)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-07) (inbox unread)
+Subject: thread-naming: Newer changed subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Sun, 07 Jan 2001 15:43:56 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 2)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 3)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 3)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-08) (unread)
+Subject: thread-naming: Final thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Mon, 08 Jan 2001 15:43:56 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 3)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 4)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 4)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-09) (inbox unread)
+Subject: Re: thread-naming: Initial thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Tue, 09 Jan 2001 15:43:45 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 4)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 5)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 5)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-10) (inbox unread)
+Subject: Aw: thread-naming: Initial thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Wed, 10 Jan 2001 15:43:45 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 5)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 6)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 6)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-11) (inbox unread)
+Subject: Vs: thread-naming: Initial thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Thu, 11 Jan 2001 15:43:45 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 6)))
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:msg-$(printf "%03d" $((first + 7)))@notmuch-test-suite depth:1 match:1 excluded:0 filename:/XXX/mail/msg-$(printf "%03d" $((first + 7)))
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-12) (inbox unread)
+Subject: Sv: thread-naming: Initial thread subject
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 12 Jan 2001 15:43:45 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This is just a test message (#$((first + 7)))
+\fpart}
+\fbody}
+\fmessage}"
+test_done
diff --git a/test/T205-author-naming.sh b/test/T205-author-naming.sh
new file mode 100755 (executable)
index 0000000..68b85ce
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+test_description="naming of authors with unusual addresses"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Add author with empty quoted real name"
+add_message '[subject]="author-naming: Initial thread subject"' \
+           '[date]="Fri, 05 Jan 2001 15:43:56 -0000"' \
+           '[from]="\"\" <address@example.com>"'
+output=$(notmuch search --sort=oldest-first author-naming and tag:inbox | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] address@example.com; author-naming: Initial thread subject (inbox unread)"
+
+test_done
diff --git a/test/T210-raw.sh b/test/T210-raw.sh
new file mode 100755 (executable)
index 0000000..99fdef7
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+test_description='notmuch show --format=raw'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message
+add_message
+
+test_begin_subtest "Attempt to show multiple raw messages"
+output=$(notmuch show --format=raw "*" 2>&1)
+test_expect_equal "$output" "Error: search term did not match precisely one message (matched 2 messages)."
+
+test_begin_subtest "Show a raw message"
+output=$(notmuch show --format=raw id:msg-001@notmuch-test-suite | notmuch_date_sanitize)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-001@notmuch-test-suite>
+Subject: Test message #1
+Date: GENERATED_DATE
+
+This is just a test message (#1)"
+
+test_begin_subtest "Show another raw message"
+output=$(notmuch show --format=raw id:msg-002@notmuch-test-suite | notmuch_date_sanitize)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Message-Id: <msg-002@notmuch-test-suite>
+Subject: Test message #2
+Date: GENERATED_DATE
+
+This is just a test message (#2)"
+
+test_done
diff --git a/test/T220-reply.sh b/test/T220-reply.sh
new file mode 100755 (executable)
index 0000000..ebe710f
--- /dev/null
@@ -0,0 +1,322 @@
+#!/usr/bin/env bash
+test_description="\"notmuch reply\" in several variations"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Basic reply"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=test_suite@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="basic reply test"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> basic reply test
+OK"
+
+test_begin_subtest "Multiple recipients"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="test_suite@notmuchmail.org, Someone Else <someone@example.com>"' \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Multiple recipients"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, Someone Else <someone@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Multiple recipients
+OK"
+
+test_begin_subtest "Reply with CC"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=test_suite@notmuchmail.org \
+           '[cc]="Other Parties <cc@example.com>"' \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="reply with CC"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+Cc: Other Parties <cc@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> reply with CC
+OK"
+
+test_begin_subtest "Reply from alternate address"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=test_suite_other@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="reply from alternate address"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> reply from alternate address
+OK"
+
+test_begin_subtest "Reply from address in named group list"
+add_message '[from]="Sender <sender@example.com>"' \
+            '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \
+             [cc]=test_suite_other@notmuchmail.org \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="Reply from address in named group list"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, someone@example.com
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Reply from address in named group list
+OK"
+
+test_begin_subtest "Support for Reply-To"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=test_suite@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="support for reply-to"' \
+           '[reply-to]="Sender <elsewhere@example.com>"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <elsewhere@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> support for reply-to
+OK"
+
+test_begin_subtest "Un-munging Reply-To"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Some List <list@example.com>"' \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Un-munging Reply-To"' \
+           '[reply-to]="Evil Munging List <list@example.com>"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, Some List <list@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Un-munging Reply-To
+OK"
+
+test_begin_subtest "Un-munging Reply-To With Exact Match"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Some List <list@example.com>"' \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Un-munging Reply-To"' \
+           '[reply-to]="Some List <list@example.com>"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, Some List <list@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Un-munging Reply-To
+OK"
+
+test_begin_subtest "Un-munging Reply-To With Raw addr-spec"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Some List <list@example.com>"' \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Un-munging Reply-To"' \
+           '[reply-to]="list@example.com"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, Some List <list@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Un-munging Reply-To
+OK"
+
+test_begin_subtest "Message with header of exactly 200 bytes"
+add_message '[subject]="This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="200-byte header"'
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: This subject is exactly 200 bytes in length. Other than its
+ length there is not much of note here. Note that the length of 200 bytes
+ includes the Subject: and Re: prefixes with two spaces
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> 200-byte header
+OK"
+
+test_begin_subtest "From guessing: Envelope-To"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Recipient <recipient@example.com>"' \
+           '[subject]="From guessing"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="From guessing"' \
+           '[header]="Envelope-To: test_suite_other@notmuchmail.org"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: From guessing
+To: Sender <sender@example.com>, Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> From guessing
+OK"
+
+test_begin_subtest "From guessing: X-Original-To"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Recipient <recipient@example.com>"' \
+           '[subject]="From guessing"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="From guessing"' \
+           '[header]="X-Original-To: test_suite@otherdomain.org"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@otherdomain.org>
+Subject: Re: From guessing
+To: Sender <sender@example.com>, Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> From guessing
+OK"
+
+test_begin_subtest "From guessing: Delivered-To"
+add_message '[from]="Sender <sender@example.com>"' \
+           '[to]="Recipient <recipient@example.com>"' \
+           '[subject]="From guessing"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="From guessing"' \
+           '[header]="Delivered-To: test_suite_other@notmuchmail.org"'
+
+output=$(notmuch reply id:${gen_msg_id} 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: From guessing
+To: Sender <sender@example.com>, Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> From guessing
+OK"
+
+test_begin_subtest "Reply with RFC 2047-encoded headers"
+add_message '[subject]="=?iso-8859-1?q?=e0=df=e7?="' \
+           '[from]="=?utf-8?q?=e2=98=83?= <snowman@example.com>"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="Encoding"'
+
+# GMime happens to change from Q- to B-encoding.  We canonicalize the
+# case of the encoding and charset because different versions of GMime
+# capitalize the encoding differently.
+output=$( (notmuch reply id:${gen_msg_id} 2>&1 && echo OK) | perl -pe 's/=\?[^?]+\?[bB]\?/lc($&)/ge')
+test_expect_equal "$output" "\
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: =?iso-8859-1?b?4N/n?=
+To: =?utf-8?b?4piD?= <snowman@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, ☃ <snowman@example.com> wrote:
+> Encoding
+OK"
+
+test_begin_subtest "Reply with RFC 2047-encoded headers (JSON)"
+output=$(echo '{"answer":' && notmuch reply --format=json id:${gen_msg_id} 2>&1 && echo ', "success": "OK"}')
+test_expect_equal_json "$output" '
+{  "answer": {
+    "original": {
+        "body": [
+            {
+                "content": "Encoding\n",
+                "content-type": "text/plain",
+                "id": 1
+            }
+        ],
+        "date_relative": "2010-01-05",
+        "excluded": false,
+        "filename": ["'${MAIL_DIR}'/msg-014"],
+        "headers": {
+            "Date": "Tue, 05 Jan 2010 15:43:56 +0000",
+            "From": "\u2603 <snowman@example.com>",
+            "Subject": "\u00e0\u00df\u00e7",
+            "To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
+        },
+        "id": "'${gen_msg_id}'",
+        "match": false,
+        "tags": [
+            "inbox",
+            "unread"
+        ],
+        "timestamp": 1262706236
+    },
+    "reply-headers": {
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "In-reply-to": "<'${gen_msg_id}'>",
+        "References": "<'${gen_msg_id}'>",
+        "Subject": "Re: \u00e0\u00df\u00e7",
+        "To": "\u2603 <snowman@example.com>"
+    }
+  },
+  "success": "OK"
+}'
+
+test_begin_subtest "Reply to a message with multiple Cc headers"
+add_email_corpus broken
+output=$(notmuch reply id:multiple-cc@example.org 2>&1 && echo OK)
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: wowsers!
+To: Alice <alice@example.org>, Daniel <daniel@example.org>
+Cc: Bob <bob@example.org>, Charles <charles@example.org>
+In-Reply-To: <multiple-cc@example.org>
+References: <multiple-cc@example.org>
+
+On Thu, 16 Jun 2016 22:14:41 -0400, Alice <alice@example.org> wrote:
+> Note the Cc: and cc: headers.
+OK"
+
+test_done
diff --git a/test/T230-reply-to-sender.sh b/test/T230-reply-to-sender.sh
new file mode 100755 (executable)
index 0000000..134a106
--- /dev/null
@@ -0,0 +1,211 @@
+#!/usr/bin/env bash
+test_description="\"notmuch reply --reply-to=sender\" in several variations"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Basic reply-to-sender"
+add_message '[from]="Sender <sender@example.com>"' \
+             [to]=test_suite@notmuchmail.org \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="basic reply-to-sender test"'
+
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> basic reply-to-sender test"
+
+test_begin_subtest "From Us, Basic reply to message"
+add_message '[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"' \
+            '[to]="Recipient <recipient@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="basic reply-to-from-us test"'
+
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> basic reply-to-from-us test"
+
+test_begin_subtest "Multiple recipients"
+add_message '[from]="Sender <sender@example.com>"' \
+            '[to]="test_suite@notmuchmail.org, Someone Else <someone@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="Multiple recipients"'
+
+output=$(notmuch reply  --reply-to=sender  id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Multiple recipients"
+
+test_begin_subtest "From Us, Multiple TO recipients"
+add_message '[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"' \
+            '[to]="Recipient <recipient@example.com>, Someone Else <someone@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="From Us, Multiple TO recipients"'
+
+output=$(notmuch reply  --reply-to=sender  id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Recipient <recipient@example.com>, Someone Else <someone@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> From Us, Multiple TO recipients"
+
+test_begin_subtest "Reply with CC"
+add_message '[from]="Sender <sender@example.com>"' \
+             [to]=test_suite@notmuchmail.org \
+            '[cc]="Other Parties <cc@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="reply with CC"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> reply with CC"
+
+test_begin_subtest "From Us, Reply with CC"
+add_message '[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"' \
+            '[to]="Recipient <recipient@example.com>"' \
+            '[cc]="Other Parties <cc@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="reply with CC"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Recipient <recipient@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> reply with CC"
+
+test_begin_subtest "From Us, Reply no TO but with CC"
+add_message '[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"' \
+            '[cc]="Other Parties <cc@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="reply with CC"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+Cc: Other Parties <cc@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> reply with CC"
+
+test_begin_subtest "Reply from alternate address"
+add_message '[from]="Sender <sender@example.com>"' \
+             [to]=test_suite_other@notmuchmail.org \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="reply from alternate address"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> reply from alternate address"
+
+test_begin_subtest "Support for Reply-To"
+add_message '[from]="Sender <sender@example.com>"' \
+             [to]=test_suite@notmuchmail.org \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="support for reply-to"' \
+            '[reply-to]="Sender <elsewhere@example.com>"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <elsewhere@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> support for reply-to"
+
+test_begin_subtest "Support for Reply-To with multiple recipients"
+add_message '[from]="Sender <sender@example.com>"' \
+            '[to]="test_suite@notmuchmail.org, Someone Else <someone@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="support for reply-to with multiple recipients"' \
+            '[reply-to]="Sender <elsewhere@example.com>"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <elsewhere@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> support for reply-to with multiple recipients"
+
+test_begin_subtest "Un-munging Reply-To"
+add_message '[from]="Sender <sender@example.com>"' \
+            '[to]="Some List <list@example.com>"' \
+             [subject]=notmuch-reply-test \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="Un-munging Reply-To"' \
+            '[reply-to]="Evil Munging List <list@example.com>"'
+
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> Un-munging Reply-To"
+
+test_begin_subtest "Message with header of exactly 200 bytes"
+add_message '[subject]="This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces"' \
+            '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+            '[body]="200-byte header"'
+output=$(notmuch reply  --reply-to=sender id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: This subject is exactly 200 bytes in length. Other than its
+ length there is not much of note here. Note that the length of 200 bytes
+ includes the Subject: and Re: prefixes with two spaces
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> 200-byte header"
+test_done
diff --git a/test/T240-dump-restore.sh b/test/T240-dump-restore.sh
new file mode 100755 (executable)
index 0000000..0870ff9
--- /dev/null
@@ -0,0 +1,344 @@
+#!/usr/bin/env bash
+test_description="\"notmuch dump\" and \"notmuch restore\""
+. $(dirname "$0")/test-lib.sh || exit 1
+
+NOTMUCH_NEW > /dev/null
+test_begin_subtest "dump header"
+cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 config,properties,tags
+EOF
+notmuch dump > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+add_email_corpus
+
+test_begin_subtest "Dumping all tags"
+test_expect_success 'generate_message && notmuch new && notmuch dump > dump.expected'
+
+# The use of from:cworth is rather arbitrary: it matches some of the
+# email corpus' messages, but not all of them.
+
+test_begin_subtest "Dumping all tags II"
+test_expect_success \
+  'notmuch tag +ABC +DEF -- from:cworth &&
+  notmuch dump > dump-ABC_DEF.expected &&
+  ! cmp dump.expected dump-ABC_DEF.expected'
+
+test_begin_subtest "Clearing all tags"
+test_expect_success \
+  'sed -e "s/(\([^(]*\))$/()/" < dump.expected > clear.expected &&
+  notmuch restore --input=clear.expected &&
+  notmuch dump > clear.actual &&
+  test_cmp clear.expected clear.actual'
+
+test_begin_subtest "Clearing all tags"
+test_expect_success \
+  'notmuch tag +ABC +DEF -- from:cworth &&
+  notmuch restore --accumulate < dump.expected &&
+  notmuch dump > dump.actual &&
+  test_cmp dump-ABC_DEF.expected dump.actual'
+
+test_begin_subtest "Restoring original tags"
+test_expect_success \
+  'notmuch restore --input=dump.expected &&
+  notmuch dump > dump.actual &&
+  test_cmp dump.expected dump.actual'
+
+test_begin_subtest "Restore with nothing to do"
+test_expect_success \
+  'notmuch restore < dump.expected &&
+  notmuch dump > dump.actual &&
+  test_cmp dump.expected dump.actual'
+
+test_begin_subtest "Accumulate with existing tags"
+test_expect_success \
+  'notmuch restore --accumulate --input=dump.expected &&
+  notmuch dump > dump.actual &&
+  test_cmp dump.expected dump.actual'
+
+test_begin_subtest "Accumulate with no tags"
+test_expect_success \
+  'notmuch restore --accumulate < clear.expected &&
+  notmuch dump > dump.actual &&
+  test_cmp dump.expected dump.actual'
+
+test_begin_subtest "Accumulate with new tags"
+test_expect_success \
+  'notmuch restore --input=dump.expected &&
+  notmuch restore --accumulate --input=dump-ABC_DEF.expected &&
+  notmuch dump >  OUTPUT.$test_count &&
+  notmuch restore --input=dump.expected &&
+  test_cmp dump-ABC_DEF.expected OUTPUT.$test_count'
+
+# notmuch restore currently only considers the first argument.
+test_begin_subtest "Invalid restore invocation"
+test_expect_success \
+  'test_must_fail notmuch restore --input=dump.expected another_one'
+
+test_begin_subtest "dump --output=outfile"
+notmuch dump --output=dump-outfile.actual
+test_expect_equal_file dump.expected dump-outfile.actual
+
+test_begin_subtest "dump --output=outfile --"
+notmuch dump --output=dump-1-arg-dash.actual --
+test_expect_equal_file dump.expected dump-1-arg-dash.actual
+
+# gzipped output
+
+test_begin_subtest "dump --gzip"
+notmuch dump --gzip > dump-gzip.gz
+gunzip dump-gzip.gz
+test_expect_equal_file dump.expected dump-gzip
+
+test_begin_subtest "dump --gzip --output=outfile"
+notmuch dump --gzip --output=dump-gzip-outfile.gz
+gunzip dump-gzip-outfile.gz
+test_expect_equal_file dump.expected dump-gzip-outfile
+
+test_begin_subtest "restoring gzipped stdin"
+notmuch dump --gzip --output=backup.gz
+notmuch tag +new_tag '*'
+notmuch restore < backup.gz
+notmuch dump --output=dump.actual
+test_expect_equal_file dump.expected dump.actual
+
+test_begin_subtest "restoring gzipped file"
+notmuch dump --gzip --output=backup.gz
+notmuch tag +new_tag '*'
+notmuch restore --input=backup.gz
+notmuch dump --output=dump.actual
+test_expect_equal_file dump.expected dump.actual
+
+# Note, we assume all messages from cworth have a message-id
+# containing cworth.org
+
+{ head -1 dump.expected ; grep 'cworth[.]org' dump.expected; } > dump-cworth.expected
+
+test_begin_subtest "dump -- from:cworth"
+notmuch dump -- from:cworth > dump-dash-cworth.actual
+test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
+
+test_begin_subtest "dump --output=outfile from:cworth"
+notmuch dump --output=dump-outfile-cworth.actual from:cworth
+test_expect_equal_file dump-cworth.expected dump-outfile-cworth.actual
+
+test_begin_subtest "dump --output=outfile -- from:cworth"
+notmuch dump --output=dump-outfile-dash-inbox.actual -- from:cworth
+test_expect_equal_file dump-cworth.expected dump-outfile-dash-inbox.actual
+
+test_begin_subtest "Check for a safe set of message-ids"
+notmuch search --output=messages from:cworth | sed s/^id:// > EXPECTED
+notmuch search --output=messages from:cworth | sed s/^id:// |\
+       $TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "format=batch-tag, dump sanity check."
+NOTMUCH_DUMP_TAGS --format=sup from:cworth | cut -f1 -d' ' | \
+    sort > EXPECTED.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag from:cworth | sed 's/^.*-- id://' | \
+    sort > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "format=batch-tag, missing newline"
+printf "+a_tag_without_newline -- id:20091117232137.GA7669@griffis1.net" > IN
+notmuch restore --accumulate < IN
+NOTMUCH_DUMP_TAGS id:20091117232137.GA7669@griffis1.net > OUT
+cat <<EOF > EXPECTED
++a_tag_without_newline +inbox +unread -- id:20091117232137.GA7669@griffis1.net
+EOF
+test_expect_equal_file EXPECTED OUT
+
+test_begin_subtest "format=batch-tag, # round-trip"
+notmuch dump --format=sup | sort > EXPECTED.$test_count
+notmuch dump --format=batch-tag > DUMPFILE
+notmuch restore --format=batch-tag < DUMPFILE
+notmuch dump --format=sup | sort > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "format=batch-tag, # blank lines and comments"
+notmuch dump --format=batch-tag| sort > EXPECTED.$test_count
+notmuch restore <<EOF
+# this line is a comment; the next has only white space
+        
+
+# the previous line is empty
+EOF
+notmuch dump --format=batch-tag | sort > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "format=batch-tag, # reverse-round-trip empty tag"
+cat <<EOF >EXPECTED.$test_count
++ -- id:20091117232137.GA7669@griffis1.net
+EOF
+notmuch restore --format=batch-tag < EXPECTED.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag id:20091117232137.GA7669@griffis1.net > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+tag1='comic_swear=$&^%$^%\\//-+$^%$'
+enc1=$($TEST_DIRECTORY/hex-xcode --direction=encode "$tag1")
+
+tag2=$(printf 'this\n tag\t has\n spaces')
+enc2=$($TEST_DIRECTORY/hex-xcode --direction=encode "$tag2")
+
+enc3='%c3%91%c3%a5%c3%b0%c3%a3%c3%a5%c3%a9-%c3%8f%c3%8a'
+tag3=$($TEST_DIRECTORY/hex-xcode --direction=decode $enc3)
+
+notmuch dump --format=batch-tag > BACKUP
+
+notmuch tag +"$tag1" +"$tag2" +"$tag3" -inbox -unread "*"
+
+# initial segment of file used for several tests below.
+cat <<EOF > comments-and-blanks
+# this is a comment
+
+# next line has leading whitespace
+       
+
+EOF
+
+test_begin_subtest 'restoring empty file is not an error'
+notmuch restore < /dev/null 2>OUTPUT.$test_count
+cp /dev/null EXPECTED
+test_expect_equal_file EXPECTED OUTPUT.$test_count
+
+test_begin_subtest 'file of comments and blank lines is not an error'
+notmuch restore --input=comments-and-blanks
+ret_val=$?
+test_expect_equal "$ret_val" "0"
+
+cp comments-and-blanks leading-comments-blanks-batch-tag
+echo "+some_tag -- id:yun1vjwegii.fsf@aiko.keithp.com" \
+    >> leading-comments-blanks-batch-tag
+
+test_begin_subtest 'detect format=batch-tag with leading comments and blanks'
+notmuch restore --input=leading-comments-blanks-batch-tag
+notmuch search --output=tags id:yun1vjwegii.fsf@aiko.keithp.com > OUTPUT.$test_count
+echo "some_tag" > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT.$test_count
+
+cp comments-and-blanks leading-comments-blanks-sup
+echo "yun1vjwegii.fsf@aiko.keithp.com (another_tag)" \
+    >> leading-comments-blanks-sup
+
+test_begin_subtest 'detect format=sup with leading comments and blanks'
+notmuch restore --input=leading-comments-blanks-sup
+notmuch search --output=tags id:yun1vjwegii.fsf@aiko.keithp.com > OUTPUT.$test_count
+echo "another_tag" > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT.$test_count
+
+test_begin_subtest 'format=batch-tag, round trip with strange tags'
+notmuch dump --format=batch-tag > EXPECTED.$test_count
+notmuch dump --format=batch-tag > DUMPFILE
+notmuch restore --format=batch-tag < DUMPFILE
+notmuch dump --format=batch-tag > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'format=batch-tag, checking encoded output'
+NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
+        awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth  > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'restoring sane tags'
+notmuch restore --format=batch-tag < BACKUP
+notmuch dump --format=batch-tag > OUTPUT.$test_count
+test_expect_equal_file BACKUP OUTPUT.$test_count
+
+test_begin_subtest 'format=batch-tag, restore=auto'
+notmuch dump --format=batch-tag > EXPECTED.$test_count
+notmuch tag -inbox -unread "*"
+notmuch restore --format=auto < EXPECTED.$test_count
+notmuch dump --format=batch-tag > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'format=sup, restore=auto'
+notmuch dump --format=sup > EXPECTED.$test_count
+notmuch tag -inbox -unread "*"
+notmuch restore --format=auto < EXPECTED.$test_count
+notmuch dump --format=sup > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'format=batch-tag, restore=default'
+notmuch dump --format=batch-tag > EXPECTED.$test_count
+notmuch tag -inbox -unread "*"
+notmuch restore < EXPECTED.$test_count
+notmuch dump --format=batch-tag > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'format=sup, restore=default'
+notmuch dump --format=sup > EXPECTED.$test_count
+notmuch tag -inbox -unread "*"
+notmuch restore < EXPECTED.$test_count
+notmuch dump --format=sup > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest 'restore: checking error messages'
+notmuch restore <<EOF 2>OUTPUT
+# the next line has a space
+a
++0
++a +b
+# trailing whitespace
++a +b 
++c +d --
+# this is a harmless comment, do not yell about it.
+
+# the previous line was blank; also no yelling please
++%zz -- id:whatever
++e +f id:"
++e +f tag:abc
+# the next non-comment line should report an an empty tag error for
+# batch tagging, but not for restore
++ +e -- id:20091117232137.GA7669@griffis1.net
+# valid id, but warning about missing message
++e id:missing_message_id
+# exercise parser
++e -- id:some)stuff
++e -- id:some stuff
++e -- id:some"stuff
++e -- id:"a_message_id_with""_a_quote"
++e -- id:"a message id with spaces"
++e --  id:an_id_with_leading_and_trailing_ws \
+
+EOF
+
+cat <<EOF > EXPECTED
+Warning: cannot parse query: a (skipping)
+Warning: no query string [+0]
+Warning: no query string [+a +b]
+Warning: missing query string [+a +b ]
+Warning: no query string after -- [+c +d --]
+Warning: hex decoding of tag %zz failed [+%zz -- id:whatever]
+Warning: cannot parse query: id:" (skipping)
+Warning: not an id query: tag:abc (skipping)
+Warning: cannot apply tags to missing message: missing_message_id
+Warning: cannot parse query: id:some)stuff (skipping)
+Warning: cannot parse query: id:some stuff (skipping)
+Warning: cannot apply tags to missing message: some"stuff
+Warning: cannot apply tags to missing message: a_message_id_with"_a_quote
+Warning: cannot apply tags to missing message: a message id with spaces
+Warning: cannot apply tags to missing message: an_id_with_leading_and_trailing_ws
+EOF
+
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'roundtripping random message-ids and tags'
+
+    ${TEST_DIRECTORY}/random-corpus --config-path=${NOTMUCH_CONFIG} \
+                       --num-messages=100
+
+     notmuch dump --format=batch-tag| \
+        sort > EXPECTED.$test_count
+
+     notmuch tag +this_tag_is_very_unlikely_to_be_random '*'
+
+     notmuch restore --format=batch-tag < EXPECTED.$test_count
+
+     notmuch dump --format=batch-tag| \
+        sort > OUTPUT.$test_count
+
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_done
+
+# Note the database is "poisoned" for sup format at this point.
diff --git a/test/T250-uuencode.sh b/test/T250-uuencode.sh
new file mode 100755 (executable)
index 0000000..251c0b4
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+test_description="handling of uuencoded data"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message [subject]=uuencodetest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \
+'[body]="This message is used to ensure that notmuch correctly handles a
+message containing a block of uuencoded data. First, we have a marker
+this content beforeuudata . Then we begin the uuencoded data itself:
+
+begin 644 bogus-uuencoded-data
+M0123456789012345678901234567890123456789012345678901234567890
+MOBVIOUSLY, THIS IS NOT ANY SORT OF USEFUL UUENCODED DATA.    
+MINSTEAD THIS IS JUST A WAY TO ENSURE THAT THIS BLOCK OF DATA 
+MIS CORRECTLY IGNORED WHEN NOTMUCH CREATES ITS INDEX. SO WE   
+MINCLUDE A DURINGUUDATA MARKER THAT SHOULD NOT RESULT IN ANY  
+MSEARCH RESULT.                                               
+\\\`
+end
+
+Finally, we have our afteruudata marker as well."'
+
+test_begin_subtest "Ensure content before uu data is indexed"
+output=$(notmuch search beforeuudata | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; uuencodetest (inbox unread)"
+
+test_begin_subtest "Ensure uu data is not indexed"
+output=$(notmuch search DURINGUUDATA | notmuch_search_sanitize)
+test_expect_equal "$output" ""
+
+test_begin_subtest "Ensure content after uu data is indexed"
+output=$(notmuch search afteruudata | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; uuencodetest (inbox unread)"
+
+test_done
diff --git a/test/T260-thread-order.sh b/test/T260-thread-order.sh
new file mode 100755 (executable)
index 0000000..fea6127
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+test_description="threading when messages received out of order"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# Generate all single-root four message thread structures.  We'll use
+# this for multiple tests below.
+THREADS=$($NOTMUCH_PYTHON ${NOTMUCH_SRCDIR}/test/gen-threads.py 4)
+nthreads=$(wc -l <<< "$THREADS")
+
+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 "Messages with all parents get linked in all delivery orders"
+# 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} != None ]]; do
+            references="<m$parent@t$thread> $references"
+            pp=$parent
+            parent=${parents[$parent]}
+            # Avoid looping over broken input (if ever)
+            parents[$pp]="None"
+        done
+
+        generate_message \
+            [id]=m$n@t$thread [references]="'$references'" \
+            [subject]=p$thread [from]=m$n
+        thread=$((thread + 1))
+    done <<< "$THREADS"
+    notmuch new > /dev/null
+
+    output="$output
+$(notmuch search --sort=newest-first '*' | notmuch_search_sanitize)"
+
+    # 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
diff --git a/test/T270-author-order.sh b/test/T270-author-order.sh
new file mode 100755 (executable)
index 0000000..c28ecb0
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+test_description="author reordering;"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Adding parent message"
+generate_message [body]=findme [id]=new-parent-id [subject]=author-reorder-threadtest '[from]="User <user@example.com>"' '[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 "Adding initial child message"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="User1 <user1@example.com>"' '[date]="Sat, 01 Jan 2000 12:01:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Adding second child message"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="User2 <user2@example.com>"' '[date]="Sat, 01 Jan 2000 12:02:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Searching when all three messages match"
+output=$(notmuch search findme | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [3/3] User, User1, User2; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Searching when two messages match"
+output=$(notmuch search User1 or User2 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [2/3] User1, User2| User; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Searching when only one message matches"
+output=$(notmuch search User2 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/3] User2| User, User1; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Searching when only first message matches"
+output=$(notmuch search User | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/3] User| User1, User2; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Adding duplicate author"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="User1 <user1@example.com>"' '[date]="Sat, 01 Jan 2000 12:03:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Searching when all four messages match"
+output=$(notmuch search findme | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [4/4] User, User1, User2; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Adding non-monotonic child message"
+generate_message [body]=findme "[in-reply-to]=\<new-parent-id\>" [subject]=author-reorder-threadtest '[from]="User0 <user0@example.com>"' '[date]="Sat, 01 Jan 2000 11:00:00 -0000"'
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Searching non-monotonic messages (oldest-first)"
+output=$(notmuch search --sort=oldest-first findme | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [5/5] User0, User, User1, User2; author-reorder-threadtest (inbox unread)"
+
+test_begin_subtest "Searching non-monotonic messages (newest-first)"
+output=$(notmuch search --sort=newest-first findme | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [5/5] User0, User, User1, User2; author-reorder-threadtest (inbox unread)"
+
+test_done
diff --git a/test/T280-from-guessing.sh b/test/T280-from-guessing.sh
new file mode 100755 (executable)
index 0000000..b871823
--- /dev/null
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+test_description="From line heuristics (with multiple configured addresses)"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Magic from guessing (nothing to go on)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Envelope-to:)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (X-Original-To:)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Received: .. for ..)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
+       by mail.notmuchmail.org (some MTA) with ESMTP id 12345678
+       for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Received: domain)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
+       by mail.otherdomain.org (some MTA) with ESMTP id 12345678
+       Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@otherdomain.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (multiple Received: headers)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           "[header]=\"Received: from extraneous.example.com (extraneous.example.com [1.1.1.1])
+Received: from mail.example.com (mail.example.com [1.1.1.1])
+       by mail.otherdomain.org (some MTA) with ESMTP id 12345678
+       for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)
+Received: from extraneous.example.com (extraneous.example.com [1.1.1.1])\"" \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output="$(notmuch reply id:${gen_msg_id})"
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Testing From line heuristics (with single configured address)"
+sed -i -e "s/^other_email.*//" "${NOTMUCH_CONFIG}"
+test_expect_equal '' ''
+
+test_begin_subtest "Magic from guessing (nothing to go on)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Envelope-to:)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[header]="Envelope-To: test_suite_other@notmuchmail.org"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (X-Original-To:)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           '[header]="X-Original-To: test_suite_other@notmuchmail.org"' \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Received: .. for ..)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
+       by mail.notmuchmail.org (some MTA) with ESMTP id 12345678
+       for <test_suite_other@notmuchmail.org>; Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_begin_subtest "Magic from guessing (Received: domain)"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=mailinglist@notmuchmail.org \
+            [subject]=notmuch-reply-test \
+           "[header]=\"Received: from mail.example.com (mail.example.com [1.1.1.1])
+       by mail.otherdomain.org (some MTA) with ESMTP id 12345678
+       Sat, 10 Apr 2010 07:54:51 -0400 (EDT)\"" \
+           '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+           '[body]="from guessing test"'
+
+output=$(notmuch reply id:${gen_msg_id})
+test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>, mailinglist@notmuchmail.org
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> from guessing test"
+
+test_done
diff --git a/test/T290-long-id.sh b/test/T290-long-id.sh
new file mode 100755 (executable)
index 0000000..5e3879f
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+test_description="messages with ridiculously-long message IDs"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Referencing long ID before adding"
+generate_message '[subject]="Reference of ridiculously-long message ID"' \
+                "[references]=\<abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-\>"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Adding message with long ID"
+generate_message '[subject]="A ridiculously-long message ID"' \
+                "[id]=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Referencing long ID after adding"
+generate_message '[subject]="Reply to ridiculously-long message ID"' \
+                "[in-reply-to]=\<abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-\>"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Ensure all messages were threaded together"
+output=$(notmuch search 'subject:"a ridiculously-long message ID"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/3] Notmuch Test Suite; A ridiculously-long message ID (inbox unread)"
+
+test_done
diff --git a/test/T300-encoding.sh b/test/T300-encoding.sh
new file mode 100755 (executable)
index 0000000..1e9d2a3
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+test_description="encoding issues"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Message with text of unknown charset"
+add_message '[content-type]="text/plain; charset=unknown-8bit"' \
+           "[body]=irrelevant"
+output=$(notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize_all)
+test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Message with text of unknown charset
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+irrelevant
+\fpart}
+\fbody}
+\fmessage}"
+
+test_begin_subtest "Search for ISO-8859-2 encoded message"
+add_message '[content-type]="text/plain; charset=iso-8859-2"' \
+            '[content-transfer-encoding]=8bit' \
+            '[subject]="ISO-8859-2 encoded message"' \
+            "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
+output=$(notmuch search tučňáččí 2>&1 | notmuch_show_sanitize_all)
+test_expect_equal "$output" "thread:0000000000000002   2001-01-05 [1/1] Notmuch Test Suite; ISO-8859-2 encoded message (inbox unread)"
+
+test_begin_subtest "RFC 2047 encoded word with spaces"
+add_message '[subject]="=?utf-8?q?encoded word with spaces?="'
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000003   2001-01-05 [1/1] Notmuch Test Suite; encoded word with spaces (inbox unread)"
+
+test_begin_subtest "RFC 2047 encoded words back to back"
+add_message '[subject]="=?utf-8?q?encoded-words-back?==?utf-8?q?to-back?="'
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000004   2001-01-05 [1/1] Notmuch Test Suite; encoded-words-backto-back (inbox unread)"
+
+test_begin_subtest "RFC 2047 encoded words without space before or after"
+add_message '[subject]="=?utf-8?q?encoded?=word without=?utf-8?q?space?=" '
+output=$(notmuch search id:${gen_msg_id} 2>&1 | notmuch_show_sanitize)
+test_expect_equal "$output" "thread:0000000000000005   2001-01-05 [1/1] Notmuch Test Suite; encodedword withoutspace (inbox unread)"
+
+test_begin_subtest "Mislabeled Windows-1252 encoding"
+add_message '[content-type]="text/plain; charset=iso-8859-1"'                           \
+            "[body]=$'This text contains \x93Windows-1252\x94 character codes.'"
+cat <<EOF > EXPECTED
+\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox unread)
+Subject: Mislabeled Windows-1252 encoding
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+This text contains “Windows-1252” character codes.
+\fpart}
+\fbody}
+\fmessage}
+EOF
+notmuch show id:${gen_msg_id} 2>&1 | notmuch_show_sanitize_all > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh
new file mode 100755 (executable)
index 0000000..5935819
--- /dev/null
@@ -0,0 +1,1134 @@
+#!/usr/bin/env bash
+
+test_description="emacs interface"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
+
+add_email_corpus
+
+# syntax errors in test-lib.el cause mysterious failures
+test_begin_subtest "Syntax of emacs test library"
+test_expect_success "${TEST_EMACS} -Q --batch --load $NOTMUCH_SRCDIR/test/test-lib.el"
+
+test_begin_subtest "Basic notmuch-hello view in emacs"
+test_emacs '(notmuch-hello)
+           (test-output)'
+test_expect_equal_file $EXPECTED/notmuch-hello OUTPUT
+
+test_begin_subtest "Saved search with 0 results"
+test_emacs '(let ((notmuch-show-empty-saved-searches t)
+                 (notmuch-saved-searches
+                  '\''(("inbox" . "tag:inbox")
+                       ("unread" . "tag:unread")
+                       ("empty" . "tag:doesnotexist"))))
+             (notmuch-hello)
+             (test-output))'
+test_expect_equal_file $EXPECTED/notmuch-hello-with-empty OUTPUT
+
+test_begin_subtest "No saved searches displayed (all with 0 results)"
+test_emacs '(let ((notmuch-saved-searches
+                  '\''(("empty" . "tag:doesnotexist"))))
+             (notmuch-hello)
+             (test-output))'
+test_expect_equal_file $EXPECTED/notmuch-hello-no-saved-searches OUTPUT
+
+test_begin_subtest "Basic notmuch-search view in emacs"
+test_emacs '(notmuch-search "tag:inbox")
+           (notmuch-test-wait)
+           (test-output)'
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
+
+test_begin_subtest "Incremental parsing of search results"
+test_emacs "(ad-enable-advice 'notmuch-search-process-filter 'around 'pessimal)
+           (ad-activate 'notmuch-search-process-filter)
+           (notmuch-search \"tag:inbox\")
+           (notmuch-test-wait)
+           (ad-disable-advice 'notmuch-search-process-filter 'around 'pessimal)
+           (ad-activate 'notmuch-search-process-filter)
+           (test-output)"
+test_expect_equal_file $EXPECTED/notmuch-search-tag-inbox OUTPUT
+
+test_begin_subtest "Navigation of notmuch-hello to search results"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (test-output)'
+test_expect_equal_file $EXPECTED/notmuch-hello-view-inbox OUTPUT
+
+test_begin_subtest "Basic notmuch-show view in emacs"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(notmuch-show \"$maildir_storage_thread\")
+           (test-output)"
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
+
+test_begin_subtest "Basic notmuch-show view in emacs default indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 1))
+             (notmuch-show \"$maildir_storage_thread\")
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
+
+test_begin_subtest "Basic notmuch-show view in emacs without indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 0))
+             (notmuch-show \"$maildir_storage_thread\")
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage-without-indentation OUTPUT
+
+test_begin_subtest "Basic notmuch-show view in emacs with fourfold indentation"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(let ((notmuch-show-indent-messages-width 4))
+             (notmuch-show \"$maildir_storage_thread\")
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage-with-fourfold-indentation OUTPUT
+
+test_begin_subtest "notmuch-show for message with invalid From"
+test_subtest_broken_gmime_3
+add_message "[subject]=\"message-with-invalid-from\"" \
+           "[from]=\"\\\"Invalid \\\" From\\\" <test_suite@notmuchmail.org>\""
+thread=$(notmuch search --output=threads subject:message-with-invalid-from)
+test_emacs "(notmuch-show \"$thread\")
+           (test-output \"OUTPUT.raw\")"
+cat <<EOF >EXPECTED
+"Invalid " (2001-01-05) (inbox)
+Subject: message-with-invalid-from
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+This is just a test message (#1)
+EOF
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Navigation of notmuch-search to thread view"
+test_emacs '(notmuch-search "tag:inbox")
+           (notmuch-test-wait)
+           (goto-char (point-min))
+           (re-search-forward "Working with Maildir")
+           (notmuch-search-show-thread)
+           (notmuch-test-wait)
+           (test-output)'
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
+
+test_begin_subtest "Add tag from search view"
+os_x_darwin_thread=$(notmuch search --output=threads id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com)
+test_emacs "(notmuch-search \"$os_x_darwin_thread\")
+           (notmuch-test-wait)
+           (execute-kbd-macro \"+tag-from-search-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-search-view unread)"
+
+test_begin_subtest "Remove tag from search view"
+test_emacs "(notmuch-search \"$os_x_darwin_thread\")
+           (notmuch-test-wait)
+           (execute-kbd-macro \"-tag-from-search-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
+
+test_begin_subtest "Add tag (large query)"
+# We use a long query to force us into batch mode and use a funny tag
+# that requires escaping for batch tagging.
+test_emacs "(notmuch-tag (concat \"$os_x_darwin_thread\" \" or \" (mapconcat #'identity (make-list notmuch-tag-argument-limit \"x\") \"-\")) (list \"+tag-from-%-large-query\"))"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-%-large-query unread)"
+notmuch tag -tag-from-%-large-query $os_x_darwin_thread
+
+test_begin_subtest "notmuch-show: add single tag to single message"
+test_emacs "(notmuch-show \"$os_x_darwin_thread\")
+           (execute-kbd-macro \"+tag-from-show-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag-from-show-view unread)"
+
+test_begin_subtest "notmuch-show: remove single tag from single message"
+test_emacs "(notmuch-show \"$os_x_darwin_thread\")
+           (execute-kbd-macro \"-tag-from-show-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
+
+test_begin_subtest "notmuch-show: add multiple tags to single message"
+test_emacs "(notmuch-show \"$os_x_darwin_thread\")
+           (execute-kbd-macro \"+tag1-from-show-view +tag2-from-show-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox tag1-from-show-view tag2-from-show-view unread)"
+
+test_begin_subtest "notmuch-show: remove multiple tags from single message"
+test_emacs "(notmuch-show \"$os_x_darwin_thread\")
+           (execute-kbd-macro \"-tag1-from-show-view -tag2-from-show-view\")"
+output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
+
+test_begin_subtest "Message with .. in Message-Id:"
+add_message [id]=123..456@example '[subject]="Message with .. in Message-Id"'
+test_emacs '(notmuch-search "id:\"123..456@example\"")
+           (notmuch-test-wait)
+           (execute-kbd-macro "+search-add")
+           (execute-kbd-macro "+search-remove")
+           (execute-kbd-macro "-search-remove")
+           (notmuch-show "id:\"123..456@example\"")
+           (notmuch-test-wait)
+           (execute-kbd-macro "+show-add")
+           (execute-kbd-macro "+show-remove")
+           (execute-kbd-macro "-show-remove")'
+output=$(notmuch search 'id:"123..456@example"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Message with .. in Message-Id (inbox search-add show-add)"
+
+test_begin_subtest "Message with quote in Message-Id:"
+add_message '[id]="\"quote\"@example"' '[subject]="Message with quote in Message-Id"'
+test_emacs '(notmuch-search "subject:\"Message with quote\"")
+           (notmuch-test-wait)
+           (execute-kbd-macro "+search-add")
+            (notmuch-search-show-thread)
+           (notmuch-test-wait)
+           (execute-kbd-macro "+show-add")'
+output=$(notmuch search 'id:"""quote""@example"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Message with quote in Message-Id (inbox search-add show-add)"
+
+test_begin_subtest "Sending a message via (fake) SMTP"
+emacs_deliver_message \
+    'Testing message sent via SMTP' \
+    'This is a test that messages are sent via SMTP' \
+    '(message-goto-to)
+     (kill-whole-line)
+     (insert "To: user@example.com\n")'
+sed \
+    -e s',^Message-ID: <.*>$,Message-ID: <XXX>,' \
+    -e s',^\(Content-Type: text/plain\); charset=us-ascii$,\1,' < sent_message >OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: user@example.com
+Subject: Testing message sent via SMTP
+Date: 01 Jan 2000 12:00:00 -0000
+Message-ID: <XXX>
+MIME-Version: 1.0
+Content-Type: text/plain
+
+This is a test that messages are sent via SMTP
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Folding a long header when sending via (fake) SMTP"
+long_subject="This is a long subject `echo {1..1000}`"
+emacs_deliver_message \
+    "${long_subject}" \
+    'This is a test that long headers are folded when messages are sent via SMTP' \
+    '(message-goto-to)
+     (kill-whole-line)
+     (insert "To: user@example.com\n")'
+sed \
+    -e s',^Message-ID: <.*>$,Message-ID: <XXX>,' \
+    -e s',^\(Content-Type: text/plain\); charset=us-ascii$,\1,' < sent_message >OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: user@example.com
+Subject: This is a long subject 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
+ 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
+ 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
+ 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
+ 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
+ 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
+ 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
+ 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
+ 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
+ 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
+ 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
+ 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
+ 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
+ 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
+ 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
+ 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
+ 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
+ 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
+ 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
+ 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
+ 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
+ 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
+ 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
+ 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
+ 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
+ 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
+ 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
+ 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
+ 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
+ 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
+ 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
+ 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
+ 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
+ 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
+ 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
+ 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
+ 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
+ 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
+ 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
+ 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
+ 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
+ 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
+ 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
+ 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
+ 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
+ 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
+ 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
+ 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
+ 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
+ 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
+ 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
+ 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
+ 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
+ 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
+ 996 997 998 999 1000
+Date: 01 Jan 2000 12:00:00 -0000
+Message-ID: <XXX>
+MIME-Version: 1.0
+Content-Type: text/plain
+
+This is a test that long headers are folded when messages are sent via SMTP
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)"
+notmuch new > /dev/null
+output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; Testing message sent via SMTP (inbox)"
+
+test_begin_subtest "notmuch-fcc-dirs set to nil"
+test_emacs "(let ((notmuch-fcc-dirs nil))
+             (notmuch-mua-mail)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: 
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# Make another FCC maildir specific for the next test
+mkdir -p mail/sent-string/cur
+mkdir -p mail/sent-string/new
+mkdir -p mail/sent-string/tmp
+
+test_begin_subtest "notmuch-fcc-dirs set to a string"
+test_emacs "(let ((notmuch-fcc-dirs \"sent-string\"))
+             (notmuch-mua-mail)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: 
+Fcc: ${MAIL_DIR}/sent-string
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# Make more FCC maildirs specific for the next test
+mkdir -p mail/sent-list-match/cur
+mkdir -p mail/sent-list-match/new
+mkdir -p mail/sent-list-match/tmp
+mkdir -p mail/failure/cur
+mkdir -p mail/failure/new
+mkdir -p mail/failure/tmp
+
+test_begin_subtest "notmuch-fcc-dirs set to a list (with match)"
+test_emacs "(let ((notmuch-fcc-dirs
+                  '((\"notmuchmail.org\" . \"sent-list-match\")
+                    (\".*\" . \"failure\"))))
+             (notmuch-mua-mail)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: 
+Fcc: ${MAIL_DIR}/sent-list-match
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# Make another FCC maildir specific for the next test
+mkdir -p mail/sent-list-catch-all/cur
+mkdir -p mail/sent-list-catch-all/new
+mkdir -p mail/sent-list-catch-all/tmp
+
+test_begin_subtest "notmuch-fcc-dirs set to a list (catch-all)"
+test_emacs "(let ((notmuch-fcc-dirs
+                  '((\"example.com\" . \"failure\")
+                    (\".*\" . \"sent-list-catch-all\"))))
+             (notmuch-mua-mail)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: 
+Fcc: ${MAIL_DIR}/sent-list-catch-all
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch-fcc-dirs set to a list (no match)"
+test_emacs "(let ((notmuch-fcc-dirs
+                  '((\"example.com\" . \"failure\")
+                    (\"nomatchhere.net\" . \"failure\"))))
+             (notmuch-mua-mail)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: 
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs"
+test_emacs '(let ((message-hidden-headers ''()))
+           (notmuch-search "subject:\"testing message sent via SMTP\"")
+           (notmuch-test-wait)
+           (notmuch-search-reply-to-thread)
+           (test-output))'
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
+sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: user@example.com
+Subject: Re: Testing message sent via SMTP
+In-Reply-To: <XXX>
+Fcc: ${MAIL_DIR}/sent
+References: <XXX>
+--text follows this line--
+Notmuch Test Suite <test_suite@notmuchmail.org> writes:
+
+> This is a test that messages are sent via SMTP
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs to a message with TAB in subject"
+test_emacs '(let ((message-hidden-headers ''()))
+           (notmuch-search "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net")
+           (notmuch-test-wait)
+           (notmuch-search-show-thread)
+           (notmuch-test-wait)
+           (notmuch-show-reply-sender)
+           (test-output))'
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
+sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
+sed -i -e '/^--text follows this line--$/q' OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Subject: Re: [notmuch] [PATCH 1/2] Close message file after parsing message headers
+In-Reply-To: <XXX>
+Fcc: ${MAIL_DIR}/sent
+References: <XXX>
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply from alternate address within emacs"
+add_message '[from]="Sender <sender@example.com>"' \
+            [to]=test_suite_other@notmuchmail.org
+
+test_emacs "(let ((message-hidden-headers '()))
+           (notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+           (notmuch-test-wait)
+           (notmuch-search-reply-to-thread)
+           (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
+To: Sender <sender@example.com>
+Subject: Re: ${test_subtest_name}
+In-Reply-To: <${gen_msg_id}>
+Fcc: ${MAIL_DIR}/sent
+References: <${gen_msg_id}>
+--text follows this line--
+Sender <sender@example.com> writes:
+
+> This is just a test message (#${gen_msg_cnt})
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply from address in named group list within emacs"
+add_message '[from]="Sender <sender@example.com>"' \
+            '[to]=group:test_suite@notmuchmail.org,someone@example.com\;' \
+             [cc]=test_suite_other@notmuchmail.org
+
+test_emacs "(let ((message-hidden-headers '()))
+           (notmuch-search \"id:\\\"${gen_msg_id}\\\"\")
+           (notmuch-test-wait)
+           (notmuch-search-reply-to-thread)
+           (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Sender <sender@example.com>, someone@example.com
+Subject: Re: ${test_subtest_name}
+In-Reply-To: <${gen_msg_id}>
+Fcc: ${MAIL_DIR}/sent
+References: <${gen_msg_id}>
+--text follows this line--
+Sender <sender@example.com> writes:
+
+> This is just a test message (#${gen_msg_cnt})
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs to a multipart/mixed message"
+test_emacs '(let ((message-hidden-headers ''()))
+           (notmuch-show "id:20091118002059.067214ed@hikari")
+               (notmuch-show-reply)
+               (test-output))'
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Adrian Perez de Castro <aperez@igalia.com>, notmuch@notmuchmail.org
+Subject: Re: [notmuch] Introducing myself
+In-Reply-To: <20091118002059.067214ed@hikari>
+Fcc: ${MAIL_DIR}/sent
+References: <20091118002059.067214ed@hikari>
+--text follows this line--
+Adrian Perez de Castro <aperez@igalia.com> writes:
+
+> Hello to all,
+>
+> I have just heard about Not Much today in some random Linux-related news
+> site (LWN?), my name is Adrian Perez and I work as systems administrator
+> (although I can do some code as well :P). I have always thought that the
+> ideas behind Sup were great, but after some time using it, I got tired of
+> the oddities that it has. I also do not like doing things like having to
+> install Ruby just for reading and sorting mails. Some time ago I thought
+> about doing something like Not Much and in fact I played a bit with the
+> Python+Xapian and the Python+Whoosh combinations, because I find relaxing
+> to code things in Python when I am not working and also it is installed
+> by default on most distribution. I got to have some mailboxes indexed and
+> basic searching working a couple of months ago. Lately I have been very
+> busy and had no time for coding, and them... boom! Not Much appears -- and
+> it is almost exactly what I was trying to do, but faster. I have been
+> playing a bit with Not Much today, and I think it has potential.
+>
+> Also, I would like to share one idea I had in mind, that you might find
+> interesting: One thing I have found very annoying is having to re-tag my
+> mail when the indexes get b0rked (it happened a couple of times to me while
+> using Sup), so I was planning to mails as read/unread and adding the tags
+> not just to the index, but to the mail text itself, e.g. by adding a
+> "X-Tags" header field or by reusing the "Keywords" one. This way, the index
+> could be totally recreated by re-reading the mail directories, and this
+> would also allow to a tools like OfflineIMAP [1] to get the mails into a
+> local maildir, tagging and indexing the mails with the e-mail reader and
+> then syncing back the messages with the "X-Tags" header to the IMAP server.
+> This would allow to use the mail reader from a different computer and still
+> have everything tagged finely.
+>
+> Best regards,
+>
+>
+> ---
+> [1] http://software.complete.org/software/projects/show/offlineimap
+>
+> -- 
+> Adrian Perez de Castro <aperez@igalia.com>
+> Igalia - Free Software Engineering
+> _______________________________________________
+> notmuch mailing list
+> notmuch@notmuchmail.org
+> http://notmuchmail.org/mailman/listinfo/notmuch
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs to a multipart/alternative message"
+test_emacs '(let ((message-hidden-headers ''()))
+           (notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com")
+               (notmuch-show-reply)
+               (test-output))'
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Alex Botero-Lowry <alex.boterolowry@gmail.com>, notmuch@notmuchmail.org
+Subject: Re: [notmuch] preliminary FreeBSD support
+In-Reply-To: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+Fcc: ${MAIL_DIR}/sent
+References: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+--text follows this line--
+Alex Botero-Lowry <alex.boterolowry@gmail.com> writes:
+
+> I saw the announcement this morning, and was very excited, as I had been
+> hoping sup would be turned into a library,
+> since I like the concept more than the UI (I'd rather an emacs interface).
+>
+> I did a preliminary compile which worked out fine, but
+> sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on
+> FreeBSD, so notmuch_config_open segfaulted.
+>
+> Attached is a patch that supplies a default buffer size of 64 in cases where
+> -1 is returned.
+>
+> http://www.opengroup.org/austin/docs/austin_328.txt - seems to indicate this
+> is acceptable behavior,
+> and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically
+> uses 64 as the
+> buffer size.
+> From e3bc4bbd7b9d0d086816ab5f8f2d6ffea1dd3ea4 Mon Sep 17 00:00:00 2001
+> From: Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+> Date: Tue, 17 Nov 2009 11:30:39 -0800
+> Subject: [PATCH] Deal with situation where sysconf(_SC_GETPW_R_SIZE_MAX) returns -1
+>
+> ---
+>  notmuch-config.c |    2 ++
+>  1 files changed, 2 insertions(+), 0 deletions(-)
+>
+> diff --git a/notmuch-config.c b/notmuch-config.c
+> index 248149c..e7220d8 100644
+> --- a/notmuch-config.c
+> +++ b/notmuch-config.c
+> @@ -77,6 +77,7 @@ static char *
+>  get_name_from_passwd_file (void *ctx)
+>  {
+>      long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+> +    if (pw_buf_size == -1) pw_buf_size = 64;
+>      char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+>      struct passwd passwd, *ignored;
+>      char *name;
+> @@ -101,6 +102,7 @@ static char *
+>  get_username_from_passwd_file (void *ctx)
+>  {
+>      long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+> +    if (pw_buf_size == -1) pw_buf_size = 64;
+>      char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+>      struct passwd passwd, *ignored;
+>      char *name;
+> -- 
+> 1.6.5.2
+>
+> _______________________________________________
+> notmuch mailing list
+> notmuch@notmuchmail.org
+> http://notmuchmail.org/mailman/listinfo/notmuch
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs to an html-only message"
+add_message '[content-type]="text/html"' \
+           '[body]="Hi,<br />This is an <b>HTML</b> test message.<br /><br />OK?"'
+test_emacs "(let ((message-hidden-headers '()))
+           (notmuch-show \"id:${gen_msg_id}\")
+           (notmuch-show-reply)
+           (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: Re: Reply within emacs to an html-only message
+In-Reply-To: <${gen_msg_id}>
+Fcc: ${MAIL_DIR}/sent
+References: <${gen_msg_id}>
+--text follows this line--
+Notmuch Test Suite <test_suite@notmuchmail.org> writes:
+
+> Hi,This is an HTML test message.OK?
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Reply within emacs to message from self"
+test_subtest_known_broken
+add_message '[from]="test_suite@notmuchmail.org"' \
+           '[to]="test_suite@notmuchmail.org"'
+test_emacs "(let ((message-hidden-headers '()))
+           (notmuch-show \"id:${gen_msg_id}\")
+           (notmuch-show-reply)
+           (test-output))"
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
+sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: test_suite@notmuchmail.org
+Subject: Re: Reply within emacs to message from self
+In-Reply-To: <XXX>
+Fcc: ${MAIL_DIR}/sent
+References: <XXX>
+--text follows this line--
+test_suite@notmuchmail.org writes:
+
+> This is just a test message (#7)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Quote MML tags in reply"
+message_id='test-emacs-mml-quoting@message.id'
+add_message [id]="$message_id" \
+           "[subject]='$test_subtest_name'" \
+           '[body]="<#part disposition=inline>"'
+test_emacs "(let ((message-hidden-headers '()))
+             (notmuch-show \"id:$message_id\")
+             (notmuch-show-reply)
+             (test-output))"
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: 
+Subject: Re: Quote MML tags in reply
+In-Reply-To: <test-emacs-mml-quoting@message.id>
+Fcc: ${MAIL_DIR}/sent
+References: <test-emacs-mml-quoting@message.id>
+--text follows this line--
+Notmuch Test Suite <test_suite@notmuchmail.org> writes:
+
+> <#!part disposition=inline>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Save attachment from within emacs using notmuch-show-save-attachments"
+# save as archive to test that Emacs does not re-compress .gz
+test_emacs '(let ((standard-input "\"attachment1.gz\""))
+             (notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com")
+             (notmuch-show-save-attachments))'
+test_expect_equal_file attachment1.gz "$EXPECTED/attachment"
+
+test_begin_subtest "Save attachment from within emacs using notmuch-show-save-part"
+# save as archive to test that Emacs does not re-compress .gz
+test_emacs '(let ((standard-input "\"attachment2.gz\""))
+             (notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com")
+             (search-forward "0001-Deal-with")
+             (notmuch-show-save-part))'
+test_expect_equal_file attachment2.gz "$EXPECTED/attachment"
+
+test_begin_subtest "Save 8bit attachment from within emacs using notmuch-show-save-attachments"
+
+add_message '[subject]="Attachment with 8bit chars"' \
+       '[header]="MIME-Version: 1.0"' \
+       '[content-type]="multipart/mixed; boundary=\"abcd\""' \
+       '[body]="--abcd
+Content-Type: text/plain
+
+Attachment follows:
+
+--abcd
+Content-Type: application/octet-stream; name=\"sample\"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: attachment; filename=\"sample\"
+
+“¡ Hey ! It compiles ¡ Ship it !”
+
+--abcd--
+"'
+test_emacs '(notmuch-show "id:'"${gen_msg_id}"'")
+           (delete-file "OUTPUT")
+           (let ((standard-input "\"OUTPUT\""))
+             (notmuch-show-save-attachments))'
+
+test_expect_equal "$(cat OUTPUT)" '“¡ Hey ! It compiles ¡ Ship it !”'
+
+test_begin_subtest "View raw message within emacs"
+test_emacs '(notmuch-show "id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com")
+           (notmuch-show-view-raw-message)
+           (test-output)'
+test_expect_equal_file $EXPECTED/raw-message-cf0c4d-52ad0a OUTPUT
+
+test_begin_subtest "Hiding/showing signature in notmuch-show view"
+maildir_storage_thread=$(notmuch search --output=threads id:20091117190054.GU3165@dottiness.seas.harvard.edu)
+test_emacs "(notmuch-show \"$maildir_storage_thread\")
+           (search-forward \"Click/Enter to show.\")
+           (button-activate (button-at (point)))
+           (search-backward \"Click/Enter to hide.\")
+           (button-activate (button-at (point)))
+           (test-output)"
+test_expect_equal_file $EXPECTED/notmuch-show-thread-maildir-storage OUTPUT
+
+test_begin_subtest "Detection and hiding of top-post quoting of message"
+add_message '[subject]="The problem with top-posting"' \
+           [id]=top-post-target \
+           '[body]="A: Because it messes up the order in which people normally read text.
+Q: Why is top-posting such a bad thing?
+A: Top-posting.
+Q: What is the most annoying thing in e-mail?"'
+add_message '[from]="Top Poster <top@poster.com>"' \
+           [in-reply-to]=top-post-target \
+           [references]=top-post-target \
+           '[subject]="Re: The problem with top-posting"' \
+           '[body]="Thanks for the advice! I will be sure to put it to good use.
+
+-Top Poster
+
+----- Original Message -----
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Notmuch Test Suite <test_suite@notmuchmai.org>
+Sent: Fri, 05 Jan 2001 15:43:57 +0000
+Subject: The problem with top-posting
+
+Q: Why is top-posting such a bad thing?
+A: Top-posting.
+Q: What is the most annoying thing in e-mail?"'
+test_emacs "(notmuch-show \"top-posting\")
+           (test-visible-output \"OUTPUT.raw\")"
+echo "Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
+Subject: The problem with top-posting
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+A: Because it messes up the order in which people normally read text.
+Q: Why is top-posting such a bad thing?
+A: Top-posting.
+Q: What is the most annoying thing in e-mail?
+Top Poster <top@poster.com> (2001-01-05) (inbox unread)
+Subject: Re: The problem with top-posting
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+Thanks for the advice! I will be sure to put it to good use.
+
+-Top Poster
+
+[ 9-line hidden original message. Click/Enter to show. ]" > EXPECTED
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Hiding message in notmuch-show view"
+test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+           (notmuch-show-toggle-message)
+           (test-visible-output)'
+test_expect_equal_file $EXPECTED/notmuch-show-thread-with-hidden-messages OUTPUT
+
+test_begin_subtest "Hiding message with visible citation in notmuch-show view"
+test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+           (search-forward "Click/Enter to show.")
+           (button-activate (button-at (point)))
+           (notmuch-show-toggle-message)
+           (test-visible-output)'
+test_expect_equal_file $EXPECTED/notmuch-show-thread-with-hidden-messages OUTPUT
+
+test_begin_subtest "notmuch-show: show message headers"
+test_emacs \
+       '(let ((notmuch-message-headers '\''("Subject" "To" "Cc" "Date"))
+              (notmuch-message-headers-visible t))
+          (notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+          (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-message-with-headers-visible OUTPUT
+
+test_begin_subtest "notmuch-show: hide message headers"
+test_emacs \
+       '(let ((notmuch-message-headers '\''("Subject" "To" "Cc" "Date"))
+              (notmuch-message-headers-visible nil))
+          (notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+          (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-message-with-headers-hidden OUTPUT
+
+test_begin_subtest "notmuch-show: hide message headers (w/ notmuch-show-toggle-visibility-headers)"
+test_emacs \
+       '(let ((notmuch-message-headers '\''("Subject" "To" "Cc" "Date"))
+              (notmuch-message-headers-visible t))
+          (notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+          (notmuch-show-toggle-visibility-headers)
+          (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-message-with-headers-hidden OUTPUT
+
+test_begin_subtest "notmuch-show: collapse all messages in thread"
+test_emacs '(notmuch-show "id:f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com")
+       (let ((current-prefix-arg t))
+         (notmuch-show-open-or-close-all)
+         (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-thread-with-all-messages-collapsed OUTPUT
+
+test_begin_subtest "notmuch-show: uncollapse all messages in thread"
+test_emacs '(notmuch-show "id:f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com")
+       (notmuch-show-open-or-close-all)
+       (test-visible-output)'
+test_expect_equal_file $EXPECTED/notmuch-show-thread-with-all-messages-uncollapsed OUTPUT
+
+test_begin_subtest "Stashing in notmuch-show"
+add_message '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \
+    '[from]="Some One <someone@somewhere.org>"' \
+    '[to]="Some One Else <notsomeone@somewhere.org>"' \
+    '[cc]="Notmuch <notmuch@notmuchmail.org>"' \
+    '[subject]="Stash my stashables"' \
+    '[id]="bought"' \
+    '[body]="Unable to stash body. Where did you get it in the first place?!?"'
+notmuch tag +stashtest id:${gen_msg_id}
+test_emacs '(notmuch-show "id:\"bought\"")
+       (notmuch-show-stash-date)
+       (notmuch-show-stash-from)
+       (notmuch-show-stash-to)
+       (notmuch-show-stash-cc)
+       (notmuch-show-stash-subject)
+       (notmuch-show-stash-message-id)
+       (notmuch-show-stash-message-id-stripped)
+       (notmuch-show-stash-tags)
+       (notmuch-show-stash-filename)
+       (notmuch-show-stash-mlarchive-link "Gmane")
+       (notmuch-show-stash-mlarchive-link "MARC")
+       (notmuch-show-stash-mlarchive-link "Mail Archive, The")
+       (switch-to-buffer
+         (generate-new-buffer "*test-stashing*"))
+       (dotimes (i 12)
+         (yank)
+         (insert "\n")
+         (rotate-yank-pointer 1))
+       (reverse-region (point-min) (point-max))
+           (test-output)'
+cat <<EOF >EXPECTED
+Sat, 01 Jan 2000 12:00:00 +0000
+Some One <someone@somewhere.org>
+Some One Else <notsomeone@somewhere.org>
+Notmuch <notmuch@notmuchmail.org>
+Stash my stashables
+id:bought
+bought
+inbox,stashtest
+${gen_msg_filename}
+https://mid.gmane.org/bought
+https://marc.info/?i=bought
+https://mid.mail-archive.com/bought
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Stashing in notmuch-search"
+test_emacs '(notmuch-search "id:\"bought\"")
+       (notmuch-test-wait)
+       (notmuch-search-stash-thread-id)
+       (switch-to-buffer
+         (generate-new-buffer "*test-stashing*"))
+       (yank)
+           (test-output)'
+sed -i -e 's/^thread:.*$/thread:XXX/' OUTPUT
+test_expect_equal "$(cat OUTPUT)" "thread:XXX"
+
+test_begin_subtest 'notmuch-show-advance-and-archive with invisible signature'
+message1='id:20091118010116.GC25380@dottiness.seas.harvard.edu'
+message2='id:1258491078-29658-1-git-send-email-dottedmag@dottedmag.net'
+test_emacs "(notmuch-show \"$message2\")
+           (test-output \"EXPECTED\")"
+test_emacs "(notmuch-search \"$message1 or $message2\")
+           (notmuch-test-wait)
+           (notmuch-search-show-thread)
+           (goto-char (point-max))
+           (redisplay)
+           (notmuch-show-advance-and-archive)
+           (test-output)"
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Refresh show buffer"
+test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+           (test-visible-output "EXPECTED")
+           (notmuch-show-refresh-view)
+           (test-visible-output)'
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Refresh modified show buffer"
+test_emacs '(notmuch-show "id:f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com")
+           (notmuch-show-toggle-message)
+           (notmuch-show-next-message)
+           (notmuch-show-toggle-message)
+           (test-visible-output "EXPECTED")
+           (notmuch-show-refresh-view)
+           (test-visible-output)'
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Do not call notmuch for non-inlinable application/mpeg parts"
+id='message-with-application/mpeg-attachment@notmuchmail.org'
+emacs_fcc_message \
+    'Message with application/mpeg attachment' \
+    '' \
+    "(message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")
+     (message-goto-body)
+     (mml-insert-part \"application/mpeg\")
+     (insert \"a fake mp3 file\")"
+notmuch_counter_reset
+test_emacs "(let ((notmuch-command \"$notmuch_counter_command\"))
+             (notmuch-show \"id:$id\"))"
+test_expect_equal $(notmuch_counter_value) 1
+
+test_begin_subtest "Do not call notmuch for non-inlinable audio/mpeg parts"
+id='message-with-audio/mpeg-attachment@notmuchmail.org'
+emacs_fcc_message \
+    'Message with audio/mpeg attachment' \
+    '' \
+    "(message-goto-eoh)
+     (insert \"Message-ID: <$id>\n\")
+     (message-goto-body)
+     (mml-insert-part \"audio/mpeg\")
+     (insert \"a fake mp3 file\")"
+notmuch_counter_reset
+test_emacs "(let ((notmuch-command \"$notmuch_counter_command\"))
+             (notmuch-show \"id:$id\"))"
+test_expect_equal $(notmuch_counter_value) 1
+
+test_begin_subtest "notmuch-hello-mode hook is called"
+counter=$(test_emacs \
+    '(let ((notmuch-hello-mode-hook-counter 0))
+       (kill-buffer "*notmuch-hello*")
+       (notmuch-hello)
+       notmuch-hello-mode-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-mode hook is not called on updates"
+counter=$(test_emacs \
+    '(let ((notmuch-hello-mode-hook-counter 0))
+       (kill-buffer "*notmuch-hello*")
+       (notmuch-hello)
+       (notmuch-hello-update)
+       notmuch-hello-mode-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-refresh hook is called"
+counter=$(test_emacs \
+    '(let ((notmuch-hello-refresh-hook-counter 0))
+       (kill-buffer "*notmuch-hello*")
+       (notmuch-hello)
+       notmuch-hello-refresh-hook-counter)'
+)
+test_expect_equal "$counter" 1
+
+test_begin_subtest "notmuch-hello-refresh hook is called on updates"
+counter=$(test_emacs \
+    '(let ((notmuch-hello-refresh-hook-counter 0))
+       (kill-buffer "*notmuch-hello*")
+       (notmuch-hello)
+       (notmuch-hello-update)
+       notmuch-hello-refresh-hook-counter)'
+)
+test_expect_equal "$counter" 2
+
+
+add_message '[subject]="HTML mail with images"' \
+    '[content-type]="multipart/related; boundary=abcd"' \
+    '[body]="--abcd
+Content-Type: text/html
+
+<img src="cid:330@goomoji.gmail"> smiley
+
+--abcd
+Content-Type: image/gif
+Content-Transfer-Encoding: base64
+Content-ID: <330@goomoji.gmail>
+
+R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMl
+WLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7
+--abcd--"'
+test_emacs "(let ((mm-text-html-renderer
+                  (if (assq 'shr mm-text-html-renderer-alist)
+                      'shr 'html2text)))
+             (notmuch-show \"id:${gen_msg_id}\"))
+           (test-output)" > /dev/null
+# Different Emacs versions and renderers give very different results,
+# so just check that something reasonable showed up.  We first cat the
+# output so the test framework will print it if the test fails.
+test_begin_subtest "Rendering HTML mail with images"
+test_expect_success 'cat OUTPUT && grep -q smiley OUTPUT'
+
+test_begin_subtest "Search handles subprocess error exit codes"
+cat > notmuch_fail <<EOF
+#!/bin/sh
+echo '()'
+exit 1
+EOF
+chmod a+x notmuch_fail
+test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
+              (with-current-buffer \"*Messages*\"
+                 (let ((inhibit-read-only t)) (erase-buffer)))
+              (with-current-buffer (get-buffer-create \"*Notmuch errors*\")
+                 (erase-buffer))
+              (notmuch-search \"tag:inbox\")
+              (notmuch-test-wait)
+              (with-current-buffer \"*Messages*\"
+                 (test-output \"MESSAGES\"))
+              (with-current-buffer \"*Notmuch errors*\"
+                 (test-output \"ERROR\"))
+              (test-output))"
+
+test_expect_equal "$(notmuch_emacs_error_sanitize notmuch_fail OUTPUT MESSAGES ERROR)" "\
+=== OUTPUT ===
+End of search results.
+=== MESSAGES ===
+YYY/notmuch_fail exited with status 1 (see *Notmuch errors* for more details)
+=== ERROR ===
+[XXX]
+YYY/notmuch_fail exited with status 1
+command: YYY/notmuch_fail search --format\=sexp --format-version\=4 --sort\=newest-first tag\:inbox
+exit status: 1"
+
+test_begin_subtest "Search handles subprocess warnings"
+cat > notmuch_fail <<EOF
+#!/bin/sh
+echo '()'
+echo This is a warning >&2
+echo This is another warning >&2
+exit 0
+EOF
+chmod a+x notmuch_fail
+test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
+              (with-current-buffer \"*Messages*\"
+                 (let ((inhibit-read-only t)) (erase-buffer)))
+              (with-current-buffer (get-buffer-create \"*Notmuch errors*\")
+                 (erase-buffer))
+              (notmuch-search \"tag:inbox\")
+              (notmuch-test-wait)
+              (with-current-buffer \"*Messages*\"
+                 (test-output \"MESSAGES\"))
+              (with-current-buffer \"*Notmuch errors*\"
+                 (test-output \"ERROR\"))
+              (test-output))"
+sed -i -e 's/^\[.*\]$/[XXX]/' ERROR
+test_expect_equal "$(cat OUTPUT; echo ---; cat MESSAGES; echo ---; cat ERROR)" "\
+End of search results.
+---
+This is a warning (see *Notmuch errors* for more details)
+---
+[XXX]
+This is a warning
+This is another warning"
+
+test_begin_subtest "Search thread tag operations are race-free"
+add_message '[subject]="Search race test"'
+gen_msg_id_1=$gen_msg_id
+generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
+           '[references]="<'$gen_msg_id_1'>"' \
+           '[subject]="Search race test two"'
+test_emacs '(notmuch-search "subject:\"search race test\"")
+           (notmuch-test-wait)
+           (notmuch-poll)
+           (execute-kbd-macro "+search-thread-race-tag")'
+output=$(notmuch search --output=messages 'tag:search-thread-race-tag')
+test_expect_equal "$output" "id:$gen_msg_id_1"
+
+test_begin_subtest "Search global tag operations are race-free"
+generate_message '[in-reply-to]="<'$gen_msg_id_1'>"' \
+           '[references]="<'$gen_msg_id_1'>"' \
+           '[subject]="Re: Search race test"'
+test_emacs '(notmuch-search "subject:\"search race test\" -subject:two")
+           (notmuch-test-wait)
+           (notmuch-poll)
+           (execute-kbd-macro "*+search-global-race-tag")'
+output=$(notmuch search --output=messages 'tag:search-global-race-tag')
+test_expect_equal "$output" "id:$gen_msg_id_1"
+
+test_begin_subtest "Term escaping"
+output=$(test_emacs "(mapcar 'notmuch-escape-boolean-term (list
+       \"\"
+       \"abc\`~\!@#\$%^&*-=_+123\"
+       \"(abc\"
+       \")abc\"
+       \"\\\"abc\"
+       \"\x01xyz\"
+       \"\\x201cxyz\\x201d\"))")
+test_expect_equal "$output" '("\"\"" "abc`~!@#$%^&*-=_+123" "\"(abc\"" "\")abc\"" "\"\"\"abc\"" "\"'$'\x01''xyz\"" "\"“xyz”\"")'
+
+test_begin_subtest "Sending a message calls the send message hooks"
+emacs_deliver_message \
+    'Testing message sending hooks' \
+    'This is a test of the message sending hooks.' \
+    "(message-goto-to)
+     (kill-whole-line)
+     (insert \"To: user@example.com\n\")
+     (add-hook 'notmuch-mua-send-hook (lambda () (goto-char (point-max)) (insert \"\nThis text added by the hook.\")))"
+sed \
+    -e s',^Message-ID: <.*>$,Message-ID: <XXX>,' \
+    -e s',^\(Content-Type: text/plain\); charset=us-ascii$,\1,' < sent_message >OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: user@example.com
+Subject: Testing message sending hooks
+Date: 01 Jan 2000 12:00:00 -0000
+Message-ID: <XXX>
+MIME-Version: 1.0
+Content-Type: text/plain
+
+This is a test of the message sending hooks.
+This text added by the hook.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T320-emacs-large-search-buffer.sh b/test/T320-emacs-large-search-buffer.sh
new file mode 100755 (executable)
index 0000000..f61e8a9
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+test_description="Emacs with large search results buffer"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+x=xxxxxxxxxx # 10
+x=$x$x$x$x$x$x$x$x$x$x # 100
+x=$x$x$x$x$x$x$x$x$x # 900
+
+# We generate a long subject here (over 900 bytes) so that the emacs
+# search results get large quickly. With 30 such messages we should
+# cross several 4kB page boundaries and see the bug.
+n=30
+for i in $(seq 1 $n); do
+  # Roughly 100B2 KiB per message.  That is, we need two messages in order to
+  # exceed the typical size of the pipe buffer (4 KiB on commodity systems).
+  generate_message '[subject]="$x $i of $n"'
+done
+
+notmuch new > /dev/null
+
+test_begin_subtest "Ensure that emacs doesn't drop results"
+notmuch search '*' > EXPECTED
+sed -i -e 's/^thread:[0-9a-f]*  //' -e 's/;//' -e 's/xx*/[BLOB]/' EXPECTED
+echo 'End of search results.' >> EXPECTED
+
+test_emacs '(notmuch-search "*")
+           (notmuch-test-wait)
+           (test-output)'
+sed -i -e s',  *, ,g' -e 's/xxx*/[BLOB]/g' OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T330-emacs-subject-to-filename.sh b/test/T330-emacs-subject-to-filename.sh
new file mode 100755 (executable)
index 0000000..eaf7c98
--- /dev/null
@@ -0,0 +1,138 @@
+#!/usr/bin/env bash
+
+test_description="emacs: mail subject to filename"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# emacs server can't be started in a child process with $(test_emacs ...)
+test_emacs '(ignore)' > /dev/null
+
+# test notmuch-wash-subject-to-patch-sequence-number (subject)
+test_begin_subtest "no patch sequence number"
+output=$(test_emacs '(format "%S" (notmuch-wash-subject-to-patch-sequence-number
+      "[PATCH] A normal patch subject without numbers"))'
+)
+test_expect_equal "$output" '"nil"'
+
+test_begin_subtest "patch sequence number #1"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[PATCH 2/3] A most regular patch subject")'
+)
+test_expect_equal "$output" 2
+
+test_begin_subtest "patch sequence number #2"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "  [dummy list prefix]  [RFC PATCH v2 13/42]  Special prefixes")'
+)
+test_expect_equal "$output" 13
+
+test_begin_subtest "patch sequence number #3"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[PATCH 2/3] [PATCH 032/037] use the last prefix")'
+)
+test_expect_equal "$output" 32
+
+test_begin_subtest "patch sequence number #4"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[dummy list prefix] [PATCH 2/3] PATCH 3/3] do not use a broken prefix")'
+)
+test_expect_equal "$output" 2
+
+test_begin_subtest "patch sequence number #5"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[RFC][PATCH 3/5][PATCH 4/5][PATCH 5/5] A made up test")'
+)
+test_expect_equal "$output" 5
+
+test_begin_subtest "patch sequence number #6"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[PATCH 2/3] this -> [PATCH 3/3] is not a prefix anymore [nor this 4/4]")'
+)
+test_expect_equal "$output" 2
+
+test_begin_subtest "patch sequence number #7"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-sequence-number
+      "[liberally accept crapola right before123/456and after] the numbers")'
+)
+test_expect_equal "$output" 123
+
+# test notmuch-wash-subject-to-filename (subject &optional maxlen)
+test_begin_subtest "filename #1"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "just a subject line")'
+)
+test_expect_equal "$output" '"just-a-subject-line"'
+
+test_begin_subtest "filename #2"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      " [any]  [prefixes are ] [removed!] from the subject")'
+)
+test_expect_equal "$output" '"from-the-subject"'
+
+test_begin_subtest "filename #3"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "  leading and trailing space  ")'
+)
+test_expect_equal "$output" '"leading-and-trailing-space"'
+
+test_begin_subtest "filename #4"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "!#  leading ()// &%, and in between_and_trailing garbage ()(&%%")'
+)
+test_expect_equal "$output" '"-leading-and-in-between_and_trailing-garbage"'
+
+test_begin_subtest "filename #5"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_01234567890")'
+)
+test_expect_equal "$output" '"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_01234567890"'
+
+test_begin_subtest "filename #6"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "sequences of ... are squashed and trailing are removed ...")'
+)
+test_expect_equal "$output" '"sequences-of-.-are-squashed-and-trailing-are-removed"'
+
+test_begin_subtest "filename #7"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "max length test" 1)'
+)
+test_expect_equal "$output" '"m"'
+
+test_begin_subtest "filename #8"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "max length test /&(/%&/%%&¤%¤" 20)'
+)
+test_expect_equal "$output" '"max-length-test"'
+
+test_begin_subtest "filename #9"
+output=$(test_emacs '(notmuch-wash-subject-to-filename
+      "[a prefix] [is only separated] by [spaces], so \"by\" is not okay!")'
+)
+test_expect_equal "$output" '"by-spaces-so-by-is-not-okay"'
+
+# test notmuch-wash-subject-to-patch-filename (subject)
+test_begin_subtest "patch filename #1"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-filename
+      "[RFC][PATCH 099/100] rewrite notmuch")'
+)
+test_expect_equal "$output" '"0099-rewrite-notmuch.patch"'
+
+test_begin_subtest "patch filename #2"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-filename
+      "[RFC PATCH v1] has no patch number, default to 1")'
+)
+test_expect_equal "$output" '"0001-has-no-patch-number-default-to-1.patch"'
+
+test_begin_subtest "patch filename #3"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-filename
+      "[PATCH 4/5] the maximum length of a patch filename is 52 + patch sequence number + .patch extension")'
+)
+test_expect_equal "$output" '"0004-the-maximum-length-of-a-patch-filename-is-52-patch-s.patch"'
+
+test_begin_subtest "patch filename #4"
+output=$(test_emacs '(notmuch-wash-subject-to-patch-filename
+      "[PATCH 4/5] the maximum length of a patch filename is 52 + patchh ! sequence number + .patch extension, *before* trimming trailing - and .")'
+)
+test_expect_equal "$output" '"0004-the-maximum-length-of-a-patch-filename-is-52-patchh.patch"'
+
+test_done
diff --git a/test/T340-maildir-sync.sh b/test/T340-maildir-sync.sh
new file mode 100755 (executable)
index 0000000..7fece5f
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env bash
+
+test_description="maildir synchronization"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# Create the expected maildir structure
+mkdir $MAIL_DIR/cur
+mkdir $MAIL_DIR/new
+mkdir $MAIL_DIR/tmp
+
+test_begin_subtest "Adding 'S' flag to existing filename removes 'unread' tag"
+add_message [subject]='"Adding S flag"' [filename]='adding-s-flag:2,' [dir]=cur
+output=$(notmuch search subject:"Adding S flag" | notmuch_search_sanitize)
+output+="
+"
+mv "${gen_msg_filename}" "${gen_msg_filename}S"
+output+=$(NOTMUCH_NEW)
+output+="
+"
+output+=$(notmuch search subject:"Adding S flag" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding S flag (inbox unread)
+No new mail. Detected 1 file rename.
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding S flag (inbox)"
+
+test_begin_subtest "Adding message with 'S' flag prevents 'unread' tag"
+add_message [subject]='"Adding message with S"' [filename]='adding-with-s-flag:2,S' [dir]=cur
+output=$(notmuch search subject:"Adding message with S" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding message with S (inbox)"
+
+test_begin_subtest "Adding message with 'S' w/o 'unread' in new.tags prevents 'unread' tag"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags "inbox"
+add_message [subject]='"Adding message with S 2"' [filename]='adding-with-s-flag2:2,S' [dir]=cur
+notmuch config set new.tags $OLDCONFIG
+output=$(notmuch search subject:Adding-message-with-S-2 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Adding message with S 2 (inbox)"
+
+test_begin_subtest "Adding 'replied' tag adds 'R' flag to filename"
+add_message [subject]='"Adding replied tag"' [filename]='adding-replied-tag:2,S' [dir]=cur
+notmuch tag +replied subject:"Adding replied tag"
+output=$(cd ${MAIL_DIR}/cur; ls -1 adding-replied*)
+test_expect_equal "$output" "adding-replied-tag:2,RS"
+
+test_begin_subtest "notmuch show works with renamed file (without notmuch new)"
+output=$(notmuch show --format=json id:${gen_msg_id} | notmuch_json_show_sanitize)
+test_expect_equal_json "$output" '[[[{"id": "XXXXX",
+"match": true,
+"excluded": false,
+"filename": ["YYYYY"],
+"timestamp": 42,
+"date_relative": "2001-01-05",
+"tags": ["inbox","replied"],
+"headers": {"Subject": "Adding replied tag",
+"From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+"To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+"Date": "GENERATED_DATE"},
+"body": [{"id": 1,
+"content-type": "text/plain",
+"content": "This is just a test message (#4)\n"}]},
+[]]]]'
+
+test_begin_subtest "notmuch reply works with renamed file (without notmuch new)"
+test_expect_success 'notmuch reply id:${gen_msg_id}'
+
+test_begin_subtest "notmuch new detects no file rename after tag->flag synchronization"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "No new mail."
+
+test_begin_subtest "When read, message moved from new to cur"
+add_message [subject]='"Message to move to cur"' [date]='"Sat, 01 Jan 2000 12:00:00 -0000"' [filename]='message-to-move-to-cur' [dir]=new
+notmuch tag -unread subject:"Message to move to cur"
+output=$(cd "$MAIL_DIR/cur"; ls message-to-move*)
+test_expect_equal "$output" "message-to-move-to-cur:2,S"
+
+test_begin_subtest "No rename should be detected by notmuch new"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "No new mail."
+# (*) If notmuch new was not run we've got "Processed 1 file in almost
+# no time" here. The reason is that removing unread tag in a previous
+# test created directory document in the database but this document
+# was not linked as subdirectory of $MAIL_DIR. Therefore notmuch new
+# could not reach the cur/ directory and its files in it during
+# recursive traversal.
+#
+# XXX: The above sounds like a bug that should be fixed. If notmuch is
+# creating new directories in the mail store, then it should be
+# creating all necessary database state for those directories.
+
+test_begin_subtest "Adding non-maildir tags does not move message from new to cur"
+add_message [subject]='"Message to stay in new"' \
+    [date]='"Sat, 01 Jan 2000 12:00:00 -0000"' \
+    [filename]='message-to-stay-in-new' [dir]=new
+notmuch tag +donotmove subject:"Message to stay in new"
+output=$(cd "$MAIL_DIR"; ls */message-to-stay-in-new*)
+test_expect_equal "$output" "new/message-to-stay-in-new"
+
+test_begin_subtest "Message in cur lacking maildir info gets one on any tag change"
+add_message [filename]='message-to-get-maildir-info' [dir]=cur
+notmuch tag +anytag id:$gen_msg_id
+output=$(cd "$MAIL_DIR"; ls */message-to-get-maildir-info*)
+test_expect_equal "$output" "cur/message-to-get-maildir-info:2,"
+
+test_begin_subtest "Message in new with maildir info is moved to cur on any tag change"
+add_message [filename]='message-with-info-to-be-moved-to-cur:2,' [dir]=new
+notmuch tag +anytag id:$gen_msg_id
+output=$(cd "$MAIL_DIR"; ls */message-with-info-to-be-moved-to-cur*)
+test_expect_equal "$output" "cur/message-with-info-to-be-moved-to-cur:2,"
+
+test_begin_subtest "Removing 'S' flag from existing filename adds 'unread' tag"
+add_message [subject]='"Removing S flag"' [filename]='removing-s-flag:2,S' [dir]=cur
+output=$(notmuch search subject:"Removing S flag" | notmuch_search_sanitize)
+output+="
+"
+mv "${gen_msg_filename}" "${gen_msg_filename%S}"
+output+=$(NOTMUCH_NEW)
+output+="
+"
+output+=$(notmuch search subject:"Removing S flag" | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Removing S flag (inbox)
+No new mail. Detected 1 file rename.
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Removing S flag (inbox unread)"
+
+test_begin_subtest "Removing info from filename leaves tags unchanged"
+add_message [subject]='"Message to lose maildir info"' [filename]='message-to-lose-maildir-info' [dir]=cur
+notmuch tag -unread subject:"Message to lose maildir info"
+mv "$MAIL_DIR/cur/message-to-lose-maildir-info:2,S" "$MAIL_DIR/cur/message-without-maildir-info"
+output=$(NOTMUCH_NEW)
+output+="
+"
+output+=$(notmuch search subject:"Message to lose maildir info" | notmuch_search_sanitize)
+test_expect_equal "$output" "No new mail. Detected 1 file rename.
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Message to lose maildir info (inbox)"
+
+test_begin_subtest "Can remove unread tag from message in non-maildir directory"
+add_message [subject]='"Non-maildir message"' [dir]=notmaildir [filename]='non-maildir-message'
+expected=$(notmuch search --output=files subject:"Non-maildir message")
+test_expect_success 'notmuch tag -unread subject:"Non-maildir message"'
+
+test_begin_subtest "Message in non-maildir directory does not get renamed"
+output=$(notmuch search --output=files subject:"Non-maildir message")
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest "notmuch dump/restore re-synchronizes maildir tags with flags"
+# Capture current filename state
+expected=$(ls $MAIL_DIR/cur)
+# Add/remove some flags from filenames
+mv $MAIL_DIR/cur/adding-replied-tag:2,RS $MAIL_DIR/cur/adding-replied-tag:2,S
+mv $MAIL_DIR/cur/adding-s-flag:2,S $MAIL_DIR/cur/adding-s-flag:2,
+mv $MAIL_DIR/cur/adding-with-s-flag:2,S $MAIL_DIR/cur/adding-with-s-flag:2,RS
+mv $MAIL_DIR/cur/message-to-move-to-cur:2,S $MAIL_DIR/cur/message-to-move-to-cur:2,DS
+notmuch dump --output=dump.txt
+NOTMUCH_NEW >/dev/null
+notmuch restore --input=dump.txt
+output=$(ls $MAIL_DIR/cur)
+test_expect_equal "$output" "$expected"
+
+test_begin_subtest 'Adding flags to duplicate message tags the mail'
+add_message [subject]='"Duplicated message"' [dir]=cur [filename]='duplicated-message:2,'
+cp "$MAIL_DIR/cur/duplicated-message:2," "$MAIL_DIR/cur/duplicated-message-copy:2,RS"
+NOTMUCH_NEW > output
+notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
+test_expect_equal "$(< output)" "No new mail.
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Duplicated message (inbox replied)"
+
+test_begin_subtest "Adding duplicate message without flags does not remove tags"
+cp "$MAIL_DIR/cur/duplicated-message-copy:2,RS" "$MAIL_DIR/cur/duplicated-message-another-copy:2,"
+NOTMUCH_NEW > output
+notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
+test_expect_equal "$(< output)" "No new mail.
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; Duplicated message (inbox replied)"
+
+test_begin_subtest "Tag changes modify flags of multiple files"
+notmuch tag -replied subject:"Duplicated message"
+(cd $MAIL_DIR/cur/; ls duplicated*) > actual
+test_expect_equal "$(< actual)"  "duplicated-message-another-copy:2,S
+duplicated-message-copy:2,S
+duplicated-message:2,S"
+
+test_begin_subtest "Synchronizing tag changes preserves unsupported maildir flags"
+add_message [subject]='"Unsupported maildir flags"' [dir]=cur [filename]='unsupported-maildir-flags:2,FSZxyz'
+notmuch tag +unread +draft -flagged subject:"Unsupported maildir flags"
+test_expect_equal "$(cd $MAIL_DIR/cur/; ls unsupported*)" "unsupported-maildir-flags:2,DZxyz"
+
+test_begin_subtest "A file with non-compliant maildir info will not be renamed"
+add_message [subject]='"Non-compliant maildir info"' [dir]=cur [filename]='non-compliant-maildir-info:2,These-are-not-flags-in-ASCII-order-donottouch'
+notmuch tag +unread +draft -flagged subject:"Non-compliant maildir info"
+test_expect_equal "$(cd $MAIL_DIR/cur/; ls non-compliant*)" "non-compliant-maildir-info:2,These-are-not-flags-in-ASCII-order-donottouch"
+
+test_begin_subtest "Files in new/ get default synchronized tags"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags "test;unread"
+add_message [subject]='"File in new/"' [dir]=new [filename]='file-in-new'
+notmuch config set new.tags $OLDCONFIG
+notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
+test_expect_equal "$(< output)" \
+"thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; File in new/ (test unread)"
+
+for tag in draft flagged passed replied; do
+    test_begin_subtest "$tag is valid in new.tags"
+    OLDCONFIG=$(notmuch config get new.tags)
+    notmuch config set new.tags "$tag;unread"
+    add_message [subject]="\"$tag sync in new\"" [dir]=new
+    notmuch config set new.tags $OLDCONFIG
+    notmuch search "subject:\"$tag sync in new\"" | notmuch_search_sanitize > output
+    test_expect_equal "$(< output)" \
+                     "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; $tag sync in new ($tag unread)"
+done
+test_done
diff --git a/test/T350-crypto.sh b/test/T350-crypto.sh
new file mode 100755 (executable)
index 0000000..a776ec3
--- /dev/null
@@ -0,0 +1,458 @@
+#!/usr/bin/env bash
+
+# TODO:
+# - decryption/verification with signer key not available
+# - verification of signatures from expired/revoked keys
+
+test_description='PGP/MIME signature verification and decryption'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+##################################################
+
+add_gnupg_home
+# Change this if we ship a new test key
+FINGERPRINT="5AEAB11F5E33DCE875DDB75B6D92612D94E46381"
+
+test_begin_subtest "emacs delivery of signed message"
+test_expect_success \
+'emacs_fcc_message \
+    "test signed message 001" \
+    "This is a test signed message." \
+    "(mml-secure-message-sign)"'
+
+test_begin_subtest "signed part content-type indexing"
+output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)"
+
+test_begin_subtest "signature verification"
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"status": "good",
+ "fingerprint": "'$FINGERPRINT'",
+ "created": 946728000}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "detection of modified signed contents"
+emacs_fcc_message \
+    "bad signed message 001" \
+    "Incriminating stuff. This is a test signed message." \
+    "(mml-secure-message-sign)"
+
+file=$(notmuch search --output=files subject:"bad signed message 001")
+
+sed -i 's/Incriminating stuff. //' ${file}
+
+output=$(notmuch show --format=json --verify subject:"bad signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "bad signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"status": "bad",
+ "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'"}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "corrupted pgp/mime signature"
+emacs_fcc_message \
+    "bad signed message 002" \
+    "Incriminating stuff. This is a test signed message." \
+    "(mml-secure-message-sign)"
+
+file=$(notmuch search --output=files subject:"bad signed message 002")
+
+awk '/-----BEGIN PGP SIGNATURE-----/{flag=1;print;next} \
+     /-----END PGP SIGNATURE-----/{flag=0;print;next} \
+     flag{gsub(/[A-Za-z]/,"0");print}!flag{print}' $file > $file.new
+
+rm $file
+mv $file.new $file
+
+output=$(notmuch show --format=json --verify subject:"bad signed message 002" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "bad signed message 002",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "Incriminating stuff. This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "signature verification with full owner trust"
+test_subtest_broken_gmime_2
+# give the key full owner trust
+echo "${FINGERPRINT}:6:" | gpg --no-tty --import-ownertrust >>"$GNUPGHOME"/trust.log 2>&1
+gpg --no-tty --check-trustdb >>"$GNUPGHOME"/trust.log 2>&1
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"status": "good",
+ "fingerprint": "'$FINGERPRINT'",
+ "created": 946728000,
+ "userid": "Notmuch Test Suite <test_suite@notmuchmail.org> (INSECURE!)"}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "signature verification with signer key unavailable"
+# move the gnupghome temporarily out of the way
+mv "${GNUPGHOME}"{,.bak}
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"status": "error",
+ "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'",
+ "errors": {"key-missing": true}}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+mv "${GNUPGHOME}"{.bak,}
+
+test_begin_subtest "emacs delivery of encrypted message with attachment"
+# create a test encrypted message with attachment
+cat <<EOF >TESTATTACHMENT
+This is a test file.
+EOF
+test_expect_success \
+'emacs_fcc_message \
+    "test encrypted message 001" \
+    "This is a test encrypted message.\n" \
+    "(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
+
+test_begin_subtest "encrypted part content-type indexing"
+output=$(notmuch search mimetype:multipart/encrypted and mimetype:application/pgp-encrypted and mimetype:application/octet-stream | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
+
+test_begin_subtest "decryption, --format=text"
+output=$(notmuch show --format=text --decrypt=true subject:"test encrypted message 001" \
+    | notmuch_show_sanitize_all \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='\fmessage{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (2000-01-01) (encrypted inbox)
+Subject: test encrypted message 001
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: test_suite@notmuchmail.org
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: multipart/encrypted
+\fpart{ ID: 2, Content-type: application/pgp-encrypted
+Non-text part: application/pgp-encrypted
+\fpart}
+\fpart{ ID: 3, Content-type: multipart/mixed
+\fpart{ ID: 4, Content-type: text/plain
+This is a test encrypted message.
+\fpart}
+\fattachment{ ID: 5, Filename: TESTATTACHMENT, Content-type: application/octet-stream
+Non-text part: application/octet-stream
+\fattachment}
+\fpart}
+\fpart}
+\fbody}
+\fmessage}'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "decryption, --format=json"
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["encrypted","inbox"],
+ "headers": {"Subject": "test encrypted message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "encstatus": [{"status": "good"}],
+ "sigstatus": [],
+ "content-type": "multipart/encrypted",
+ "content": [{"id": 2,
+ "content-type": "application/pgp-encrypted",
+ "content-length": "NONZERO"},
+ {"id": 3,
+ "content-type": "multipart/mixed",
+ "content": [{"id": 4,
+ "content-type": "text/plain",
+ "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"}]}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "decryption, --format=json, --part=4"
+output=$(notmuch show --format=json --part=4 --decrypt=true subject:"test encrypted message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='{"id": 4,
+ "content-type": "text/plain",
+ "content": "This is a test encrypted message.\n"}'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "decrypt attachment (--part=5 --format=raw)"
+notmuch show \
+    --format=raw \
+    --part=5 \
+    --decrypt=true \
+    subject:"test encrypted message 001" >OUTPUT
+test_expect_equal_file TESTATTACHMENT OUTPUT
+
+test_begin_subtest "decryption failure with missing key"
+mv "${GNUPGHOME}"{,.bak}
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["encrypted","inbox"],
+ "headers": {"Subject": "test encrypted message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "encstatus": [{"status": "bad"}],
+ "content-type": "multipart/encrypted",
+ "content": [{"id": 2,
+ "content-type": "application/pgp-encrypted",
+ "content-length": "NONZERO"},
+ {"id": 3,
+ "content-type": "application/octet-stream",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+mv "${GNUPGHOME}"{.bak,}
+
+test_begin_subtest "emacs delivery of encrypted + signed message"
+test_expect_success \
+'emacs_fcc_message \
+    "test encrypted message 002" \
+    "This is another test encrypted message.\n" \
+    "(mml-secure-message-sign-encrypt)"'
+
+test_begin_subtest "decryption + signature verification"
+test_subtest_broken_gmime_2
+output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 002" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["encrypted","inbox"],
+ "headers": {"Subject": "test encrypted message 002",
+ "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,
+ "encstatus": [{"status": "good"}],
+ "sigstatus": [{"status": "good",
+ "fingerprint": "'$FINGERPRINT'",
+ "created": 946728000,
+ "userid": "Notmuch Test Suite <test_suite@notmuchmail.org> (INSECURE!)"}],
+ "content-type": "multipart/encrypted",
+ "content": [{"id": 2,
+ "content-type": "application/pgp-encrypted",
+ "content-length": "NONZERO"},
+ {"id": 3,
+ "content-type": "text/plain",
+ "content": "This is another test encrypted message.\n"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "reply to encrypted message"
+output=$(notmuch reply --decrypt=true subject:"test encrypted message 002" \
+    | notmuch_drop_mail_headers In-Reply-To References)
+expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: test encrypted message 002
+
+On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
+> This is another test encrypted message.'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "Reply within emacs to an encrypted message"
+test_emacs "(let ((message-hidden-headers '())
+      (notmuch-crypto-process-mime 't))
+  (notmuch-show \"subject:test.encrypted.message.002\")
+  (notmuch-show-reply)
+  (test-output))"
+# the empty To: is probably a bug, but it's not to do with encryption
+grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' -e 'To:' < OUTPUT > OUTPUT.clean
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: test encrypted message 002
+--text follows this line--
+<#secure method=pgpmime mode=signencrypt>
+Notmuch Test Suite <test_suite@notmuchmail.org> writes:
+
+> This is another test encrypted message.
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+
+test_begin_subtest "signature verification with revoked key"
+# generate revocation certificate and load it to revoke key
+echo "y
+1
+Notmuch Test Suite key revocation (automated) $(date '+%F_%T%z')
+
+y
+
+" \
+    | gpg --no-tty --quiet --command-fd 0 --armor --gen-revoke "0x${FINGERPRINT}!" 2>/dev/null \
+    | gpg --no-tty --quiet --import
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"status": "error",
+ "keyid": "6D92612D94E46381",
+ "errors": {"key-revoked": true}}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+ "content-type": "application/pgp-signature",
+ "content-length": "NONZERO"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_done
diff --git a/test/T355-smime.sh b/test/T355-smime.sh
new file mode 100755 (executable)
index 0000000..be45e3b
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+
+test_description='S/MIME signature verification and decryption'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_gpgsm_home ()
+{
+    local fpr
+    [ -d ${GNUPGHOME} ] && return
+    _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
+    at_exit_function _gnupg_exit
+    mkdir -m 0700 "$GNUPGHOME"
+    gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
+    fpr=$(gpgsm  --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
+    echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt
+    test_debug "cat $GNUPGHOME/import.log"
+}
+
+test_require_external_prereq openssl
+test_require_external_prereq gpgsm
+
+cp $NOTMUCH_SRCDIR/test/smime/key+cert.pem test_suite.pem
+
+FINGERPRINT=$(openssl x509 -fingerprint -in test_suite.pem -noout | sed -e 's/^.*=//' -e s/://g)
+
+add_gpgsm_home
+
+test_begin_subtest "emacs delivery of S/MIME signed message"
+test_expect_success \
+     'emacs_fcc_message \
+     "test signed message 001" \
+     "This is a test signed message." \
+     "(mml-secure-message-sign \"smime\")"'
+
+test_begin_subtest "emacs delivery of S/MIME encrypted + signed message"
+# Hard code the MML to avoid several interactive questions
+test_expect_success \
+'emacs_fcc_message \
+    "test encrypted message 001" \
+    "<#secure method=smime mode=signencrypt keyfile=\\\"test_suite.pem\\\" certfile=\\\"test_suite.pem\\\">\nThis is a test encrypted message.\n"'
+
+test_begin_subtest "Signature verification (openssl)"
+notmuch show --format=raw subject:"test signed message 001" |\
+    openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
+cat <<EOF > EXPECTED
+Verification successful
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "signature verification (notmuch CLI)"
+if [ "${NOTMUCH_GMIME_MAJOR}" -lt 3 ]; then
+    # gmime 2 can't report User IDs properly for S/MIME
+    USERID=''
+else
+    USERID='"userid": "CN=Notmuch Test Suite",'
+fi
+output=$(notmuch show --format=json --verify subject:"test signed message 001" \
+    | notmuch_json_show_sanitize \
+    | sed -e 's|"created": [-1234567890]*|"created": 946728000|' \
+         -e 's|"expires": [-1234567890]*|"expires": 424242424|' )
+expected='[[[{"id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","signed"],
+ "headers": {"Subject": "test signed message 001",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "test_suite@notmuchmail.org",
+ "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+ "sigstatus": [{"fingerprint": "'$FINGERPRINT'",
+ "status": "good",'$USERID'
+ "expires": 424242424,
+ "created": 946728000}],
+ "content-type": "multipart/signed",
+ "content": [{"id": 2,
+ "content-type": "text/plain",
+ "content": "This is a test signed message.\n"},
+ {"id": 3,
+  "content-disposition": "attachment",
+  "content-length": "NONZERO",
+  "content-transfer-encoding": "base64",
+  "content-type": "application/x-pkcs7-signature",
+  "filename": "smime.p7s"}]}]},
+ []]]]'
+test_expect_equal_json \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "Decryption and signature verification (openssl)"
+notmuch show --format=raw subject:"test encrypted message 001" |\
+    openssl smime -decrypt -recip test_suite.pem |\
+    openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
+cat <<EOF > EXPECTED
+Verification successful
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh
new file mode 100755 (executable)
index 0000000..c5435f4
--- /dev/null
@@ -0,0 +1,321 @@
+#!/usr/bin/env bash
+
+# TODO: test index.decryption=failed
+
+test_description='indexing decrypted mail'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+##################################################
+
+add_gnupg_home
+# get key fingerprint
+FINGERPRINT=$(gpg --no-tty --list-secret-keys --with-colons --fingerprint | grep '^fpr:' | cut -d: -f10)
+
+# create a test encrypted message
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message \
+    "test encrypted message for cleartext index 001" \
+    "This is a test encrypted message with a wumpus.\n" \
+    "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "search for unindexed cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# create a test encrypted message that is indexed in the clear
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message --decrypt=true \
+    "test encrypted message for cleartext index 002" \
+    "This is a test encrypted message with a wumpus.\n" \
+    "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "emacs delivery of encrypted message, indexed cleartext"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000002   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for one message"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "show the message body of the encrypted message"
+notmuch dump wumpus
+output=$(notmuch show wumpus | notmuch_show_part 3)
+expected='This is a test encrypted message with a wumpus.'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+test_begin_subtest "message should go away after deletion"
+# cache the message in an env var and remove it:
+fname=$(notmuch search --output=files wumpus)
+contents="$(notmuch show --format=raw wumpus)"
+rm -f "$fname"
+notmuch new
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try reinserting it without decryption, should stay the same:
+test_begin_subtest "message cleartext not present after insert"
+notmuch insert --folder=sent <<<"$contents"
+output=$(notmuch search wumpus)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# show the message using stashing decryption
+test_begin_subtest "stash decryption during show"
+output=$(notmuch show --decrypt=stash tag:encrypted subject:002 | notmuch_show_part 3)
+expected='This is a test encrypted message with a wumpus.'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "search should now find the contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000003   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try reinserting it with decryption, should appear again, but now we
+# have two copies of the message:
+test_begin_subtest "message cleartext is present after reinserting with --decrypt=true"
+notmuch insert --folder=sent --decrypt=true <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000003   2000-01-01 [1/1(2)] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# remove all copies
+test_begin_subtest "delete all copies of the message"
+mid="$(notmuch search --output=messages wumpus)"
+rm -f $(notmuch search --output=files wumpus)
+notmuch new
+output=$(notmuch search "id:$mid")
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try inserting it with decryption, should appear as a single copy
+# (note: i think thread id skips 4 because of duplicate message-id
+# insertion, above)
+test_begin_subtest "message cleartext is present with insert --decrypt=true"
+notmuch insert --folder=sent --decrypt=true <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+# add a tag to all messages to ensure that it stays after reindexing
+test_begin_subtest 'tagging all messages'
+test_expect_success 'notmuch tag +blarney "encrypted message"'
+test_begin_subtest "verify that tags have not changed"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# see if first message shows up after reindexing with --decrypt=true (same $expected, untouched):
+test_begin_subtest 'reindex old messages'
+test_expect_success 'notmuch reindex --decrypt=true tag:encrypted and not property:index.decryption=success'
+test_begin_subtest "reindexed encrypted message, including cleartext"
+output=$(notmuch search wumpus)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for both messages"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try a simple reindex
+test_begin_subtest 'reindex in auto mode'
+test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, should not have changed"
+output=$(notmuch search wumpus)
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# try to remove cleartext indexing
+test_begin_subtest 'reindex without cleartext'
+test_expect_success 'notmuch reindex --decrypt=false tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, without cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# ensure no session keys are present:
+test_begin_subtest 'reindex using only session keys'
+test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, decrypting only with session keys"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property with both messages unindexed"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+# ensure that the tags remain even when we are dropping the cleartext.
+test_begin_subtest "verify that tags remain without cleartext"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "index cleartext without keeping session keys"
+test_expect_success "notmuch reindex --decrypt=nostash tag:blarney"
+
+test_begin_subtest "Ensure that the indexed terms are present"
+output=$(notmuch search wumpus)
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "show one of the messages with --decrypt=true"
+output=$(notmuch show --decrypt=true thread:0000000000000001 | notmuch_show_part 3)
+expected='This is a test encrypted message with a wumpus.'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "Ensure that we cannot show the message with --decrypt=auto"
+output=$(notmuch show thread:0000000000000001 | notmuch_show_part 3)
+expected='Non-text part: application/octet-stream'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+add_email_corpus crypto
+
+test_begin_subtest "indexing message fails when secret key not available"
+notmuch reindex --decrypt=true id:simple-encrypted@crypto.notmuchmail.org
+output=$(notmuch dump )
+expected='#notmuch-dump batch-tag:3 config,properties,tags
++encrypted +inbox +unread -- id:simple-encrypted@crypto.notmuchmail.org
+#= simple-encrypted@crypto.notmuchmail.org index.decryption=failure'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "cannot find cleartext index"
+output=$(notmuch search sekrit)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "cleartext index recovery on reindexing with stashed session keys"
+notmuch restore <<EOF
+#notmuch-dump batch-tag:3 config,properties,tags
+#= simple-encrypted@crypto.notmuchmail.org session-key=9%3AFC09987F5F927CC0CC0EE80A96E4C5BBF4A499818FB591207705DFDDD6112CF9
+EOF
+notmuch reindex id:simple-encrypted@crypto.notmuchmail.org
+output=$(notmuch search sekrit)
+expected='thread:0000000000000001   2016-12-22 [1/1] Daniel Kahn Gillmor; encrypted message (encrypted inbox unread)'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "notmuch reply should show cleartext if session key is present"
+output=$(notmuch reply id:simple-encrypted@crypto.notmuchmail.org | grep '^>')
+expected='> This is a top sekrit message.'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "notmuch show should show cleartext if session key is present"
+output=$(notmuch show id:simple-encrypted@crypto.notmuchmail.org | notmuch_show_part 3)
+expected='This is a top sekrit message.'
+if [ $NOTMUCH_HAVE_GMIME_SESSION_KEYS -eq 0 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "notmuch show should show nothing if decryption is explicitly disallowed"
+output=$(notmuch show --decrypt=false id:simple-encrypted@crypto.notmuchmail.org | notmuch_show_part 3)
+expected='Non-text part: application/octet-stream'
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "purging stashed session keys should lose access to the cleartext"
+notmuch reindex --decrypt=false id:simple-encrypted@crypto.notmuchmail.org
+output=$(notmuch search sekrit)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+test_begin_subtest "and cleartext should be unrecoverable now that there are no stashed session keys"
+notmuch dump
+notmuch reindex --decrypt=true id:simple-encrypted@crypto.notmuchmail.org
+output=$(notmuch search sekrit)
+expected=''
+test_expect_equal \
+    "$output" \
+    "$expected"
+
+
+# TODO: test removal of a message from the message store between
+# indexing and reindexing.
+
+# TODO: insert the same message into the message store twice, index,
+# remove one of them from the message store, and then reindex.
+# reindexing should return a failure but the message should still be
+# present? -- or what should the semantics be if you ask to reindex a
+# message whose underlying files have been renamed or moved or
+# removed?
+
+test_done
diff --git a/test/T360-symbol-hiding.sh b/test/T360-symbol-hiding.sh
new file mode 100755 (executable)
index 0000000..43921cb
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2011 David Bremner
+#
+
+# This test tests whether hiding Xapian::Error symbols in libnotmuch
+# also hides them for other users of libxapian. This is motivated by
+# the discussion in https://gcc.gnu.org/wiki/Visibility'
+
+test_description='exception symbol hiding'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest 'running test' run_test
+mkdir -p ${PWD}/fakedb/.notmuch
+$TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \
+       | notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,${NOTMUCH_DEFAULT_XAPIAN_BACKEND},backend,g" > OUTPUT
+
+cat <<EOF > EXPECTED
+A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
+caught No backend database found at path 'CWD/nonexistent'
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'checking output'
+test_expect_equal "$result" "$output"
+
+test_begin_subtest 'comparing existing to exported symbols'
+nm -P $NOTMUCH_BUILDDIR/lib/libnotmuch.so | awk '$2 == "T" && $1 ~ "^notmuch" {print $1}' | sort | uniq > ACTUAL
+sed -n 's/^\(notmuch_[a-zA-Z0-9_]*\)[[:blank:]]*(.*/\1/p' $NOTMUCH_SRCDIR/lib/notmuch.h | sort | uniq > EXPORTED
+test_expect_equal_file EXPORTED ACTUAL
+
+test_done
diff --git a/test/T370-search-folder-coherence.sh b/test/T370-search-folder-coherence.sh
new file mode 100755 (executable)
index 0000000..0a2727e
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+test_description='folder tags removed and added through file renames remain consistent'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "No new messages"
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "No new mail."
+
+
+test_begin_subtest "Single new message"
+generate_message
+file_x=$gen_msg_filename
+id_x=$gen_msg_id
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "Add second folder for same message"
+dir=$(dirname $file_x)
+mkdir $dir/spam
+cp $file_x $dir/spam
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "No new mail."
+
+
+test_begin_subtest "Multiple files for same message"
+cat <<EOF >EXPECTED
+MAIL_DIR/msg-001
+MAIL_DIR/spam/msg-001
+EOF
+notmuch search --output=files id:$id_x | notmuch_search_files_sanitize >OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Test matches folder:spam"
+output=$(notmuch search folder:spam)
+test_expect_equal "$output" "thread:0000000000000001   2001-01-05 [1/1(2)] Notmuch Test Suite; Single new message (inbox unread)"
+
+test_begin_subtest "Remove folder:spam copy of email"
+rm $dir/spam/$(basename $file_x)
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "No new mail. Detected 1 file rename."
+
+test_begin_subtest "No mails match the folder:spam search"
+output=$(notmuch search folder:spam)
+test_expect_equal "$output" ""
+
+test_done
diff --git a/test/T380-atomicity.sh b/test/T380-atomicity.sh
new file mode 100755 (executable)
index 0000000..45de222
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+test_description='atomicity'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# This script tests the effects of killing and restarting "notmuch
+# new" at arbitrary points.  If notmuch new is properly atomic, the
+# final database contents should be the same regardless of when (or
+# if) it is killed and restarted.
+
+if test_require_external_prereq gdb; then
+
+# Create a maildir structure to also stress flag synchronization
+    mkdir $MAIL_DIR/cur
+    mkdir $MAIL_DIR/new
+    mkdir $MAIL_DIR/tmp
+    mkdir $MAIL_DIR/.remove-dir
+
+    # Prepare the initial database
+    generate_message [subject]='Duplicate' [filename]='duplicate:2,' [dir]=cur
+    generate_message [subject]='Remove' [filename]='remove:2,' [dir]=cur
+    generate_message [subject]='"Remove duplicate"' [filename]='remove-duplicate:2,' [dir]=cur
+    cp $MAIL_DIR/cur/remove-duplicate:2, $MAIL_DIR/cur/remove-duplicate-copy:2,
+    generate_message [subject]='Rename' [filename]='rename:2,' [dir]=cur
+    generate_message [subject]='"Rename duplicate"' [filename]='rename-duplicate:2,' [dir]=cur
+    generate_message [subject]='"Move 1"' [filename]='move1:2,' [dir]=cur
+    generate_message [subject]='"Move 2"' [filename]='move2:2,' [dir]=new
+    generate_message [subject]='Flag' [filename]='flag:2,' [dir]=cur
+    generate_message [subject]='"Flag duplicate"' [filename]='flag-duplicate:2,' [dir]=cur
+    cp $MAIL_DIR/cur/flag-duplicate:2, $MAIL_DIR/cur/flag-duplicate-copy:2,F
+    generate_message [subject]='"Remove directory"' [filename]='remove-directory:2,' [dir]=.remove-dir
+    generate_message [subject]='"Remove directory duplicate"' [filename]='remove-directory-duplicate:2,' [dir]=.remove-dir
+    cp $MAIL_DIR/.remove-dir/remove-directory-duplicate:2, $MAIL_DIR/cur/
+    notmuch new > /dev/null
+
+    # Make all maildir changes, but *don't* update the database
+    generate_message [subject]='Added' [filename]='added:2,' [dir]=cur
+    cp $MAIL_DIR/cur/duplicate:2, $MAIL_DIR/cur/duplicate-copy:2,
+    generate_message [subject]='"Add duplicate"' [filename]='add-duplicate:2,' [dir]=cur
+    generate_message [subject]='"Add duplicate copy"' [filename]='add-duplicate-copy:2,' [dir]=cur
+    rm $MAIL_DIR/cur/remove:2,
+    rm $MAIL_DIR/cur/remove-duplicate-copy:2,
+    mv $MAIL_DIR/cur/rename:2, $MAIL_DIR/cur/renamed:2,
+    mv $MAIL_DIR/cur/rename-duplicate:2, $MAIL_DIR/cur/renamed-duplicate:2,
+    mv $MAIL_DIR/cur/move1:2, $MAIL_DIR/new/move1:2,
+    mv $MAIL_DIR/new/move2:2, $MAIL_DIR/cur/move2:2,
+    mv $MAIL_DIR/cur/flag:2, $MAIL_DIR/cur/flag:2,F
+    rm $MAIL_DIR/cur/flag-duplicate-copy:2,F
+    rm $MAIL_DIR/.remove-dir/remove-directory:2,
+    rm $MAIL_DIR/.remove-dir/remove-directory-duplicate:2,
+    rmdir $MAIL_DIR/.remove-dir
+
+    # Prepare a snapshot of the updated maildir.  The gdb script will
+    # update the database in this snapshot as it goes.
+    cp -a $MAIL_DIR $MAIL_DIR.snap
+    cp ${NOTMUCH_CONFIG} ${NOTMUCH_CONFIG}.snap
+    NOTMUCH_CONFIG=${NOTMUCH_CONFIG}.snap notmuch config set database.path $MAIL_DIR.snap
+
+
+
+    # Execute notmuch new and, at every call to rename, snapshot the
+    # database, run notmuch new again on the snapshot, and capture the
+    # results of search.
+    #
+    # -tty /dev/null works around a conflict between the 'timeout' wrapper
+    # and gdb's attempt to control the TTY.
+    export MAIL_DIR
+    ${TEST_GDB} -tty /dev/null -batch -x $NOTMUCH_SRCDIR/test/atomicity.py notmuch 1>gdb.out 2>&1
+
+    # Get the final, golden output
+    notmuch search '*' > expected
+
+    # Check output against golden output
+    outcount=$(cat outcount)
+    echo -n > searchall
+    echo -n > expectall
+    for ((i = 0; i < $outcount; i++)); do
+       if ! cmp -s search.$i expected; then
+           # Find the range of interruptions that match this output
+           for ((end = $i + 1 ; end < $outcount; end++)); do
+               if ! cmp -s search.$i search.$end; then
+                   break
+               fi
+           done
+           echo "When interrupted after $test/backtrace.$(expr $i - 1) (abort points $i-$(expr $end - 1))" >> searchall
+           cat search.$i >> searchall
+           cat expected >> expectall
+           echo >> searchall
+           echo >> expectall
+
+           i=$(expr $end - 1)
+       fi
+    done
+fi
+
+test_begin_subtest '"notmuch new" is idempotent under arbitrary aborts'
+test_expect_equal_file searchall expectall
+
+test_begin_subtest "detected $outcount>10 abort points"
+test_expect_success "test $outcount -gt 10"
+
+test_done
diff --git a/test/T390-python.sh b/test/T390-python.sh
new file mode 100755 (executable)
index 0000000..9f71ce3
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/env bash
+test_description="python bindings"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_require_external_prereq ${NOTMUCH_PYTHON}
+
+add_email_corpus
+add_gnupg_home
+
+test_begin_subtest "compare thread ids"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+q_new = notmuch.Query(db, 'tag:inbox')
+q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
+for t in q_new.search_threads():
+    print (t.get_thread_id())
+EOF
+notmuch search --sort=oldest-first --output=threads tag:inbox | sed s/^thread:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "compare message ids"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+q_new = notmuch.Query(db, 'tag:inbox')
+q_new.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
+for m in q_new.search_messages():
+    print (m.get_message_id())
+EOF
+notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get non-existent file"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+print (db.find_message_by_filename("i-dont-exist"))
+EOF
+test_expect_equal "$(cat OUTPUT)" "None"
+
+test_begin_subtest "get revision"
+test_python ${MAIL_DIR} <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+(revision, uuid) = db.get_revision()
+print ("%s\t%lu" % (uuid, revision))
+EOF
+notmuch_uuid_sanitize < OUTPUT > CLEAN
+cat <<'EOF' >EXPECTED
+UUID   53
+EOF
+test_expect_equal_file EXPECTED CLEAN
+
+grep '^[0-9a-f]' OUTPUT > INITIAL_OUTPUT
+
+test_begin_subtest "output of count matches test code"
+notmuch count --lastmod '*' | cut -f2-3 > OUTPUT
+test_expect_equal_file INITIAL_OUTPUT OUTPUT
+add_message '[content-type]="text/plain; charset=iso-8859-2"' \
+            '[content-transfer-encoding]=8bit' \
+            '[subject]="ISO-8859-2 encoded message"' \
+            "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
+test_begin_subtest "Add ISO-8859-2 encoded message, call get_message_parts"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+q_new = notmuch.Query(db, 'ISO-8859-2 encoded message')
+for m in q_new.search_messages():
+    for mp in m.get_message_parts():
+      continue
+    print(m.get_message_id())
+EOF
+
+notmuch search --sort=oldest-first --output=messages "tučňáččí" | sed s/^id:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+# TODO currently these tests for setting and getting config values are
+# somewhat interdependent.  This is because the config values stored in the
+# database are not cleaned up after each test, so they remain there for the
+# next test.  The ./README file states that this can happen so it seems kind
+# of ok.
+
+test_begin_subtest "set and get config values"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+db.set_config('testkey1', 'testvalue1')
+db.set_config('testkey2', 'testvalue2')
+v1 = db.get_config('testkey1')
+v2 = db.get_config('testkey2')
+print('testkey1 = ' + v1)
+print('testkey2 = ' + v2)
+EOF
+cat <<'EOF' >EXPECTED
+testkey1 = testvalue1
+testkey2 = testvalue2
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get_configs with no match returns empty generator"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database()
+v = db.get_configs('nonexistent')
+print(list(v) == [])
+EOF
+test_expect_equal "$(cat OUTPUT)" "True"
+
+test_begin_subtest "get_configs with no arguments returns all pairs"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+db.set_config("zzzafter", "afterval")
+db.set_config("aaabefore", "beforeval")
+v = db.get_configs()
+for index, keyval in enumerate(v):
+    key, val = keyval
+    print('{}: {} => {}'.format(index, key, val))
+EOF
+cat <<'EOF' >EXPECTED
+0: aaabefore => beforeval
+1: testkey1 => testvalue1
+2: testkey2 => testvalue2
+3: zzzafter => afterval
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get_configs prefix is used to match keys"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+db.set_config('testkey1', 'testvalue1')
+db.set_config('testkey2', 'testvalue2')
+v = db.get_configs('testkey')
+for index, keyval in enumerate(v):
+    key, val = keyval
+    print('{}: {} => {}'.format(index, key, val))
+EOF
+cat <<'EOF' >EXPECTED
+0: testkey1 => testvalue1
+1: testkey2 => testvalue2
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "set_config with no value will unset config entries"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+db.set_config('testkey1', '')
+db.set_config('testkey2', '')
+db.set_config("zzzafter", '')
+db.set_config("aaabefore", '')
+v = db.get_configs()
+print(list(v) == [])
+EOF
+test_expect_equal "$(cat OUTPUT)" "True"
+
+mkdir -p "${MAIL_DIR}/cur"
+fname="${MAIL_DIR}/cur/simplemsg.eml"
+cat <<EOF > "$fname"
+From: test_suite@notmuchmail.org
+To: test_suite@notmuchmail.org
+Subject: encrypted message
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-ID: <simplemsg@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+       protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+$(printf 'Content-Type: text/plain\n\nThis is the sekrit message\n' | gpg --no-tty --batch --quiet --trust-model=always --encrypt --armor --recipient test_suite@notmuchmail.org)
+--=-=-=--
+EOF
+
+test_begin_subtest "index message with decryption"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+(m, status) = db.index_file('$fname', decrypt_policy=notmuch.Database.DECRYPTION_POLICY.TRUE)
+if status == notmuch.errors.STATUS.DUPLICATE_MESSAGE_ID:
+   print("got duplicate message")
+q_new = notmuch.Query(db, 'sekrit')
+for m in q_new.search_messages():
+    print(m.get_filename())
+EOF
+echo "$fname" > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T395-ruby.sh b/test/T395-ruby.sh
new file mode 100755 (executable)
index 0000000..a0b76eb
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+test_description="ruby bindings"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
+    test_subtest_missing_external_prereq_["ruby development files"]=t
+fi
+
+add_email_corpus
+
+test_begin_subtest "compare thread ids"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+@q.sort = Notmuch::SORT_OLDEST_FIRST
+for t in @q.search_threads do
+  print t.thread_id, "\n"
+end
+EOF
+notmuch search --sort=oldest-first --output=threads tag:inbox | sed s/^thread:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "compare message ids"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+@q.sort = Notmuch::SORT_OLDEST_FIRST
+for m in @q.search_messages do
+  print m.message_id, "\n"
+end
+EOF
+notmuch search --sort=oldest-first --output=messages tag:inbox | sed s/^id:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get non-existent file"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+result = @db.find_message_by_filename('i-dont-exist')
+print (result == nil)
+EOF
+test_expect_equal "$(cat OUTPUT)" "true"
+
+test_begin_subtest "count messages"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+print @q.count_messages(),"\n"
+EOF
+notmuch count --output=messages tag:inbox > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "count threads"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@q = @db.query('tag:inbox')
+print @q.count_threads(),"\n"
+EOF
+notmuch count --output=threads tag:inbox > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get all tags"
+test_ruby <<"EOF"
+require 'notmuch'
+$maildir = ENV['MAIL_DIR']
+if not $maildir then
+  abort('environment variable MAIL_DIR must be set')
+end
+@db = Notmuch::Database.new($maildir)
+@t = @db.all_tags()
+for tag in @t do
+   print tag,"\n"
+end
+EOF
+notmuch search --output=tags '*' > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T400-hooks.sh b/test/T400-hooks.sh
new file mode 100755 (executable)
index 0000000..49c690e
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+test_description='hooks'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
+
+create_echo_hook () {
+    local TOKEN="${RANDOM}"
+    mkdir -p ${HOOK_DIR}
+    cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+echo "${TOKEN}" > ${3}
+EOF
+    chmod +x "${HOOK_DIR}/${1}"
+    echo "${TOKEN}" > ${2}
+}
+
+create_failing_hook () {
+    mkdir -p ${HOOK_DIR}
+    cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+exit 13
+EOF
+    chmod +x "${HOOK_DIR}/${1}"
+}
+
+rm_hooks () {
+    rm -rf ${HOOK_DIR}
+}
+
+# add a message to generate mail dir and database
+add_message
+# create maildir structure for notmuch-insert
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+
+test_begin_subtest "pre-new is run"
+rm_hooks
+generate_message
+create_echo_hook "pre-new" expected output
+notmuch new > /dev/null
+test_expect_equal_file expected output
+
+test_begin_subtest "post-new is run"
+rm_hooks
+generate_message
+create_echo_hook "post-new" expected output
+notmuch new > /dev/null
+test_expect_equal_file expected output
+
+test_begin_subtest "post-insert hook is run"
+rm_hooks
+generate_message
+create_echo_hook "post-insert" expected output
+notmuch insert < "$gen_msg_filename"
+test_expect_equal_file expected output
+
+test_begin_subtest "pre-new is run before post-new"
+rm_hooks
+generate_message
+create_echo_hook "pre-new" pre-new.expected pre-new.output
+create_echo_hook "post-new" post-new.expected post-new.output
+notmuch new > /dev/null
+test_expect_equal_file post-new.expected post-new.output
+
+test_begin_subtest "pre-new non-zero exit status (hook status)"
+rm_hooks
+generate_message
+create_failing_hook "pre-new"
+output=`notmuch new 2>&1`
+test_expect_equal "$output" "Error: pre-new hook failed with status 13"
+
+# depends on the previous subtest leaving broken hook behind
+test_begin_subtest "pre-new non-zero exit status (notmuch status)"
+test_expect_code 1 "notmuch new"
+
+# depends on the previous subtests leaving 1 new message behind
+test_begin_subtest "pre-new non-zero exit status aborts new"
+rm_hooks
+output=$(NOTMUCH_NEW)
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "post-new non-zero exit status (hook status)"
+rm_hooks
+generate_message
+create_failing_hook "post-new"
+NOTMUCH_NEW 2>output.stderr >output
+cat output.stderr >> output
+echo "Added 1 new message to the database." > expected
+echo "Error: post-new hook failed with status 13" >> expected
+test_expect_equal_file expected output
+
+# depends on the previous subtest leaving broken hook behind
+test_begin_subtest "post-new non-zero exit status (notmuch status)"
+test_expect_code 1 "notmuch new"
+
+test_begin_subtest "post-insert hook does not affect insert status"
+rm_hooks
+generate_message
+create_failing_hook "post-insert"
+test_expect_success "notmuch insert < \"$gen_msg_filename\" > /dev/null"
+
+test_begin_subtest "hook without executable permissions"
+rm_hooks
+mkdir -p ${HOOK_DIR}
+cat <<EOF >"${HOOK_DIR}/pre-new"
+#!/bin/sh
+echo foo
+EOF
+output=`notmuch new 2>&1`
+test_expect_code 1 "notmuch new"
+
+test_begin_subtest "hook execution failure"
+rm_hooks
+mkdir -p ${HOOK_DIR}
+cat <<EOF >"${HOOK_DIR}/pre-new"
+no hashbang, execl fails
+EOF
+chmod +x "${HOOK_DIR}/pre-new"
+test_expect_code 1 "notmuch new"
+
+test_done
diff --git a/test/T410-argument-parsing.sh b/test/T410-argument-parsing.sh
new file mode 100755 (executable)
index 0000000..b31d239
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+test_description="argument parsing"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "sanity check"
+$TEST_DIRECTORY/arg-test  pos1  --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
+cat <<EOF > EXPECTED
+boolean 1
+keyword 1
+flags 5
+int 7
+string foo
+positional arg 1 pos1
+positional arg 2 pos2
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "sanity check zero values"
+$TEST_DIRECTORY/arg-test --keyword=zero --boolean=false --int=0 > OUTPUT
+cat <<EOF > EXPECTED
+boolean 0
+keyword 0
+int 0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "space instead of = between parameter name and value"
+# Note: spaces aren't allowed for booleans. false turns into a positional arg!
+$TEST_DIRECTORY/arg-test --keyword one --boolean false --string foo --int 7 --flag one --flag three > OUTPUT
+cat <<EOF > EXPECTED
+boolean 1
+keyword 1
+flags 5
+int 7
+string foo
+positional arg 1 false
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--boolean=true"
+$TEST_DIRECTORY/arg-test --boolean=true > OUTPUT
+cat <<EOF > EXPECTED
+boolean 1
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--boolean=false"
+$TEST_DIRECTORY/arg-test --boolean=false > OUTPUT
+cat <<EOF > EXPECTED
+boolean 0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--no-boolean"
+$TEST_DIRECTORY/arg-test --no-boolean > OUTPUT
+cat <<EOF > EXPECTED
+boolean 0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "--no-flag"
+$TEST_DIRECTORY/arg-test --flag=one --flag=three --no-flag=three > OUTPUT
+cat <<EOF > EXPECTED
+flags 1
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments without value"
+$TEST_DIRECTORY/arg-test --boolkeyword bananas > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 1
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments with non-default value separated by a space"
+$TEST_DIRECTORY/arg-test --boolkeyword false bananas > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 0
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments without value at the end"
+$TEST_DIRECTORY/arg-test bananas --boolkeyword > OUTPUT
+cat <<EOF > EXPECTED
+boolkeyword 1
+positional arg 1 bananas
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "test keyword arguments without value but with = (should be an error)"
+$TEST_DIRECTORY/arg-test bananas --boolkeyword= > OUTPUT 2>&1
+cat <<EOF > EXPECTED
+Unknown keyword argument "" for option "boolkeyword".
+Unrecognized option: --boolkeyword=
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T420-emacs-test-functions.sh b/test/T420-emacs-test-functions.sh
new file mode 100755 (executable)
index 0000000..bfc10be
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+test_description="emacs test function sanity"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "emacs test function sanity"
+test_emacs_expect_t 't'
+
+test_done
diff --git a/test/T430-emacs-address-cleaning.sh b/test/T430-emacs-address-cleaning.sh
new file mode 100755 (executable)
index 0000000..02d3b41
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+test_description="emacs address cleaning"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "notmuch-test-address-clean part 1"
+test_emacs_expect_t '(notmuch-test-address-cleaning-1)'
+
+test_begin_subtest "notmuch-test-address-clean part 2"
+test_emacs_expect_t '(notmuch-test-address-cleaning-2)'
+
+test_begin_subtest "notmuch-test-address-clean part 3"
+test_emacs_expect_t '(notmuch-test-address-cleaning-3)'
+
+test_done
diff --git a/test/T440-emacs-hello.sh b/test/T440-emacs-hello.sh
new file mode 100755 (executable)
index 0000000..d23c1fc
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+
+test_description="emacs notmuch-hello view"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
+
+add_email_corpus
+
+test_begin_subtest "User-defined section with inbox tag"
+test_emacs "(let ((notmuch-hello-sections
+                   (list (lambda () (notmuch-hello-insert-searches
+                                     \"Test\" '((\"inbox\" . \"tag:inbox\")))))))
+           (notmuch-hello)
+           (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-new-section OUTPUT
+
+test_begin_subtest "User-defined section with empty, hidden entry"
+test_emacs "(let ((notmuch-hello-sections
+                   (list (lambda () (notmuch-hello-insert-searches
+                                     \"Test-with-empty\"
+                                     '((\"inbox\" . \"tag:inbox\")
+                                       (\"doesnotexist\" . \"tag:doesnotexist\"))
+                                     :hide-empty-searches t)))))
+             (notmuch-hello)
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-section-with-empty OUTPUT
+
+test_begin_subtest "User-defined section, unread tag filtered out"
+test_emacs "(let ((notmuch-hello-sections
+                   (list (lambda () (notmuch-hello-insert-tags-section
+                                     \"Test-with-filtered\"
+                                     :hide-tags '(\"unread\"))))))
+             (notmuch-hello)
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-section-hidden-tag OUTPUT
+
+test_begin_subtest "User-defined section, different query for counts"
+test_emacs "(let ((notmuch-hello-sections
+                   (list (lambda () (notmuch-hello-insert-tags-section
+                                     \"Test-with-counts\"
+                                     :filter-count \"tag:signed\")))))
+             (notmuch-hello)
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-section-counts OUTPUT
+
+test_begin_subtest "Empty custom tags section"
+test_emacs "(let* ((widget (widget-create 'notmuch-hello-tags-section))
+                   (notmuch-hello-sections (list (widget-value widget))))
+             (notmuch-hello)
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-empty-custom-tags-section OUTPUT
+
+test_begin_subtest "Empty custom queries section"
+test_emacs "(let* ((widget (widget-create 'notmuch-hello-query-section))
+                   (notmuch-hello-sections (list (widget-value widget))))
+             (notmuch-hello)
+             (test-output))"
+test_expect_equal_file $EXPECTED/notmuch-hello-empty-custom-queries-section OUTPUT
+
+test_begin_subtest "Column alignment for tag/queries with long names"
+tag=a-very-long-tag # length carefully calculated for 80 characters window width
+notmuch tag +$tag '*'
+test_emacs '(notmuch-hello)
+            (test-output)'
+notmuch tag -$tag '*'
+test_expect_equal_file $EXPECTED/notmuch-hello-long-names OUTPUT
+
+test_done
diff --git a/test/T450-emacs-show.sh b/test/T450-emacs-show.sh
new file mode 100755 (executable)
index 0000000..3555a93
--- /dev/null
@@ -0,0 +1,211 @@
+#!/usr/bin/env bash
+
+test_description="emacs notmuch-show view"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-show.expected-output
+
+add_email_corpus
+
+test_begin_subtest "Hiding Original Message region at beginning of a message"
+message_id='OriginalMessageHiding.1@notmuchmail.org'
+add_message \
+    [id]="$message_id" \
+    '[subject]="Hiding Original Message region at beginning of a message"' \
+    '[body]="-----Original Message-----
+Text here."'
+
+cat <<EOF >EXPECTED
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
+Subject: Hiding Original Message region at beginning of a message
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+[ 2-line hidden original message. Click/Enter to show. ]
+EOF
+
+test_emacs "(notmuch-show \"id:$message_id\")
+           (test-visible-output \"OUTPUT.raw\")"
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Bare subject #1"
+output=$(test_emacs '(notmuch-show-strip-re "Re: subject")')
+test_expect_equal "$output" '"subject"'
+
+test_begin_subtest "Bare subject #2"
+output=$(test_emacs '(notmuch-show-strip-re "re:Re: re:  Re:  re:subject")')
+test_expect_equal "$output" '"subject"'
+
+test_begin_subtest "Bare subject #3"
+output=$(test_emacs '(notmuch-show-strip-re "the cure: fix the regexp")')
+test_expect_equal "$output" '"the cure: fix the regexp"'
+
+test_begin_subtest "don't process cryptographic MIME parts"
+test_emacs '(let ((notmuch-crypto-process-mime nil))
+       (notmuch-show "id:20091117203301.GV3165@dottiness.seas.harvard.edu")
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-process-crypto-mime-parts-off OUTPUT
+
+test_begin_subtest "process cryptographic MIME parts"
+test_emacs '(let ((notmuch-crypto-process-mime t))
+       (notmuch-show "id:20091117203301.GV3165@dottiness.seas.harvard.edu")
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-process-crypto-mime-parts-on OUTPUT
+
+test_begin_subtest "process cryptographic MIME parts (w/ notmuch-show-toggle-process-crypto)"
+test_emacs '(let ((notmuch-crypto-process-mime nil))
+       (notmuch-show "id:20091117203301.GV3165@dottiness.seas.harvard.edu")
+       (notmuch-show-toggle-process-crypto)
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-process-crypto-mime-parts-on OUTPUT
+
+test_begin_subtest "notmuch-show: don't elide non-matching messages"
+test_emacs '(let ((notmuch-show-only-matching-messages nil))
+       (notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
+       (notmuch-test-wait)
+       (notmuch-search-show-thread)
+       (notmuch-test-wait)
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-elide-non-matching-messages-off OUTPUT
+
+test_begin_subtest "notmuch-show: elide non-matching messages"
+test_emacs '(let ((notmuch-show-only-matching-messages t))
+       (notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
+       (notmuch-test-wait)
+       (notmuch-search-show-thread)
+       (notmuch-test-wait)
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-elide-non-matching-messages-on OUTPUT
+
+test_begin_subtest "notmuch-show: elide non-matching messages (w/ notmuch-show-toggle-elide-non-matching)"
+test_emacs '(let ((notmuch-show-only-matching-messages nil))
+       (notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
+       (notmuch-test-wait)
+       (notmuch-search-show-thread)
+       (notmuch-test-wait)
+       (notmuch-show-toggle-elide-non-matching)
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-elide-non-matching-messages-on OUTPUT
+
+test_begin_subtest "notmuch-show: elide non-matching messages (w/ prefix arg to notmuch-show)"
+test_emacs '(let ((notmuch-show-only-matching-messages nil))
+       (notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
+       (notmuch-test-wait)
+       (notmuch-search-show-thread t)
+       (notmuch-test-wait)
+       (test-visible-output))'
+test_expect_equal_file $EXPECTED/notmuch-show-elide-non-matching-messages-on OUTPUT
+
+test_begin_subtest "notmuch-show: disable indentation of thread content (w/ notmuch-show-toggle-thread-indentation)"
+test_emacs '(notmuch-search "from:lars@seas.harvard.edu and subject:\"Maildir storage\"")
+       (notmuch-test-wait)
+       (notmuch-search-show-thread)
+       (notmuch-test-wait)
+       (notmuch-show-toggle-thread-indentation)
+       (test-visible-output)'
+test_expect_equal_file $EXPECTED/notmuch-show-indent-thread-content-off OUTPUT
+
+test_begin_subtest "id buttonization"
+add_message '[body]="
+id:abc
+id:abc.def. id:abc,def, id:abc;def; id:abc:def:
+id:foo@bar.?baz? id:foo@bar!.baz!
+(id:foo@bar.baz) [id:foo@bar.baz]
+id:foo@bar.baz...
+id:2+2=5
+id:=_-:/.[]@$%+
+id:abc)def
+id:ab\"c def
+id:\"abc\"def
+id:\"ab\"\"c\"def
+id:\"ab c\"def
+id:\"abc\".def
+id:\"abc
+\"
+id:)
+id:
+cid:xxx
+mid:abc mid:abc/def
+mid:abc%20def
+mid:abc. mid:abc, mid:abc;"'
+test_emacs '(notmuch-show "id:'$gen_msg_id'")
+       (notmuch-test-mark-links)
+       (test-visible-output "OUTPUT.raw")'
+cat <<EOF >EXPECTED
+Notmuch Test Suite <test_suite@notmuchmail.org> (2001-01-05) (inbox)
+Subject: id buttonization
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: GENERATED_DATE
+
+<<id:abc>>
+<<id:abc.def>>. <<id:abc,def>>, <<id:abc;def>>; <<id:abc:def>>:
+<<id:foo@bar.?baz>>? <<id:foo@bar!.baz>>!
+(<<id:foo@bar.baz>>) [<<id:foo@bar.baz>>]
+<<id:foo@bar.baz>>...
+<<id:2+2=5>>
+<<id:=_-:/.[]@$%+>>
+<<id:abc>>)def
+<<id:ab"c>> def
+<<id:"abc">>def
+<<id:"ab""c">>def
+<<id:"ab c">>def
+<<id:"abc">>.def
+id:"abc
+"
+id:)
+id:
+cid:xxx
+<<mid:abc>> <<mid:abc/def>>
+<<mid:abc%20def>>
+<<mid:abc>>. <<mid:abc>>, <<mid:abc>>;
+EOF
+notmuch_date_sanitize < OUTPUT.raw > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "Show handles subprocess errors"
+cat > notmuch_fail <<EOF
+#!/bin/sh
+echo This is output
+echo This is an error >&2
+exit 1
+EOF
+chmod a+x notmuch_fail
+test_emacs "(let ((notmuch-command \"$PWD/notmuch_fail\"))
+              (with-current-buffer \"*Messages*\"
+                  (let ((inhibit-read-only t)) (erase-buffer)))
+              (condition-case err
+                  (notmuch-show \"*\")
+                (error (message \"%s\" (second err))))
+              (notmuch-test-wait)
+              (with-current-buffer \"*Messages*\"
+                 (test-output \"MESSAGES\"))
+              (with-current-buffer \"*Notmuch errors*\"
+                 (test-output \"ERROR\"))
+              (test-output))"
+test_expect_equal "$(notmuch_emacs_error_sanitize notmuch_fail OUTPUT MESSAGES ERROR)" "\
+=== OUTPUT ===
+=== MESSAGES ===
+This is an error (see *Notmuch errors* for more details)
+=== ERROR ===
+[XXX]
+This is an error
+command: YYY/notmuch_fail show --format\\=sexp --format-version\\=4 --decrypt\\=true --exclude\\=false \\' \\* \\'
+exit status: 1
+stderr:
+This is an error
+stdout:
+This is output"
+
+test_begin_subtest "text/enriched exploit mitigation"
+add_message '[content-type]="text/enriched"
+             [body]="
+<x-display><param>(when (progn (read-only-mode -1) (insert ?p ?0 ?w ?n ?e ?d)) nil)</param>test</x-display>
+"'
+test_emacs '(notmuch-show "id:'$gen_msg_id'")
+       (test-visible-output "OUTPUT.raw")'
+output=$(head -1 OUTPUT.raw|cut -f1-4 -d' ')
+test_expect_equal "$output" "Notmuch Test Suite <test_suite@notmuchmail.org>"
+
+test_done
diff --git a/test/T455-emacs-charsets.sh b/test/T455-emacs-charsets.sh
new file mode 100755 (executable)
index 0000000..cb1297c
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+
+test_description="emacs notmuch-show charset handling"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+
+UTF8_YEN=$'\xef\xbf\xa5'
+BIG5_YEN=$'\xa2\x44'
+
+# Add four messages with unusual encoding requirements:
+#
+# 1) text/plain in quoted-printable big5
+generate_message \
+    [id]=test-plain@example.com \
+    '[content-type]="text/plain; charset=big5"' \
+    '[content-transfer-encoding]=quoted-printable' \
+    '[body]="Yen: =A2=44"'
+
+# 2) text/plain in 8bit big5
+generate_message \
+    [id]=test-plain-8bit@example.com \
+    '[content-type]="text/plain; charset=big5"' \
+    '[content-transfer-encoding]=8bit' \
+    '[body]="Yen: '$BIG5_YEN'"'
+
+# 3) text/html in quoted-printable big5
+generate_message \
+    [id]=test-html@example.com \
+    '[content-type]="text/html; charset=big5"' \
+    '[content-transfer-encoding]=quoted-printable' \
+    '[body]="<html><body>Yen: =A2=44</body></html>"'
+
+# 4) application/octet-stream in quoted-printable of big5 text
+generate_message \
+    [id]=test-binary@example.com \
+    '[content-type]="application/octet-stream"' \
+    '[content-transfer-encoding]=quoted-printable' \
+    '[body]="Yen: =A2=44"'
+
+notmuch new > /dev/null
+
+# Test rendering
+
+test_begin_subtest "Text parts are decoded when rendering"
+test_emacs '(notmuch-show "id:test-plain@example.com")
+           (test-visible-output "OUTPUT.raw")'
+awk 'show {print} /^$/ {show=1}' < OUTPUT.raw > OUTPUT
+cat <<EOF >EXPECTED
+Yen: $UTF8_YEN
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "8bit text parts are decoded when rendering"
+test_emacs '(notmuch-show "id:test-plain-8bit@example.com")
+           (test-visible-output "OUTPUT.raw")'
+awk 'show {print} /^$/ {show=1}' < OUTPUT.raw > OUTPUT
+cat <<EOF >EXPECTED
+Yen: $UTF8_YEN
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "HTML parts are decoded when rendering"
+test_emacs '(notmuch-show "id:test-html@example.com")
+           (test-visible-output "OUTPUT.raw")'
+awk 'show {print} /^$/ {show=1}' < OUTPUT.raw > OUTPUT
+cat <<EOF >EXPECTED
+[ text/html ]
+Yen: $UTF8_YEN
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+# Test saving
+
+test_begin_subtest "Text parts are not decoded when saving"
+rm -f part
+test_emacs '(notmuch-show "id:test-plain@example.com")
+           (search-forward "Yen")
+           (let ((standard-input "\"part\""))
+              (notmuch-show-save-part))'
+cat <<EOF >EXPECTED
+Yen: $BIG5_YEN
+EOF
+test_expect_equal_file part EXPECTED
+
+test_begin_subtest "8bit text parts are not decoded when saving"
+rm -f part
+test_emacs '(notmuch-show "id:test-plain-8bit@example.com")
+           (search-forward "Yen")
+           (let ((standard-input "\"part\""))
+              (notmuch-show-save-part))'
+cat <<EOF >EXPECTED
+Yen: $BIG5_YEN
+EOF
+test_expect_equal_file part EXPECTED
+
+test_begin_subtest "HTML parts are not decoded when saving"
+rm -f part
+test_emacs '(notmuch-show "id:test-html@example.com")
+           (search-forward "Yen")
+           (let ((standard-input "\"part\""))
+              (notmuch-show-save-part))'
+cat <<EOF >EXPECTED
+<html><body>Yen: $BIG5_YEN</body></html>
+EOF
+test_expect_equal_file part EXPECTED
+
+test_begin_subtest "Binary parts are not decoded when saving"
+rm -f part
+test_emacs '(notmuch-show "id:test-binary@example.com")
+           (search-forward "application/")
+           (let ((standard-input "\"part\""))
+              (notmuch-show-save-part))'
+cat <<EOF >EXPECTED
+Yen: $BIG5_YEN
+EOF
+test_expect_equal_file part EXPECTED
+
+# Test message viewing
+
+test_begin_subtest "Text message are not decoded when viewing"
+test_emacs '(notmuch-show "id:test-plain@example.com")
+           (notmuch-show-view-raw-message)
+           (test-visible-output "OUTPUT.raw")'
+awk 'show {print} /^$/ {show=1}' < OUTPUT.raw > OUTPUT
+cat <<EOF >EXPECTED
+Yen: =A2=44
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "8bit text message are not decoded when viewing"
+test_emacs '(notmuch-show "id:test-plain-8bit@example.com")
+           (notmuch-show-view-raw-message)
+           (test-visible-output "OUTPUT.raw")'
+awk 'show {print} /^$/ {show=1}' < OUTPUT.raw > OUTPUT
+cat <<EOF >EXPECTED
+Yen: $BIG5_YEN
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T460-emacs-tree.sh b/test/T460-emacs-tree.sh
new file mode 100755 (executable)
index 0000000..cb2c90b
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+
+test_description="emacs tree view interface"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-tree.expected-output
+
+add_email_corpus
+
+test_begin_subtest "Basic notmuch-tree view in emacs"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+test_begin_subtest "Refreshed notmuch-tree view in emacs"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (notmuch-tree-refresh-view)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+# In the following tag tests we make sure the display is updated
+# correctly and, in a separate test, that the database is updated
+# correctly.
+
+test_begin_subtest "Tag message in notmuch tree view (display)"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (forward-line)
+           (notmuch-tree-tag (list "+test_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-tagged OUTPUT
+
+test_begin_subtest "Tag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_tag')
+test_expect_equal "$output" "id:877h1wv7mg.fsf@inf-8657.int-evry.fr"
+
+test_begin_subtest "Untag message in notmuch tree view"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           (forward-line)
+           (notmuch-tree-tag (list "-test_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+test_begin_subtest "Untag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_tag')
+test_expect_equal "$output" ""
+
+test_begin_subtest "Tag thread in notmuch tree view"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           ;; move to a sizable thread
+           (forward-line 26)
+           (notmuch-tree-tag-thread (list "+test_thread_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox-thread-tagged OUTPUT
+
+test_begin_subtest "Tag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_thread_tag')
+test_expect_equal "$output" \
+"id:87ocn0qh6d.fsf@yoom.home.cworth.org
+id:20091118005040.GA25380@dottiness.seas.harvard.edu
+id:yunaayketfm.fsf@aiko.keithp.com
+id:87fx8can9z.fsf@vertex.dottedmag
+id:20091117203301.GV3165@dottiness.seas.harvard.edu
+id:87iqd9rn3l.fsf@vertex.dottedmag
+id:20091117190054.GU3165@dottiness.seas.harvard.edu"
+
+test_begin_subtest "Untag thread in notmuch tree view"
+test_emacs '(notmuch-tree "tag:inbox")
+           (notmuch-test-wait)
+           ;; move to the same sizable thread as above
+           (forward-line 26)
+           (notmuch-tree-tag-thread (list "-test_thread_tag"))
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+test_begin_subtest "Untag message in notmuch tree view (database)"
+output=$(notmuch search --output=messages 'tag:test_thread_tag')
+test_expect_equal "$output" ""
+
+test_begin_subtest "Navigation of notmuch-hello to search results"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
+
+test_begin_subtest "Tree view of a single thread (from search)"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-tree-from-search-thread)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-single-thread OUTPUT
+
+test_begin_subtest "Tree view of a single thread (from show)"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-show-thread)
+           (notmuch-tree-from-show-current-query)
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-single-thread OUTPUT
+
+test_begin_subtest "Message window of tree view"
+test_emacs '(notmuch-hello)
+           (goto-char (point-min))
+           (re-search-forward "inbox")
+           (widget-button-press (1- (point)))
+           (notmuch-test-wait)
+           (notmuch-search-next-thread)
+           (notmuch-tree-from-search-thread)
+           (notmuch-test-wait)
+           (select-window notmuch-tree-message-window)
+           (test-output)
+           (delete-other-windows)'
+test_expect_equal_file $EXPECTED/notmuch-tree-show-window OUTPUT
+
+test_begin_subtest "Stash id"
+output=$(test_emacs '(notmuch-tree "id:1258498485-sup-142@elly")
+                    (notmuch-test-wait)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258498485-sup-142@elly\""
+
+test_begin_subtest "Move to next matching message"
+output=$(test_emacs '(notmuch-tree "from:cworth")
+                    (notmuch-test-wait)
+                    (notmuch-tree-next-matching-message)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:878we4qdqf.fsf@yoom.home.cworth.org\""
+
+test_begin_subtest "Move to next thread"
+output=$(test_emacs '(notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-next-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net\""
+
+test_begin_subtest "Move to previous thread"
+output=$(test_emacs '(notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:20091117190054.GU3165@dottiness.seas.harvard.edu\""
+
+test_begin_subtest "Move to previous previous thread"
+output=$(test_emacs '(notmuch-tree "tag:inbox")
+                    (notmuch-test-wait)
+                    (forward-line 26)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-tree-prev-thread)
+                    (notmuch-show-stash-message-id)')
+test_expect_equal "$output" "\"Stashed: id:1258493565-13508-1-git-send-email-keithp@keithp.com\""
+
+test_done
diff --git a/test/T470-missing-headers.sh b/test/T470-missing-headers.sh
new file mode 100755 (executable)
index 0000000..555fd4e
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+test_description='messages with missing headers'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# Notmuch requires at least one of from, subject, or to or it will
+# ignore the file.  Generate two messages so that together they cover
+# all possible missing headers.  We also give one of the messages a
+# date to ensure stable result ordering.
+
+cat <<EOF > "${MAIL_DIR}/msg-2"
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+
+Body
+EOF
+
+cat <<EOF > "${MAIL_DIR}/msg-1"
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+
+Body
+EOF
+
+NOTMUCH_NEW >/dev/null
+
+test_begin_subtest "Search: text"
+output=$(notmuch search '*' | notmuch_search_sanitize)
+test_expect_equal "$output" "\
+thread:XXX   2001-01-05 [1/1] ;  (inbox unread)
+thread:XXX   1970-01-01 [1/1] Notmuch Test Suite;  (inbox unread)"
+
+test_begin_subtest "Search: json"
+output=$(notmuch search --format=json '*' | notmuch_search_sanitize)
+test_expect_equal_json "$output" '
+[
+    {
+        "authors": "",
+        "date_relative": "2001-01-05",
+        "matched": 1,
+        "subject": "",
+        "tags": [
+            "inbox",
+            "unread"
+        ],
+        "thread": "XXX",
+        "timestamp": 978709437,
+        "total": 1,
+        "query": ["id:notmuch-sha1-7a6e4eac383ef958fcd3ebf2143db71b8ff01161", null]
+    },
+    {
+        "authors": "Notmuch Test Suite",
+        "date_relative": "1970-01-01",
+        "matched": 1,
+        "subject": "",
+        "tags": [
+            "inbox",
+            "unread"
+        ],
+        "thread": "XXX",
+        "timestamp": 0,
+        "total": 1,
+        "query": ["id:notmuch-sha1-ca55943aff7a72baf2ab21fa74fab3d632401334", null]
+    }
+]'
+
+test_begin_subtest "Show: text"
+output=$(notmuch show '*' | notmuch_show_sanitize)
+test_expect_equal "$output" "\
+\fmessage{ id:notmuch-sha1-7a6e4eac383ef958fcd3ebf2143db71b8ff01161 depth:0 match:1 excluded:0 filename:/XXX/mail/msg-2
+\fheader{
+ (2001-01-05) (inbox unread)
+Subject: (null)
+From: (null)
+To: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Fri, 05 Jan 2001 15:43:57 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+Body
+\fpart}
+\fbody}
+\fmessage}
+\fmessage{ id:notmuch-sha1-ca55943aff7a72baf2ab21fa74fab3d632401334 depth:0 match:1 excluded:0 filename:/XXX/mail/msg-1
+\fheader{
+Notmuch Test Suite <test_suite@notmuchmail.org> (1970-01-01) (inbox unread)
+Subject: (null)
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Date: Thu, 01 Jan 1970 00:00:00 +0000
+\fheader}
+\fbody{
+\fpart{ ID: 1, Content-type: text/plain
+Body
+\fpart}
+\fbody}
+\fmessage}"
+
+test_begin_subtest "Show: json"
+output=$(notmuch show --format=json '*' | notmuch_json_show_sanitize)
+expected=$(notmuch_json_show_sanitize <<EOF
+[
+    [
+        [
+            {
+                "body": [
+                    {
+                        "content": "Body\n",
+                        "content-type": "text/plain",
+                        "id": 1
+                    }
+                ],
+                "date_relative": "2001-01-05",
+                "excluded": false,
+                "filename": ["YYYYY"],
+                "headers": {
+                    "Date": "Fri, 05 Jan 2001 15:43:57 +0000",
+                    "From": "",
+                    "Subject": "",
+                    "To": "Notmuch Test Suite <test_suite@notmuchmail.org>"
+                },
+                "id": "XXXXX",
+                "match": true,
+                "tags": [
+                    "inbox",
+                    "unread"
+                ],
+                "timestamp": 978709437
+            },
+            []
+        ]
+    ],
+    [
+        [
+            {
+                "body": [
+                    {
+                        "content": "Body\n",
+                        "content-type": "text/plain",
+                        "id": 1
+                    }
+                ],
+                "date_relative": "1970-01-01",
+                "excluded": false,
+                "filename": ["YYYYY"],
+                "headers": {
+                    "Date": "Thu, 01 Jan 1970 00:00:00 +0000",
+                    "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+                    "Subject": ""
+                },
+                "id": "XXXXX",
+                "match": true,
+                "tags": [
+                    "inbox",
+                    "unread"
+                ],
+                "timestamp": 0
+            },
+            []
+        ]
+    ]
+]
+EOF
+)
+test_expect_equal_json "$output" "$expected"
+
+test_done
diff --git a/test/T480-hex-escaping.sh b/test/T480-hex-escaping.sh
new file mode 100755 (executable)
index 0000000..2c5bbb6
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+test_description="hex encoding and decoding"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "round trip"
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
+$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "punctuation"
+tag1='comic_swear=$&^%$^%\\//-+$^%$'
+tag_enc1=$($TEST_DIRECTORY/hex-xcode --direction=encode "$tag1")
+test_expect_equal "$tag_enc1" "comic_swear=%24%26%5e%25%24%5e%25%5c%5c%2f%2f-+%24%5e%25%24"
+
+test_begin_subtest "round trip newlines"
+printf 'this\n tag\t has\n spaces\n' > EXPECTED.$test_count
+$TEST_DIRECTORY/hex-xcode --direction=encode  < EXPECTED.$test_count |\
+       $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "round trip 8bit chars"
+echo '%c3%91%c3%a5%c3%b0%c3%a3%c3%a5%c3%a9-%c3%8f%c3%8a' > EXPECTED.$test_count
+$TEST_DIRECTORY/hex-xcode --direction=decode  < EXPECTED.$test_count |\
+    $TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "round trip (in-place)"
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
+$TEST_DIRECTORY/hex-xcode --in-place --direction=encode < EXPECTED |\
+     $TEST_DIRECTORY/hex-xcode --in-place --direction=decode > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "punctuation (in-place)"
+tag1='comic_swear=$&^%$^%\\//-+$^%$'
+tag_enc1=$($TEST_DIRECTORY/hex-xcode --in-place --direction=encode "$tag1")
+test_expect_equal "$tag_enc1" "comic_swear=%24%26%5e%25%24%5e%25%5c%5c%2f%2f-+%24%5e%25%24"
+
+test_begin_subtest "round trip newlines (in-place)"
+printf 'this\n tag\t has\n spaces\n' > EXPECTED.$test_count
+$TEST_DIRECTORY/hex-xcode --in-place --direction=encode  < EXPECTED.$test_count |\
+    $TEST_DIRECTORY/hex-xcode --in-place --direction=decode > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_begin_subtest "round trip 8bit chars (in-place)"
+echo '%c3%91%c3%a5%c3%b0%c3%a3%c3%a5%c3%a9-%c3%8f%c3%8a' > EXPECTED.$test_count
+$TEST_DIRECTORY/hex-xcode --in-place --direction=decode  < EXPECTED.$test_count |\
+    $TEST_DIRECTORY/hex-xcode --in-place --direction=encode > OUTPUT.$test_count
+test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+
+test_done
diff --git a/test/T490-parse-time-string.sh b/test/T490-parse-time-string.sh
new file mode 100755 (executable)
index 0000000..d1c70cf
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+test_description="date/time parser module"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+# Sanity/smoke tests for the date/time parser independent of notmuch
+
+_date ()
+{
+    date -d "$*" +%s
+}
+
+_parse_time ()
+{
+    ${TEST_DIRECTORY}/parse-time --format=%s "$*"
+}
+
+test_begin_subtest "date(1) default format without TZ code"
+test_expect_equal "$(_parse_time Fri Aug 3 23:06:06 2012)" "$(_date Fri Aug 3 23:06:06 2012)"
+
+test_begin_subtest "date(1) --rfc-2822 format"
+test_expect_equal "$(_parse_time Fri, 03 Aug 2012 23:07:46 +0100)" "$(_date Fri, 03 Aug 2012 23:07:46 +0100)"
+
+test_begin_subtest "date(1) --rfc=3339=seconds format"
+test_expect_equal "$(_parse_time 2012-08-03 23:09:37+03:00)" "$(_date 2012-08-03 23:09:37+03:00)"
+
+test_begin_subtest "Date parser tests"
+REFERENCE=$(_date Tue Jan 11 12:13:14 +0000 2011)
+cat <<EOF > INPUT
+now          ==> Tue Jan 11 12:13:14 +0000 2011
+2010-1-1     ==> ERROR: DATEFORMAT
+Jan 2        ==> Sun Jan 02 12:13:14 +0000 2011
+Mon          ==> Mon Jan 10 12:13:14 +0000 2011
+last Friday  ==> ERROR: FORMAT
+2 hours ago  ==> Tue Jan 11 10:13:14 +0000 2011
+last month   ==> Sat Dec 11 12:13:14 +0000 2010
+month ago    ==> Sat Dec 11 12:13:14 +0000 2010
+two mo       ==> Thu Nov 11 12:13:14 +0000 2010
+3M           ==> Mon Oct 11 12:13:14 +0000 2010
+4-mont       ==> Sat Sep 11 12:13:14 +0000 2010
+5m           ==> Tue Jan 11 12:08:14 +0000 2011
+dozen mi     ==> Tue Jan 11 12:01:14 +0000 2011
+8am          ==> Tue Jan 11 08:00:00 +0000 2011
+monday       ==> Mon Jan 10 12:13:14 +0000 2011
+yesterday    ==> Mon Jan 10 12:13:14 +0000 2011
+tomorrow     ==> ERROR: KEYWORD
+             ==> Tue Jan 11 12:13:14 +0000 2011 # empty string is reference time
+
+Aug 3 23:06:06 2012             ==> Fri Aug 03 23:06:06 +0000 2012 # date(1) default format without TZ code
+Fri, 03 Aug 2012 23:07:46 +0100 ==> Fri Aug 03 22:07:46 +0000 2012 # rfc-2822
+2012-08-03 23:09:37+03:00       ==> Fri Aug 03 20:09:37 +0000 2012 # rfc-3339 seconds
+
+10:30:40     ==> Tue Jan 11 10:30:40 +0000 2011
+10:30:40     ==^> Tue Jan 11 10:30:40 +0000 2011
+10:30:40     ==^^> Tue Jan 11 10:30:40 +0000 2011
+10:30:40     ==_> Tue Jan 11 10:30:40 +0000 2011
+
+10s           ==> Tue Jan 11 12:13:04 +0000 2011
+19701223s     ==> Fri May 28 11:39:31 +0000 2010
+19701223      ==> Wed Dec 23 12:13:14 +0000 1970
+
+19701223 +0100 ==> Wed Dec 23 12:13:14 +0000 1970 # Timezone is ignored without an error
+
+today ==^^> Wed Jan 12 00:00:00 +0000 2011
+today ==^> Tue Jan 11 23:59:59 +0000 2011
+today ==_> Tue Jan 11 00:00:00 +0000 2011
+
+this week ==^^> Sun Jan 16 00:00:00 +0000 2011
+this week ==^> Sat Jan 15 23:59:59 +0000 2011
+this week ==_> Sun Jan 09 00:00:00 +0000 2011
+
+two months ago ==> Thu Nov 11 12:13:14 +0000 2010
+two months ==> Thu Nov 11 12:13:14 +0000 2010
+
+@1348569850 ==> Tue Sep 25 10:44:10 +0000 2012
+@10 ==> Thu Jan 01 00:00:10 +0000 1970
+EOF
+
+${TEST_DIRECTORY}/parse-time --ref=${REFERENCE} < INPUT > OUTPUT
+test_expect_equal_file INPUT OUTPUT
+
+test_begin_subtest "Second rounding tests"
+REFERENCE=$(_date Tue Jan 11 12:13:14 +0000 2011)
+cat <<EOF > INPUT
+9:15         ==> Tue Jan 11 09:15:14 +0000 2011
+12:34        ==> Tue Jan 11 12:34:14 +0000 2011
+10:30        ==> Tue Jan 11 10:30:14 +0000 2011
+10:30        ==^> Tue Jan 11 10:30:59 +0000 2011
+10:30        ==^^> Tue Jan 11 10:31:00 +0000 2011
+10:30        ==_> Tue Jan 11 10:30:00 +0000 2011
+EOF
+${TEST_DIRECTORY}/parse-time --ref=${REFERENCE} < INPUT > OUTPUT
+test_expect_equal_file INPUT OUTPUT
+
+test_done
diff --git a/test/T500-search-date.sh b/test/T500-search-date.sh
new file mode 100755 (executable)
index 0000000..f84b096
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+test_description="date:since..until queries"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "Absolute date range"
+output=$(notmuch search date:2010-12-16..12/16/2010 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+
+test_begin_subtest "Absolute date range with 'same' operator"
+output=$(notmuch search date:2010-12-16..! | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+
+test_begin_subtest "Absolute date field"
+output=$(notmuch search date:2010-12-16 | notmuch_search_sanitize)
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal "$output" "thread:XXX   2010-12-16 [1/1] Olivier Berger; Essai accentué (inbox unread)"
+
+test_begin_subtest "Absolute time range with TZ"
+notmuch search date:18-Nov-2009_02:19:26-0800..2009-11-18_04:49:52-06:00 | notmuch_search_sanitize > OUTPUT
+cat <<EOF >EXPECTED
+thread:XXX   2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+thread:XXX   2009-11-18 [1/2] Carl Worth| Jan Janak; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+thread:XXX   2009-11-18 [1/3(4)] Carl Worth| Aron Griffis, Keith Packard; [notmuch] archive (inbox unread)
+thread:XXX   2009-11-18 [1/2] Carl Worth| Keith Packard; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T510-thread-replies.sh b/test/T510-thread-replies.sh
new file mode 100755 (executable)
index 0000000..5d6bea7
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2013 Aaron Ecay
+#
+
+test_description='test of proper handling of in-reply-to and references headers'
+
+# This test makes sure that the thread structure in the notmuch
+# database is constructed properly, even in the presence of
+# non-RFC-compliant headers'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "Use References when In-Reply-To is broken"
+add_message '[id]="foo@one.com"' \
+    '[subject]=one'
+add_message '[in-reply-to]="mumble"' \
+    '[references]="<foo@one.com>"' \
+    '[subject]="Re: one"'
+output=$(notmuch show --format=json 'subject:one' | notmuch_json_show_sanitize)
+expected='[[[{"id": "foo@one.com",
+ "match": true,
+ "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 978709437,
+ "date_relative": "2001-01-05",
+ "tags": ["inbox", "unread"],
+ "headers": {"Subject": "one",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"},
+ "body": [{"id": 1,
+ "content-type": "text/plain",
+ "content": "This is just a test message (#1)\n"}]},
+ [[{"id": "msg-002@notmuch-test-suite",
+ "match": true, "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 978709437, "date_relative": "2001-01-05",
+ "tags": ["inbox", "unread"], "headers": {"Subject": "Re: one",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"},
+ "body": [{"id": 1, "content-type": "text/plain",
+ "content": "This is just a test message (#2)\n"}]}, []]]]]]'
+expected=`echo "$expected" | notmuch_json_show_sanitize`
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest "Prefer References to dodgy In-Reply-To"
+add_message '[id]="foo@two.com"' \
+    '[subject]=two'
+add_message '[in-reply-to]="Your message of December 31 1999 <bar@baz.com>"' \
+    '[references]="<foo@two.com>"' \
+    '[subject]="Re: two"'
+output=$(notmuch show --format=json 'subject:two' | notmuch_json_show_sanitize)
+expected='[[[{"id": "foo@two.com",
+ "match": true, "excluded": false,
+ "filename": ["YYYYY"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "two",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"},
+ "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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "Re: two",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"},
+ "body": [{"id": 1,
+ "content-type": "text/plain", "content": "This is just a test message (#4)\n"}]},
+ []]]]]]'
+expected=`echo "$expected" | notmuch_json_show_sanitize`
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest "Use In-Reply-To when no References"
+add_message '[id]="foo@three.com"' \
+    '[subject]="three"'
+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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "three",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "Re: three",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1,
+ "content-type": "text/plain", "content": "This is just a test message (#6)\n"}]},
+ []]]]]]'
+expected=`echo "$expected" | notmuch_json_show_sanitize`
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest "Use last Reference when In-Reply-To is dodgy"
+add_message '[id]="foo@four.com"' \
+    '[subject]="four"'
+add_message '[id]="bar@four.com"' \
+    '[subject]="not-four"'
+add_message '[in-reply-to]="<baz@four.com> (RFC822 4lyfe)"' \
+    '[references]="<baz@four.com> <foo@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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "four",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "neither",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "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"],
+ "timestamp": 978709437, "date_relative": "2001-01-05", "tags": ["inbox", "unread"],
+ "headers": {"Subject": "not-four",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "Fri, 05 Jan 2001 15:43:57 +0000"}, "body": [{"id": 1,
+ "content-type": "text/plain", "content": "This is just a test message (#8)\n"}]}, []]]]'
+expected=`echo "$expected" | notmuch_json_show_sanitize`
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest "Ignore garbage at the end of References"
+add_message '[id]="foo@five.com"' \
+    '[subject]="five"'
+add_message '[id]="bar@five.com"' \
+    '[references]="<foo@five.com> (garbage)"' \
+    '[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",
+ "tags": ["inbox", "unread"], "headers": {"Subject": "five",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "GENERATED_DATE"}, "body": [{"id": 1,
+ "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",
+ "tags": ["inbox", "unread"],
+ "headers": {"Subject": "not-five",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "GENERATED_DATE"},
+ "body": [{"id": 1, "content-type": "text/plain",
+ "content": "This is just a test message (#11)\n"}]}, []]]]]]'
+expected=`echo "$expected" | notmuch_json_show_sanitize`
+test_expect_equal_json "$output" "$expected"
+
+add_email_corpus threading
+
+test_begin_subtest "reply to ghost"
+notmuch show --entire-thread=true id:000-real-root@example.org | grep ^Subject: | head -1  > OUTPUT
+cat <<EOF > EXPECTED
+Subject: root message
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reply to ghost (tree view)"
+test_emacs '(notmuch-tree "id:000-real-root@example.org")
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+cat <<EOF > EXPECTED
+  2016-06-17  Alice                 ┬►root message                                        (inbox unread)
+  2016-06-18  Alice                 ╰┬►child message                                      (inbox unread)
+  2016-06-17  Mallory                ├─►fake root message                                 (inbox unread)
+  2016-06-18  Alice                  ├┬►grand-child message                               (inbox unread)
+  2016-06-18  Alice                  │╰─►great grand-child message                        (inbox unread)
+  2016-06-18  Daniel                 ╰─►grand-child message 2                             (inbox unread)
+End of search results.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reply to ghost (RT)"
+notmuch show --entire-thread=true id:87bmc6lp3h.fsf@len.workgroup | grep ^Subject: | head -1  > OUTPUT
+cat <<EOF > EXPECTED
+Subject: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reply to ghost (RT/tree view)"
+test_emacs '(notmuch-tree "id:87bmc6lp3h.fsf@len.workgroup")
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+cat <<EOF > EXPECTED
+  2016-06-19  Gregor Zattler       ┬┬►FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx                (inbox unread)
+  2016-06-19   via RT              │╰─►[support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de #33575] AutoReply: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx (inbox unread)
+  2016-06-26   via RT              ╰─►[support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de #33575] Resolved: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx (inbox unread)
+End of search results.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "trusting reply-to (tree view)"
+test_emacs '(notmuch-tree "id:B00-root@example.org")
+           (notmuch-test-wait)
+           (test-output)
+           (delete-other-windows)'
+cat <<EOF > EXPECTED
+  2016-06-17  Alice                 ┬►root message                                        (inbox unread)
+  2016-06-18  Alice                 ╰┬►child message                                      (inbox unread)
+  2016-06-18  Alice                  ╰─►grand-child message                               (inbox unread)
+End of search results.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T520-show.sh b/test/T520-show.sh
new file mode 100755 (executable)
index 0000000..1622265
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+test_description='"notmuch show"'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "exit code for show invalid query"
+notmuch show foo..
+exit_code=$?
+test_expect_equal 1 $exit_code
+
+test_done
diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh
new file mode 100755 (executable)
index 0000000..69ebec6
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+test_description="database upgrade"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+dbtarball=database-v1.tar.xz
+
+# XXX: Accomplish the same with test lib helpers
+if [ ! -e ${TEST_DIRECTORY}/test-databases/${dbtarball} ]; then
+    test_subtest_missing_external_prereq_["${dbtarball} - fetch with 'make download-test-databases'"]=t
+fi
+
+test_begin_subtest "database checksum"
+test_expect_success \
+    '( cd $TEST_DIRECTORY/test-databases &&
+       sha256sum --quiet --check --status ${dbtarball}.sha256 )'
+
+tar xf $TEST_DIRECTORY/test-databases/${dbtarball} -C ${MAIL_DIR} --strip-components=1
+
+test_begin_subtest "folder: search does not work with old database version"
+output=$(notmuch search folder:foo)
+test_expect_equal "$output" ""
+
+test_begin_subtest "path: search does not work with old database version"
+output=$(notmuch search path:foo)
+test_expect_equal "$output" ""
+
+test_begin_subtest "pre upgrade dump"
+test_expect_success 'notmuch dump | sort > pre-upgrade-dump'
+
+test_begin_subtest "database upgrade from format version 1"
+output=$(notmuch new | sed -e 's/^Backing up tags to .*$/Backing up tags to FILENAME/')
+test_expect_equal "$output" "\
+Welcome to a new version of notmuch! Your database will now be upgraded.
+This process is safe to interrupt.
+Backing up tags to FILENAME
+Your notmuch database has now been upgraded.
+No new mail."
+
+test_begin_subtest "tag backup matches pre-upgrade dump"
+gunzip -c ${MAIL_DIR}/.notmuch/dump-*.gz | sort > backup-dump
+test_expect_equal_file pre-upgrade-dump backup-dump
+
+test_begin_subtest "folder: no longer matches in the middle of path"
+output=$(notmuch search folder:baz)
+test_expect_equal "$output" ""
+
+test_begin_subtest "folder: search"
+output=$(notmuch search --output=files folder:foo | notmuch_search_files_sanitize | sort)
+test_expect_equal "$output" "MAIL_DIR/foo/06:2,
+MAIL_DIR/foo/cur/07:2,
+MAIL_DIR/foo/cur/08:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/foo/new/09:2,
+MAIL_DIR/foo/new/10:2,"
+
+test_begin_subtest "top level folder: search"
+output=$(notmuch search --output=files folder:'""' | notmuch_search_files_sanitize | sort)
+# bar/18:2, is a duplicate of cur/51:2,
+test_expect_equal "$output" "MAIL_DIR/01:2,
+MAIL_DIR/02:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/cur/29:2,
+MAIL_DIR/cur/30:2,
+MAIL_DIR/cur/31:2,
+MAIL_DIR/cur/32:2,
+MAIL_DIR/cur/33:2,
+MAIL_DIR/cur/34:2,
+MAIL_DIR/cur/35:2,
+MAIL_DIR/cur/36:2,
+MAIL_DIR/cur/37:2,
+MAIL_DIR/cur/38:2,
+MAIL_DIR/cur/39:2,
+MAIL_DIR/cur/40:2,
+MAIL_DIR/cur/41:2,
+MAIL_DIR/cur/42:2,
+MAIL_DIR/cur/43:2,
+MAIL_DIR/cur/44:2,
+MAIL_DIR/cur/45:2,
+MAIL_DIR/cur/46:2,
+MAIL_DIR/cur/47:2,
+MAIL_DIR/cur/48:2,
+MAIL_DIR/cur/49:2,
+MAIL_DIR/cur/50:2,
+MAIL_DIR/cur/51:2,
+MAIL_DIR/cur/52:2,
+MAIL_DIR/cur/53:2,
+MAIL_DIR/new/04:2,"
+
+test_begin_subtest "path: search"
+output=$(notmuch search --output=files path:"bar" | notmuch_search_files_sanitize | sort)
+# cur/51:2, is a duplicate of bar/18:2,
+test_expect_equal "$output" "MAIL_DIR/bar/17:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/cur/51:2,"
+
+test_begin_subtest "top level path: search"
+output=$(notmuch search --output=files path:'""' | notmuch_search_files_sanitize | sort)
+test_expect_equal "$output" "MAIL_DIR/01:2,
+MAIL_DIR/02:2,"
+
+test_begin_subtest "recursive path: search"
+output=$(notmuch search --output=files path:"bar/**" | notmuch_search_files_sanitize | sort)
+# cur/51:2, is a duplicate of bar/18:2,
+test_expect_equal "$output" "MAIL_DIR/bar/17:2,
+MAIL_DIR/bar/18:2,
+MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/bar/baz/23:2,
+MAIL_DIR/bar/baz/24:2,
+MAIL_DIR/bar/baz/cur/25:2,
+MAIL_DIR/bar/baz/cur/26:2,
+MAIL_DIR/bar/baz/new/27:2,
+MAIL_DIR/bar/baz/new/28:2,
+MAIL_DIR/bar/cur/19:2,
+MAIL_DIR/bar/cur/20:2,
+MAIL_DIR/bar/new/21:2,
+MAIL_DIR/bar/new/22:2,
+MAIL_DIR/cur/51:2,"
+
+test_done
diff --git a/test/T550-db-features.sh b/test/T550-db-features.sh
new file mode 100755 (executable)
index 0000000..9d5a9e7
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+test_description="database version and feature compatibility"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "future database versions abort open"
+${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
+output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
+rm -rf ${MAIL_DIR}/.notmuch
+test_expect_equal "$output" "\
+Error: Notmuch database at FILENAME
+       has a newer database format version (9999) than supported by this
+       version of notmuch (3)."
+
+test_begin_subtest "unknown 'rw' feature aborts read/write open"
+${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw'
+output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
+rm -rf ${MAIL_DIR}/.notmuch
+test_expect_equal "$output" "\
+Error: Notmuch database at FILENAME
+       requires features (test feature)
+       not supported by this version of notmuch."
+
+test_begin_subtest "unknown 'rw' feature aborts read-only open"
+${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw'
+output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
+rm -rf ${MAIL_DIR}/.notmuch
+test_expect_equal "$output" "\
+Error: Notmuch database at FILENAME
+       requires features (test feature)
+       not supported by this version of notmuch."
+
+test_begin_subtest "unknown 'w' feature aborts read/write open"
+${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw'
+output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
+rm -rf ${MAIL_DIR}/.notmuch
+test_expect_equal "$output" "\
+Error: Notmuch database at FILENAME
+       requires features (test feature)
+       not supported by this version of notmuch."
+
+test_begin_subtest "unknown 'w' feature does not abort read-only open"
+${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw'
+output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')
+rm -rf ${MAIL_DIR}/.notmuch
+test_expect_equal "$output" ""
+
+test_done
diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh
new file mode 100755 (executable)
index 0000000..06a6b86
--- /dev/null
@@ -0,0 +1,321 @@
+#!/usr/bin/env bash
+test_description="error reporting for library"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "building database"
+test_expect_success "NOTMUCH_NEW"
+
+test_begin_subtest "Open null pointer"
+test_C <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_database_t *db;
+    notmuch_status_t stat;
+    stat = notmuch_database_open (NULL, 0, 0);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Cannot open a database for a NULL path.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Open relative path"
+test_C <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_database_t *db;
+    notmuch_status_t stat;
+    stat = notmuch_database_open ("./nonexistent/foo", 0, 0);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Database path must be absolute.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Create database in relative path"
+test_C <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_database_t *db;
+    notmuch_status_t stat;
+    stat = notmuch_database_create ("./nonexistent/foo", &db);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Database path must be absolute.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Open nonexistent database"
+test_C ${PWD}/nonexistent/foo <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_database_t *db;
+    notmuch_status_t stat;
+    stat = notmuch_database_open (argv[1], 0, 0);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error opening database at CWD/nonexistent/foo/.notmuch: No such file or directory
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "create NULL path"
+test_C <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_status_t stat;
+    stat = notmuch_database_create (NULL, NULL);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Cannot create a database for a NULL path.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Create database in nonexistent directory"
+test_C ${PWD}/nonexistent/foo<<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+    notmuch_database_t *db;
+    notmuch_status_t stat;
+    stat = notmuch_database_create (argv[1], &db);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error: Cannot create database at CWD/nonexistent/foo: No such file or directory.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Write to read-only database"
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, &db);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d\n", stat);
+   }
+   stat = notmuch_database_index_file (db, "/dev/null", NULL, NULL);
+   if (stat)
+       fputs (notmuch_database_status_string (db), stderr);
+
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Cannot write to a read-only database.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Add non-existent file"
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d\n", stat);
+   }
+   stat = notmuch_database_index_file (db, "./nonexistent", NULL, NULL);
+   if (stat) {
+       char *status_string = notmuch_database_status_string (db);
+       if (status_string) fputs (status_string, stderr);
+   }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+Error opening ./nonexistent: No such file or directory
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "compact, overwriting existing backup"
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <notmuch.h>
+static void
+status_cb (const char *msg, void *closure)
+{
+    printf ("%s\n", msg);
+}
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   stat = notmuch_database_compact (argv[1], argv[1], status_cb, NULL);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+Path already exists: MAIL_DIR
+
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <talloc.h>
+#include <notmuch.h>
+
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   char *path;
+   char *msg = NULL;
+   int fd;
+
+   stat = notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db, &msg);
+   if (stat != NOTMUCH_STATUS_SUCCESS) {
+     fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+     exit (1);
+   }
+   path = talloc_asprintf (db, "%s/.notmuch/xapian/postlist.${db_ending}", argv[1]);
+   fd = open(path,O_WRONLY|O_TRUNC);
+   if (fd < 0) {
+       fprintf (stderr, "error opening %s\n", argv[1]);
+       exit (1);
+   }
+EOF
+cat <<'EOF' > c_tail
+   if (stat) {
+       const char *stat_str = notmuch_database_status_string (db);
+       if (stat_str)
+           fputs (stat_str, stderr);
+    }
+
+}
+EOF
+
+backup_database
+test_begin_subtest "Xapian exception finding message"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+   {
+       notmuch_message_t *message = NULL;
+       stat = notmuch_database_find_message (db, "id:nonexistent", &message);
+   }
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+A Xapian exception occurred finding message
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+backup_database
+test_begin_subtest "Xapian exception getting tags"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+   {
+       notmuch_tags_t *tags = NULL;
+       tags = notmuch_database_get_all_tags (db);
+       stat = (tags == NULL);
+   }
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+A Xapian exception occurred getting tags
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+backup_database
+test_begin_subtest "Xapian exception creating directory"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+   {
+       notmuch_directory_t *directory = NULL;
+       stat = notmuch_database_get_directory (db, "none/existing", &directory);
+   }
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+A Xapian exception occurred creating a directory
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+backup_database
+test_begin_subtest "Xapian exception searching messages"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+   {
+       notmuch_messages_t *messages = NULL;
+       notmuch_query_t *query=notmuch_query_create (db, "*");
+       stat = notmuch_query_search_messages (query, &messages);
+   }
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+A Xapian exception occurred performing query
+Query string was: *
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+backup_database
+test_begin_subtest "Xapian exception counting messages"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+   {
+       int count;
+       notmuch_query_t *query=notmuch_query_create (db, "id:87ocn0qh6d.fsf@yoom.home.cworth.org");
+       stat = notmuch_query_count_messages (query, &count);
+   }
+EOF
+sed 's/^\(A Xapian exception [^:]*\):.*$/\1/' < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+A Xapian exception occurred performing query
+Query string was: id:87ocn0qh6d.fsf@yoom.home.cworth.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+restore_database
+
+test_done
diff --git a/test/T570-revision-tracking.sh b/test/T570-revision-tracking.sh
new file mode 100755 (executable)
index 0000000..a59e7c9
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+test_description="database revision tracking"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "notmuch_database_get_revision"
+test_C ${MAIL_DIR} <<'EOF'
+#include <stdio.h>
+#include <string.h>
+#include <notmuch.h>
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_status_t stat;
+   unsigned long revision;
+   const char *uuid;
+
+   unsigned long rev;
+
+   stat = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY, &db);
+   if (stat)
+       fputs ("open failed\n", stderr);
+   revision = notmuch_database_get_revision (db, &uuid);
+   printf("%s\t%lu\n", uuid, revision);
+}
+EOF
+notmuch_uuid_sanitize < OUTPUT > CLEAN
+cat <<'EOF' >EXPECTED
+== stdout ==
+UUID   53
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED CLEAN
+
+grep '^[0-9a-f]' OUTPUT > INITIAL_OUTPUT
+
+test_begin_subtest "output of count matches test code"
+notmuch count --lastmod '*' | cut -f2-3 > OUTPUT
+test_expect_equal_file INITIAL_OUTPUT OUTPUT
+
+test_begin_subtest "modification count increases"
+before=$(notmuch count --lastmod '*' | cut -f3)
+notmuch tag +a-random-tag-8743632 '*'
+after=$(notmuch count --lastmod '*' | cut -f3)
+result=$(($before < $after))
+test_expect_equal 1 ${result}
+
+notmuch count --lastmod '*' | cut -f2 > UUID
+
+test_begin_subtest "search succeeds with correct uuid"
+test_expect_success "notmuch search --uuid=$(cat UUID) '*'"
+
+test_begin_subtest "uuid works as global option"
+test_expect_success "notmuch --uuid=$(cat UUID) search '*'"
+
+test_begin_subtest "uuid works as global option II"
+test_expect_code 1 "notmuch --uuid=this-is-no-uuid search '*'"
+
+test_begin_subtest "search fails with incorrect uuid"
+test_expect_code 1 "notmuch search --uuid=this-is-no-uuid '*'"
+
+test_begin_subtest "show succeeds with correct uuid"
+test_expect_success "notmuch show --uuid=$(cat UUID) '*'"
+
+test_begin_subtest "show fails with incorrect uuid"
+test_expect_code 1 "notmuch show --uuid=this-is-no-uuid '*'"
+
+test_begin_subtest "tag succeeds with correct uuid"
+test_expect_success "notmuch tag --uuid=$(cat UUID) +test '*'"
+
+test_begin_subtest "tag fails with incorrect uuid"
+test_expect_code 1 "notmuch tag --uuid=this-is-no-uuid '*' +test2"
+
+test_begin_subtest 'lastmod:0.. matches everything'
+total=$(notmuch count '*')
+modtotal=$(notmuch count lastmod:0..)
+test_expect_equal "$total" "$modtotal"
+
+test_begin_subtest 'lastmod:1000000.. matches nothing'
+modtotal=$(notmuch count lastmod:1000000..)
+test_expect_equal 0 "$modtotal"
+
+test_begin_subtest 'exclude one message using lastmod'
+lastmod=$(notmuch count --lastmod '*' | cut -f3)
+total=$(notmuch count '*')
+notmuch tag +4EFC743A.3060609@april.org id:4EFC743A.3060609@april.org
+subtotal=$(notmuch count lastmod:..$lastmod)
+result=$(($subtotal == $total-1))
+test_expect_equal 1 "$result"
+
+test_done
diff --git a/test/T580-thread-search.sh b/test/T580-thread-search.sh
new file mode 100755 (executable)
index 0000000..01aa3ef
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2015 David Bremner
+#
+
+test_description='test of searching by thread-id'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "Every message is found in exactly one thread"
+
+count=0
+success=0
+for id in $(notmuch search --output=messages '*'); do
+    count=$((count +1))
+    matches=$((`notmuch search --output=threads "$id" | wc -l`))
+    if [ "$matches" = 1 ]; then
+       success=$((success + 1))
+    fi
+done
+
+test_expect_equal "$count" "$success"
+
+test_begin_subtest "roundtripping message-ids via thread-ids"
+
+count=0
+success=0
+for id in $(notmuch search --output=messages '*'); do
+    count=$((count +1))
+    thread=$(notmuch search --output=threads "$id")
+    matched=$(notmuch search --output=messages "$thread" | grep "$id")
+    if [ "$matched" = "$id" ]; then
+       success=$((success + 1))
+    fi
+done
+
+test_expect_equal "$count" "$success"
+
+
+test_done
diff --git a/test/T585-thread-subquery.sh b/test/T585-thread-subquery.sh
new file mode 100755 (executable)
index 0000000..bf9894d
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2018 David Bremner
+#
+
+test_description='test of searching by using thread subqueries'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "Basic query that matches no messages"
+count=$(notmuch count from:keithp and to:keithp)
+test_expect_equal 0 "$count"
+
+test_begin_subtest "Same query against threads"
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
+    test_subtest_known_broken
+fi
+notmuch search thread:{from:keithp} and thread:{to:keithp} | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Mix thread and non-threads query"
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
+    test_subtest_known_broken
+fi
+notmuch search thread:{from:keithp} and to:keithp | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX   2009-11-18 [1/7] Lars Kellogg-Stedman| Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Compound subquery"
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
+    test_subtest_known_broken
+fi
+notmuch search 'thread:"{from:keithp and date:2009}" and thread:{to:keithp}' | notmuch_search_sanitize > OUTPUT
+cat<<EOF > EXPECTED
+thread:XXX   2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Syntax/quoting error in subquery"
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
+    test_subtest_known_broken
+fi
+notmuch search 'thread:{from:keithp and date:2009} and thread:{to:keithp}' 1>OUTPUT 2>&1
+cat<<EOF > EXPECTED
+notmuch search: A Xapian exception occurred
+A Xapian exception occurred parsing query: missing } in '{from:keithp'
+Query string was: thread:{from:keithp and date:2009} and thread:{to:keithp}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh
new file mode 100755 (executable)
index 0000000..46f3a76
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+test_description="library config API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+cat <<EOF > c_head
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   char *val;
+   notmuch_status_t stat;
+
+   EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+
+EOF
+
+cat <<EOF > c_tail
+   EXPECT0(notmuch_database_destroy(db));
+}
+EOF
+
+test_begin_subtest "notmuch_database_{set,get}_config"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   EXPECT0(notmuch_database_set_config (db, "testkey1", "testvalue1"));
+   EXPECT0(notmuch_database_set_config (db, "testkey2", "testvalue2"));
+   EXPECT0(notmuch_database_get_config (db, "testkey1", &val));
+   printf("testkey1 = %s\n", val);
+   EXPECT0(notmuch_database_get_config (db, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1 = testvalue1
+testkey2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "notmuch_database_get_config_list: empty list"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_get_config_list (db, "nonexistent", &list));
+   printf("valid = %d\n", notmuch_config_list_valid (list));
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+valid = 0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "notmuch_database_get_config_list: all pairs"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_set_config (db, "zzzafter", "afterval"));
+   EXPECT0(notmuch_database_set_config (db, "aaabefore", "beforeval"));
+   EXPECT0(notmuch_database_get_config_list (db, "", &list));
+   for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+      printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+   }
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+aaabefore beforeval
+testkey1 testvalue1
+testkey2 testvalue2
+zzzafter afterval
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_get_config_list: one prefix"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_config_list_t *list;
+   EXPECT0(notmuch_database_get_config_list (db, "testkey", &list));
+   for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+      printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+   }
+   notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1 testvalue1
+testkey2 testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump config"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+    EXPECT0(notmuch_database_set_config (db, "key with spaces", "value, with, spaces!"));
+}
+EOF
+notmuch dump --include=config >OUTPUT
+cat <<'EOF' >EXPECTED
+#notmuch-dump batch-tag:3 config
+#@ aaabefore beforeval
+#@ key%20with%20spaces value,%20with,%20spaces%21
+#@ testkey1 testvalue1
+#@ testkey2 testvalue2
+#@ zzzafter afterval
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore config"
+notmuch dump --include=config >EXPECTED
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+    EXPECT0(notmuch_database_set_config (db, "testkey1", "mutatedvalue"));
+}
+EOF
+notmuch restore --include=config <EXPECTED
+notmuch dump --include=config >OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T590-thread-breakage.sh b/test/T590-thread-breakage.sh
new file mode 100755 (executable)
index 0000000..aeb82cf
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2016 Daniel Kahn Gillmor
+#
+
+test_description='thread breakage during reindexing'
+
+# notmuch uses ghost documents to track messages we have seen references
+# to but have never seen.  Regardless of the order of delivery, message
+# deletion, and reindexing, the list of ghost messages for a given
+# stored corpus should not vary, so that threads can be reassmebled
+# cleanly.
+#
+# In practice, we accept a small amount of variation (and therefore
+# traffic pattern metadata leakage to be stored in the index) for the
+# sake of efficiency.
+#
+# This test also embeds some subtests to ensure that indexing actually
+# works properly and attempted fixes to threading issues do not break
+# the expected contents of the index.
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+message_a() {
+    mkdir -p ${MAIL_DIR}/cur
+    cat > ${MAIL_DIR}/cur/a <<EOF
+Subject: First message
+Message-ID: <a@example.net>
+From: Alice <alice@example.net>
+To: Bob <bob@example.net>
+Date: Thu, 31 Mar 2016 20:10:00 -0400
+
+This is the first message in the thread.
+Apple
+EOF
+}
+
+message_b() {
+    mkdir -p ${MAIL_DIR}/cur
+    cat > ${MAIL_DIR}/cur/b <<EOF
+Subject: Second message
+Message-ID: <b@example.net>
+In-Reply-To: <a@example.net>
+References: <a@example.net>
+From: Bob <bob@example.net>
+To: Alice <alice@example.net>
+Date: Thu, 31 Mar 2016 20:15:00 -0400
+
+This is the second message in the thread.
+Banana
+EOF
+}
+
+
+test_content_count() {
+    test_begin_subtest "${3:-looking for $2 instance of '$1'}"
+    count=$(notmuch count --output=threads "$1")
+    test_expect_equal "$count" "$2"
+}
+
+test_thread_count() {
+    test_begin_subtest "${2:-Expecting $1 thread(s)}"
+    count=$(notmuch count --output=threads)
+    test_expect_equal "$count" "$1"
+}
+
+test_ghost_count() {
+    test_begin_subtest "${2:-Expecting $1 ghosts(s)}"
+    ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
+    test_expect_equal "$ghosts" "$1"
+}
+
+notmuch new >/dev/null
+
+test_thread_count 0 'There should be no threads initially'
+test_ghost_count 0 'There should be no ghosts initially'
+
+message_a
+notmuch new >/dev/null
+test_thread_count 1 'One message in: one thread'
+test_content_count apple 1
+test_content_count banana 0
+test_ghost_count 0
+
+message_b
+notmuch new >/dev/null
+test_thread_count 1 'Second message in the same thread: one thread'
+test_content_count apple 1
+test_content_count banana 1
+test_ghost_count 0
+
+rm -f ${MAIL_DIR}/cur/a
+notmuch new >/dev/null
+test_thread_count 1 'First message removed: still only one thread'
+test_content_count apple 0
+test_content_count banana 1
+test_ghost_count 1 'should be one ghost after first message removed'
+
+message_a
+notmuch new >/dev/null
+test_thread_count 1 'First message reappears: should return to the same thread'
+test_content_count apple 1
+test_content_count banana 1
+test_ghost_count 0
+
+rm -f ${MAIL_DIR}/cur/b
+notmuch new >/dev/null
+test_thread_count 1 'Removing second message: still only one thread'
+test_content_count apple 1
+test_content_count banana 0
+test_begin_subtest 'No ghosts should remain after deletion of second message'
+# this is known to fail; we are leaking ghost messages deliberately
+test_subtest_known_broken
+ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
+test_expect_equal "$ghosts" "0"
+
+rm -f ${MAIL_DIR}/cur/a
+notmuch new >/dev/null
+test_thread_count 0 'All messages gone: no threads'
+test_content_count apple 0
+test_content_count banana 0
+test_ghost_count 0 'No ghosts should remain after full thread deletion'
+
+test_done
diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh
new file mode 100755 (executable)
index 0000000..abaee3b
--- /dev/null
@@ -0,0 +1,74 @@
+#!/usr/bin/env bash
+test_description='named queries'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
+
+test_begin_subtest "error adding named query before initializing DB"
+test_expect_code 1 "notmuch config set query.test \"$QUERYSTR\""
+
+add_email_corpus
+
+test_begin_subtest "adding named query"
+test_expect_success "notmuch config set query.test \"$QUERYSTR\""
+
+test_begin_subtest "adding nested named query"
+QUERYSTR2="query:test and subject:Maildir"
+test_expect_success "notmuch config set query.test2 \"$QUERYSTR2\""
+
+test_begin_subtest "retrieve named query"
+output=$(notmuch config get query.test)
+test_expect_equal "$QUERYSTR" "$output"
+
+test_begin_subtest "List all queries"
+notmuch config list | grep ^query | notmuch_config_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+query.test=date:2009-11-18..2009-11-18 and tag:unread
+query.test2=query:test and subject:Maildir
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump named queries"
+notmuch dump | grep '^#@' > OUTPUT
+cat<<EOF > QUERIES.BEFORE
+#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread
+#@ query.test2 query%3atest%20and%20subject%3aMaildir
+EOF
+test_expect_equal_file QUERIES.BEFORE OUTPUT
+
+test_begin_subtest "delete named queries"
+notmuch dump > BEFORE
+notmuch config set query.test
+notmuch dump | grep '^#@' > OUTPUT
+cat<<EOF > EXPECTED
+#@ query.test2 query%3atest%20and%20subject%3aMaildir
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore named queries"
+notmuch restore < BEFORE
+notmuch dump | grep '^#@' > OUTPUT
+test_expect_equal_file QUERIES.BEFORE OUTPUT
+
+test_begin_subtest "search named query"
+notmuch search query:test > OUTPUT
+notmuch search $QUERYSTR > EXPECTED
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search named query with other terms"
+notmuch search query:test and subject:Maildir > OUTPUT
+notmuch search $QUERYSTR and subject:Maildir > EXPECTED
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search nested named query"
+notmuch search query:test2 > OUTPUT
+notmuch search $QUERYSTR2 > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh
new file mode 100755 (executable)
index 0000000..0abef82
--- /dev/null
@@ -0,0 +1,318 @@
+#!/usr/bin/env bash
+test_description="message property API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+cat <<EOF > c_head
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <notmuch-test.h>
+
+void print_properties (notmuch_message_t *message, const char *prefix, notmuch_bool_t exact) {
+    notmuch_message_properties_t *list;
+    for (list = notmuch_message_get_properties (message, prefix, exact);
+         notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
+       printf("%s\n", notmuch_message_properties_value(list));
+    }
+    notmuch_message_properties_destroy (list);
+}
+
+int main (int argc, char** argv)
+{
+   notmuch_database_t *db;
+   notmuch_message_t *message = NULL;
+   const char *val;
+   notmuch_status_t stat;
+
+   EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+   EXPECT0(notmuch_database_find_message(db, "4EFC743A.3060609@april.org", &message));
+   if (message == NULL) {
+       fprintf (stderr, "unable to find message");
+       exit (1);
+   }
+EOF
+
+cat <<EOF > c_tail
+   EXPECT0(notmuch_database_destroy(db));
+}
+EOF
+
+test_begin_subtest "notmuch_message_{add,get,remove}_property"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   EXPECT0(notmuch_message_add_property (message, "testkey1", "testvalue1"));
+   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+   printf("testkey1[1] = %s\n", val);
+   EXPECT0(notmuch_message_add_property (message, "testkey2", "this value has spaces and = sign"));
+   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+   printf("testkey1[2] = %s\n", val);
+   EXPECT0(notmuch_message_get_property (message, "testkey1", &val));
+
+   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+
+   /* Add second value for key */
+   EXPECT0(notmuch_message_add_property (message, "testkey2", "zztestvalue3"));
+   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+
+   /* remove first value for key */
+   EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));
+   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+
+   /* remove non-existant value for key */
+   EXPECT0(notmuch_message_remove_property (message, "testkey2", "this value has spaces and = sign"));
+   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+   printf("testkey2 = %s\n", val);
+
+   /* remove only value for key */
+   EXPECT0(notmuch_message_remove_property (message, "testkey2", "zztestvalue3"));
+   EXPECT0(notmuch_message_get_property (message, "testkey2", &val));
+   printf("testkey2 = %s\n", val == NULL ? "NULL" : val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testkey1[1] = testvalue1
+testkey1[2] = testvalue1
+testkey2 = this value has spaces and = sign
+testkey2 = this value has spaces and = sign
+testkey2 = zztestvalue3
+testkey2 = zztestvalue3
+testkey2 = NULL
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_remove_all_properties"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_remove_all_properties (message, NULL));
+print_properties (message, "", FALSE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_get_properties: empty list"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+   notmuch_message_properties_t *list;
+   list = notmuch_message_get_properties (message, "nonexistent", TRUE);
+   printf("valid = %d\n", notmuch_message_properties_valid (list));
+   notmuch_message_properties_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+valid = 0
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_properties: one value"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+print_properties (message, "testkey1", TRUE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+testvalue1
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_properties: multiple values"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_add_property (message, "testkey1", "bob"));
+EXPECT0(notmuch_message_add_property (message, "testkey1", "testvalue2"));
+EXPECT0(notmuch_message_add_property (message, "testkey1", "alice"));
+print_properties (message, "testkey1", TRUE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+alice
+bob
+testvalue1
+testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_properties: prefix"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_add_property (message, "testkey3", "bob3"));
+EXPECT0(notmuch_message_add_property (message, "testkey3", "testvalue3"));
+EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
+print_properties (message, "testkey", FALSE);
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+alice
+bob
+testvalue1
+testvalue2
+alice3
+bob3
+testvalue3
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_message_properties: modify during iteration"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+    const char *keys[1000] = {NULL};
+    const char *vals[1000] = {NULL};
+    notmuch_message_properties_t *properties;
+    int i;
+
+    for (properties = notmuch_message_get_properties (message, "", FALSE), i=0;
+        notmuch_message_properties_valid (properties);
+        notmuch_message_properties_move_to_next (properties), i++)
+    {
+       const char *key, *value;
+
+       keys[i]=talloc_strdup(message,
+                   notmuch_message_properties_key (properties));
+        vals[i]=talloc_strdup(message,
+                   notmuch_message_properties_value (properties));
+
+       EXPECT0(notmuch_message_remove_property (message, keys[i], vals[i]));
+    }
+
+    print_properties (message, "", FALSE);
+
+    for (i = 0; keys[i] && vals[i]; i++) {
+        EXPECT0(notmuch_message_add_property (message, keys[i], vals[i]));
+    }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump message properties"
+cat <<EOF > PROPERTIES
+#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
+EOF
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with ="));
+EOF
+notmuch dump | grep '^#=' > OUTPUT
+test_expect_equal_file PROPERTIES OUTPUT
+
+test_begin_subtest "dump _only_ message properties"
+cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 properties
+#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
+EOF
+notmuch dump --include=properties > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "restore missing message property (single line)"
+notmuch dump | grep '^#=' > BEFORE1
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_remove_property (message, "testkey1", "bob"));
+EOF
+notmuch restore < BEFORE1
+notmuch dump | grep '^#=' > OUTPUT
+test_expect_equal_file PROPERTIES OUTPUT
+
+
+test_begin_subtest "restore missing message property (full dump)"
+notmuch dump > BEFORE2
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_remove_property (message, "testkey1", "bob"));
+EOF
+notmuch restore < BEFORE2
+notmuch dump | grep '^#=' > OUTPUT
+test_expect_equal_file PROPERTIES OUTPUT
+
+test_begin_subtest "restore clear extra message property"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+EXPECT0(notmuch_message_add_property (message, "testkey1", "charles"));
+EOF
+notmuch restore < BEFORE2
+notmuch dump | grep '^#=' > OUTPUT
+test_expect_equal_file PROPERTIES OUTPUT
+
+test_begin_subtest "test 'property:' queries: empty"
+notmuch search property:testkey1=charles > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest "test 'property:' queries: single message"
+notmuch search --output=messages property:testkey1=alice > OUTPUT
+cat <<EOF >EXPECTED
+id:4EFC743A.3060609@april.org
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "msg.get_property (python)"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE)
+msg = db.find_message("4EFC743A.3060609@april.org")
+print("testkey1 = {0}".format(msg.get_property("testkey1")))
+print("testkey3 = {0}".format(msg.get_property("testkey3")))
+EOF
+cat <<'EOF' > EXPECTED
+testkey1 = alice
+testkey3 = alice3
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "msg.get_properties (python)"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+msg = db.find_message("4EFC743A.3060609@april.org")
+for (key,val) in msg.get_properties("testkey1"):
+        print("{0} = {1}".format(key,val))
+EOF
+cat <<'EOF' > EXPECTED
+testkey1 = alice
+testkey1 = bob
+testkey1 = testvalue1
+testkey1 = testvalue2
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "msg.get_properties (python, prefix)"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+msg = db.find_message("4EFC743A.3060609@april.org")
+for (key,val) in msg.get_properties("testkey"):
+        print("{0} = {1}".format(key,val))
+EOF
+cat <<'EOF' > EXPECTED
+testkey1 = alice
+testkey1 = bob
+testkey1 = testvalue1
+testkey1 = testvalue2
+testkey3 = alice3
+testkey3 = bob3
+testkey3 = testvalue3
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "msg.get_properties (python, exact)"
+test_python <<'EOF'
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+msg = db.find_message("4EFC743A.3060609@april.org")
+for (key,val) in msg.get_properties("testkey",True):
+        print("{0} = {1}".format(key,val))
+EOF
+test_expect_equal_file /dev/null OUTPUT
+
+test_done
diff --git a/test/T620-lock.sh b/test/T620-lock.sh
new file mode 100755 (executable)
index 0000000..7aaaff2
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+test_description="locking"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "blocking open"
+if [ $NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_C ${MAIL_DIR} <<'EOF'
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <notmuch-test.h>
+
+void
+taggit (notmuch_database_t *db, const char *tag)
+{
+    notmuch_message_t *message;
+
+    EXPECT0 (notmuch_database_find_message (db, "4EFC743A.3060609@april.org", &message));
+    if (message == NULL) {
+       fprintf (stderr, "unable to find message");
+       exit (1);
+    }
+
+    EXPECT0 (notmuch_message_add_tag (message, tag));
+    notmuch_message_destroy (message);
+}
+
+int
+main (int argc, char **argv)
+{
+    pid_t child;
+    const char *path = argv[1];
+
+    child = fork ();
+    if (child == -1) {
+       fprintf (stderr, "fork failed\n");
+       exit (1);
+    }
+
+    if (child == 0) {
+       notmuch_database_t *db2;
+
+       sleep (1);
+       EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &db2));
+       taggit (db2, "child");
+       EXPECT0 (notmuch_database_close (db2));
+    } else {
+       notmuch_database_t *db;
+
+       EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+       taggit (db, "parent");
+       sleep (2);
+       EXPECT0 (notmuch_database_close (db));
+       wait (NULL);
+    }
+}
+
+EOF
+notmuch search --output=tags id:4EFC743A.3060609@april.org >> OUTPUT
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+child
+inbox
+parent
+unread
+EOF
+if [ $NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK -ne 1 ]; then
+    test_subtest_known_broken
+fi
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T630-emacs-draft.sh b/test/T630-emacs-draft.sh
new file mode 100755 (executable)
index 0000000..d7903ce
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+test_description="Emacs Draft Handling"
+. $(dirname "$0")/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..274105c
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+test_description="DatabaseModifiedError handling"
+. $(dirname "$0")/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 (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
diff --git a/test/T650-regexp-query.sh b/test/T650-regexp-query.sh
new file mode 100755 (executable)
index 0000000..4085340
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+test_description='regular expression searches'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
+    test_done
+fi
+
+add_message '[dir]=bad' '[subject]="To the bone"'
+add_message '[dir]=.' '[subject]="Top level"'
+add_message '[dir]=bad/news' '[subject]="Bears"'
+mkdir -p "${MAIL_DIR}/duplicate/bad/news"
+cp "$gen_msg_filename" "${MAIL_DIR}/duplicate/bad/news"
+
+add_message '[dir]=things' '[subject]="These are a few"'
+add_message '[dir]=things/favorite' '[subject]="Raindrops, whiskers, kettles"'
+add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"'
+
+test_begin_subtest "empty path:// search"
+notmuch search 'path:""' > EXPECTED
+notmuch search 'path:/^$/' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "empty folder:// search"
+notmuch search 'folder:""' > EXPECTED
+notmuch search 'folder:/^$/' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "unanchored folder:// specification"
+output=$(notmuch search folder:/bad/ | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
+
+test_begin_subtest "anchored folder:// search"
+output=$(notmuch search 'folder:/^bad$/' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)"
+
+test_begin_subtest "unanchored path:// specification"
+output=$(notmuch search path:/bad/ | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
+thread:XXX   2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
+
+test_begin_subtest "anchored path:// search"
+output=$(notmuch search 'path:/^bad$/' | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)"
+
+# Use "standard" corpus from here on.
+rm -rf $MAIL_DIR
+add_email_corpus
+
+notmuch search --output=messages from:cworth > cworth.msg-ids
+
+# these headers will generate no document terms
+add_message '[from]="-" [subject]="empty from"'
+add_message '[subject]="-"'
+
+test_begin_subtest "null from: search"
+notmuch search 'from:""' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] -; empty from (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "null subject: search"
+notmuch search 'subject:""' | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; - (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "xapian wildcard search for from:"
+notmuch search --output=messages 'from:cwo*' > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "xapian wildcard search for subject:"
+test_expect_equal $(notmuch count 'subject:count*') 1
+
+test_begin_subtest "regexp from search, case sensitive"
+notmuch search --output=messages from:/carl/ > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest "empty regexp or query"
+notmuch search --output=messages from:/carl/ or from:/cworth/ > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "non-empty regexp and query"
+notmuch search  from:/cworth@cworth.org/ and subject:patch | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-18 [1/2] Carl Worth| Alex Botero-Lowry; [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+thread:XXX   2009-11-18 [1/2] Carl Worth| Ingmar Vanhassel; [notmuch] [PATCH] Typsos (inbox unread)
+thread:XXX   2009-11-18 [1/2] Carl Worth| Jan Janak; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+thread:XXX   2009-11-18 [1/2] Carl Worth| Keith Packard; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+thread:XXX   2009-11-18 [2/5] Carl Worth| Mikhail Gusarov, Keith Packard; [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp from search, duplicate term search"
+notmuch search --output=messages from:/cworth/ > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "long enough regexp matches only desired senders"
+notmuch search --output=messages 'from:"/C.* Wo/"' > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "shorter regexp matches one more sender"
+notmuch search --output=messages 'from:"/C.* W/"' > OUTPUT
+{ echo id:1258544095-16616-1-git-send-email-chris@chris-wilson.co.uk; cat cworth.msg-ids; } > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp subject search, non-ASCII"
+notmuch search --output=messages subject:/accentué/ > OUTPUT
+echo id:877h1wv7mg.fsf@inf-8657.int-evry.fr > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp subject search, punctuation"
+notmuch search subject:/\'X\'/ | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-18 [2/2] Keith Packard, Carl Worth; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp subject search, no punctuation"
+notmuch search  subject:/X/ | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-18 [2/2] Keith Packard, Carl Worth; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+thread:XXX   2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "combine regexp from and subject"
+notmuch search  subject:/-C/ and from:/.an.k/ | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-17 [1/2] Jan Janak| Carl Worth; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "regexp error reporting"
+notmuch search 'from:/unbalanced[/' 1>OUTPUT 2>&1
+cat <<EOF > EXPECTED
+notmuch search: A Xapian exception occurred
+A Xapian exception occurred parsing query: Invalid regular expression
+Query string was: from:/unbalanced[/
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "empty mid search"
+notmuch search --output=messages mid:yoom > OUTPUT
+cp /dev/null EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "non-empty mid regex search"
+notmuch search --output=messages mid:/yoom/ > OUTPUT
+test_expect_equal_file cworth.msg-ids OUTPUT
+
+test_begin_subtest "combine regexp mid and subject"
+notmuch search  subject:/-C/ and mid:/y..m/ | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-18 [1/2] Carl Worth| Jan Janak; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "unanchored tag search"
+notmuch search tag:signed or tag:inbox > EXPECTED
+notmuch search tag:/i/ > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+notmuch tag +testsi '*'
+test_begin_subtest "anchored tag search"
+notmuch search tag:signed > EXPECTED
+notmuch search tag:/^si/ > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T660-bad-date.sh b/test/T660-bad-date.sh
new file mode 100755 (executable)
index 0000000..f65544b
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+test_description="parsing of bad dates"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message [date]='"()"'
+
+test_begin_subtest 'Bad dates translate to a date after the Unix epoch'
+cat <<EOF >EXPECTED
+thread:0000000000000001   1970-01-01 [1/1] Notmuch Test Suite; Test message #1 (inbox unread)
+EOF
+notmuch search '*' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T670-duplicate-mid.sh b/test/T670-duplicate-mid.sh
new file mode 100755 (executable)
index 0000000..fd7df05
--- /dev/null
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+test_description="duplicate message ids"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[id]="duplicate"' '[subject]="message 1" [filename]=copy1'
+add_message '[id]="duplicate"' '[subject]="message 2" [filename]=copy2'
+
+add_message '[id]="duplicate"' '[subject]="message 0" [filename]=copy0'
+test_begin_subtest 'search: first indexed subject preserved'
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; message 1 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'First subject preserved in notmuch-show (json)'
+test_subtest_known_broken
+output=$(notmuch show --body=false --format=json id:duplicate | notmuch_json_show_sanitize)
+expected='[[[{
+    "id": "XXXXX",
+    "match": true,
+    "excluded": false,
+    "filename": [
+        "'"${MAIL_DIR}"/copy0'",
+        "'"${MAIL_DIR}"/copy1'",
+        "'"${MAIL_DIR}"/copy2'"
+    ],
+    "timestamp": 42,
+    "date_relative": "2001-01-05",
+    "tags": ["inbox","unread"],
+    "headers": {
+        "Subject": "message 1",
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "Date": "GENERATED_DATE"
+    }
+ },
+[]]]]'
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest 'Search for second subject'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+EOF
+notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'Regexp search for second subject'
+# Note that missing field processor support really means the test
+# doesn't make sense, but it happens to pass.
+if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 1 ]; then
+    test_subtest_known_broken
+fi
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+EOF
+notmuch search --output=files 'subject:"/message 2/"' | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+add_message '[id]="duplicate"' '[body]="sekrit" [filename]=copy3'
+test_begin_subtest 'search for body in duplicate file'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+MAIL_DIR/copy3
+EOF
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy3
+test_begin_subtest 'reindex drops terms in duplicate file'
+cp /dev/null EXPECTED
+notmuch reindex '*'
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex choses subject from first filename'
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1(3)] Notmuch Test Suite; message 0 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy0
+test_begin_subtest 'Deleted first duplicate file does not stop notmuch show from working'
+output=$(notmuch show --body=false --format=json id:duplicate |
+            notmuch_json_show_sanitize | sed 's/message [0-9]/A_SUBJECT/')
+expected='[[[{
+    "id": "XXXXX",
+    "match": true,
+    "excluded": false,
+    "filename": [
+        "'"${MAIL_DIR}"/copy0'",
+        "'"${MAIL_DIR}"/copy1'",
+        "'"${MAIL_DIR}"/copy2'"
+    ],
+    "timestamp": 42,
+    "date_relative": "2001-01-05",
+    "tags": ["inbox","unread"],
+    "headers": {
+        "Subject": "A_SUBJECT",
+        "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+        "Date": "GENERATED_DATE"
+    }
+ },
+[]]]]'
+
+test_expect_equal_json "$output" "$expected"
+
+test_done
diff --git a/test/T680-html-indexing.sh b/test/T680-html-indexing.sh
new file mode 100755 (executable)
index 0000000..62ba849
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+test_description="indexing of html parts"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus html
+
+test_begin_subtest 'embedded images should not be indexed'
+notmuch search kwpza7svrgjzqwi8fhb2msggwtxtwgqcxp4wbqr4wjddstqmeqa7 > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest 'ignore > in attribute text'
+notmuch search swordfish | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file /dev/null OUTPUT
+
+test_begin_subtest 'non tag text should be indexed'
+notmuch search hunter2 | notmuch_search_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+thread:XXX   2009-11-17 [1/1] David Bremner; test html attachment (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T690-command-line-args.sh b/test/T690-command-line-args.sh
new file mode 100755 (executable)
index 0000000..9aa4761
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+
+test_description="command line arguments"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message
+
+test_begin_subtest 'bad option to show'
+notmuch show --frobnicate >& OUTPUT
+cat <<EOF > EXPECTED
+Unrecognized option: --frobnicate
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'string option with space'
+cp /dev/null EXPECTED
+notmuch dump --output foo.txt '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'string option with ='
+cp /dev/null EXPECTED
+notmuch dump --output=foo.txt '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'string option with :'
+cp /dev/null EXPECTED
+notmuch dump --output:foo.txt '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'single keyword option with space'
+cat <<EOF > EXPECTED
+id:msg-001@notmuch-test-suite
+EOF
+notmuch search --output messages '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'single keyword option with ='
+cat <<EOF > EXPECTED
+id:msg-001@notmuch-test-suite
+EOF
+notmuch search --output=messages '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'single keyword option with :'
+cat <<EOF > EXPECTED
+id:msg-001@notmuch-test-suite
+EOF
+notmuch search --output:messages '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'multiple keyword options with space'
+cat <<EOF > EXPECTED
+["msg-001@notmuch-test-suite"]
+EOF
+notmuch search --output messages --format json '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'multiple keyword options with ='
+cat <<EOF > EXPECTED
+["msg-001@notmuch-test-suite"]
+EOF
+notmuch search --output=messages --format=json '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'mixed space and = delimiters'
+cat <<EOF > EXPECTED
+["msg-001@notmuch-test-suite"]
+EOF
+notmuch search --output messages --format=json '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'mixed space and : delimiters'
+cat <<EOF > EXPECTED
+["msg-001@notmuch-test-suite"]
+EOF
+notmuch search --output:messages --format json '*' >& OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'show --entire-thread'
+test_expect_success 'notmuch show --entire-thread tag:test > /dev/null'
+
+test_begin_subtest 'show --exclude'
+test_expect_success 'notmuch show --exclude tag:test > /dev/null'
+
+test_done
diff --git a/test/T700-reindex.sh b/test/T700-reindex.sh
new file mode 100755 (executable)
index 0000000..9e79589
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+test_description='reindexing messages'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+notmuch tag +usertag1 '*'
+
+notmuch search '*' | notmuch_search_sanitize > initial-threads
+notmuch search --output=messages '*' > initial-message-ids
+notmuch dump > initial-dump
+
+test_begin_subtest 'reindex preserves threads'
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file initial-threads OUTPUT
+
+test_begin_subtest 'reindex after removing duplicate file preserves threads'
+# remove one copy
+sed 's,3/3(4),3/3,' < initial-threads > EXPECTED
+mv $MAIL_DIR/bar/18:2, duplicate-msg-1.eml
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex preserves message-ids'
+notmuch reindex '*'
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file initial-message-ids OUTPUT
+
+test_begin_subtest 'reindex preserves tags'
+notmuch reindex '*'
+notmuch dump > OUTPUT
+test_expect_equal_file initial-dump OUTPUT
+
+test_begin_subtest 'reindex moves a message between threads'
+notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
+# re-parent
+sed -i 's/1258471718-6781-1-git-send-email-dottedmag@dottedmag.net/87iqd9rn3l.fsf@vertex.dottedmag/' $MAIL_DIR/02:2,*
+notmuch reindex id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
+notmuch search --output=threads id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex detects removal of all files'
+notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
+# remove both copies
+mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
+notmuch reindex id:20091117232137.GA7669@griffis1.net
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reindex preserves properties"
+cat <<EOF > prop-dump
+#= 1258471718-6781-1-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258471718-6781-2-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258491078-29658-1-git-send-email-dottedmag@dottedmag.net userprop=userval1
+#= 20091117190054.GU3165@dottiness.seas.harvard.edu userprop=userval
+#= 20091117203301.GV3165@dottiness.seas.harvard.edu userprop=userval3
+#= 87fx8can9z.fsf@vertex.dottedmag userprop=userval2
+#= 87iqd9rn3l.fsf@vertex.dottedmag userprop=userval
+#= 87lji4lx9v.fsf@yoom.home.cworth.org userprop=userval3
+#= 87lji5cbwo.fsf@yoom.home.cworth.org userprop=userval
+#= cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com userprop=userval
+EOF
+notmuch restore < prop-dump
+notmuch reindex '*'
+notmuch dump | grep '^#=' | sort > OUTPUT
+test_expect_equal_file prop-dump OUTPUT
+
+add_email_corpus lkml
+
+test_begin_subtest "reindex of lkml corpus preserves threads"
+notmuch search '*' | notmuch_search_sanitize > EXPECTED
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
diff --git a/test/T710-message-id.sh b/test/T710-message-id.sh
new file mode 100755 (executable)
index 0000000..e73d6ba
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+test_description="message id parsing"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest "good message ids"
+${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
+<018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
+<1530507300.raoomurnbf.astroid@strange.none>
+<1258787708-21121-2-git-send-email-keithp@keithp.com>
+EOF
+cat <<EOF >EXPECTED
+GOOD: 018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org
+GOOD: 1530507300.raoomurnbf.astroid@strange.none
+GOOD: 1258787708-21121-2-git-send-email-keithp@keithp.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "leading and trailing space is OK"
+${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
+   <018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
+<1530507300.raoomurnbf.astroid@strange.none>    
+    <1258787708-21121-2-git-send-email-keithp@keithp.com>
+EOF
+cat <<EOF >EXPECTED
+GOOD: 018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org
+GOOD: 1530507300.raoomurnbf.astroid@strange.none
+GOOD: 1258787708-21121-2-git-send-email-keithp@keithp.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "<> delimeters are required"
+${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
+018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
+<1530507300.raoomurnbf.astroid@strange.none
+1258787708-21121-2-git-send-email-keithp@keithp.com
+EOF
+cat <<EOF >EXPECTED
+BAD: 018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>
+BAD: <1530507300.raoomurnbf.astroid@strange.none
+BAD: 1258787708-21121-2-git-send-email-keithp@keithp.com
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "embedded whitespace is forbidden"
+${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
+<018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915 .git.jani@nikula.org>
+<1530507300.raoomurnbf.astroid @strange.none>
+<1258787708-21121-\f2-git-send-email-keithp@keithp.com>
+EOF
+cat <<EOF >EXPECTED
+BAD: <018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915 .git.jani@nikula.org>
+BAD: <1530507300.raoomurnbf.astroid    @strange.none>
+BAD: <1258787708-21121-\f2-git-send-email-keithp@keithp.com>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "folded real life bad In-Reply-To values"
+${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
+<22597.31869.380767.339702@chiark.greenend.org.uk> (Ian Jackson's message of "Mon, 5 Dec 2016 14:41:01 +0000")
+<20170625141242.loaalhis2eodo66n@gaara.hadrons.org>  <149719990964.27883.13021127452105787770.reportbug@seneca.home.org>
+Your message of Tue, 09 Dec 2014 13:21:11 +0100. <1900758.CgLNVPbY9N@liber>
+EOF
+cat <<EOF >EXPECTED
+BAD: <22597.31869.380767.339702@chiark.greenend.org.uk> (Ian Jackson's message of "Mon, 5 Dec 2016 14:41:01 +0000")
+BAD: <20170625141242.loaalhis2eodo66n@gaara.hadrons.org>  <149719990964.27883.13021127452105787770.reportbug@seneca.home.org>
+BAD: Your message of Tue, 09 Dec 2014 13:21:11 +0100. <1900758.CgLNVPbY9N@liber>
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_done
diff --git a/test/aggregate-results.sh b/test/aggregate-results.sh
new file mode 100755 (executable)
index 0000000..6322854
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+set -eu
+
+fixed=0
+success=0
+failed=0
+broken=0
+total=0
+
+for file
+do
+       while read type value
+       do
+               case $type in
+               '')
+                       continue ;;
+               fixed)
+                       fixed=$(($fixed + $value)) ;;
+               success)
+                       success=$(($success + $value)) ;;
+               failed)
+                       failed=$(($failed + $value)) ;;
+               broken)
+                       broken=$(($broken + $value)) ;;
+               total)
+                       total=$(($total + $value)) ;;
+               esac
+       done <"$file"
+done
+
+pluralize () {
+    case $2 in
+       1)
+           case $1 in
+               test)
+                   echo test ;;
+               failure)
+                   echo failure ;;
+           esac
+           ;;
+       *)
+           case $1 in
+               test)
+                   echo tests ;;
+               failure)
+                   echo failures ;;
+           esac
+           ;;
+    esac
+}
+
+echo "Notmuch test suite complete."
+if [ "$fixed" = "0" ] && [ "$failed" = "0" ]; then
+    tests=$(pluralize "test" $total)
+    printf "All $total $tests "
+    if [ "$broken" = "0" ]; then
+       echo "passed."
+    else
+       failures=$(pluralize "failure" $broken)
+       echo "behaved as expected ($broken expected $failures)."
+    fi;
+else
+    echo "$success/$total tests passed."
+    if [ "$broken" != "0" ]; then
+       tests=$(pluralize "test" $broken)
+       echo "$broken broken $tests failed as expected."
+    fi
+    if [ "$fixed" != "0" ]; then
+       tests=$(pluralize "test" $fixed)
+       echo "$fixed broken $tests now fixed."
+    fi
+    if [ "$failed" != "0" ]; then
+       tests=$(pluralize "test" $failed)
+       echo "$failed $tests failed."
+    fi
+fi
+
+skipped=$(($total - $fixed - $success - $failed - $broken))
+if [ "$skipped" != "0" ]; then
+    tests=$(pluralize "test" $skipped)
+    echo "$skipped $tests skipped."
+fi
+
+# Note that we currently do not consider skipped tests as failing the
+# build.
+
+if [ $success -gt 0 -a $fixed -eq 0 -a $failed -eq 0 ]
+then
+    exit 0
+else
+    exit 1
+fi
diff --git a/test/arg-test.c b/test/arg-test.c
new file mode 100644 (file)
index 0000000..a218f96
--- /dev/null
@@ -0,0 +1,85 @@
+#include <stdio.h>
+#include "command-line-arguments.h"
+
+
+int main(int argc, char **argv){
+
+    int opt_index=1;
+
+    int kw_val=0;
+    int kwb_val=0;
+    int fl_val=0;
+    int int_val=0;
+    const char *pos_arg1=NULL;
+    const char *pos_arg2=NULL;
+    const char *string_val=NULL;
+    bool bool_val = false;
+    bool fl_set = false, int_set = false, bool_set = false, kwb_set = false,
+       kw_set = false, string_set = false, pos1_set = false, pos2_set = false;
+
+    notmuch_opt_desc_t parent_options[] = {
+       { .opt_flags = &fl_val, .name = "flag", .present = &fl_set, .keywords =
+         (notmuch_keyword_t []){ { "one",   1 << 0},
+                                 { "two",   1 << 1 },
+                                 { "three", 1 << 2 },
+                                 { 0, 0 } } },
+       { .opt_int = &int_val, .name = "int", .present = &int_set },
+       { }
+    };
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_bool = &bool_val, .name = "boolean", .present = &bool_set },
+       { .opt_keyword = &kw_val, .name = "keyword", .present = &kw_set, .keywords =
+         (notmuch_keyword_t []){ { "zero", 0 },
+                                 { "one", 1 },
+                                 { "two", 2 },
+                                 { 0, 0 } } },
+       { .opt_keyword = &kwb_val, .name = "boolkeyword", .present = &kwb_set,
+         .keyword_no_arg_value = "true", .keywords =
+         (notmuch_keyword_t []){ { "false", 0 },
+                                 { "true", 1 },
+                                 { "auto", 2 },
+                                 { 0, 0 } } },
+       { .opt_inherit = parent_options },
+       { .opt_string = &string_val, .name = "string", .present = &string_set },
+       { .opt_position = &pos_arg1, .present = &pos1_set },
+       { .opt_position = &pos_arg2, .present = &pos2_set },
+       { }
+    };
+
+    opt_index = parse_arguments(argc, argv, options, 1);
+
+    if (opt_index < 0)
+       return 1;
+
+    if (bool_set)
+       printf("boolean %d\n", bool_val);
+
+    if (kw_set)
+       printf("keyword %d\n", kw_val);
+
+    if (kwb_set)
+       printf("boolkeyword %d\n", kwb_val);
+
+    if (fl_set)
+       printf("flags %d\n", fl_val);
+
+    if (int_set)
+       printf("int %d\n", int_val);
+
+    if (string_set)
+       printf("string %s\n", string_val);
+
+    if (pos1_set)
+       printf("positional arg 1 %s\n", pos_arg1);
+
+    if (pos2_set)
+       printf("positional arg 2 %s\n", pos_arg2);
+
+
+    for ( ; opt_index < argc ; opt_index ++) {
+       printf("non parsed arg %d = %s\n", opt_index, argv[opt_index]);
+    }
+
+    return 0;
+}
diff --git a/test/atomicity.py b/test/atomicity.py
new file mode 100644 (file)
index 0000000..389517e
--- /dev/null
@@ -0,0 +1,78 @@
+# This gdb Python script runs notmuch new and simulates killing and
+# restarting notmuch new after every Xapian commit.  To simulate this
+# more efficiently, this script runs notmuch new and, immediately
+# after every Xapian commit, it *pauses* the running notmuch new,
+# copies the entire database and maildir to a snapshot directory, and
+# executes a full notmuch new on that snapshot, comparing the final
+# results with the expected output.  It can then resume the paused
+# notmuch new, which is still running on the original maildir, and
+# repeat this process.
+
+import gdb
+import os
+import glob
+import shutil
+import subprocess
+
+gdb.execute('set args new')
+
+# Make Xapian commit after every operation instead of batching
+gdb.execute('set environment XAPIAN_FLUSH_THRESHOLD = 1')
+
+maildir = os.environ['MAIL_DIR']
+
+# Trap calls to rename, which happens just before Xapian commits
+class RenameBreakpoint(gdb.Breakpoint):
+    def __init__(self, *args, **kwargs):
+        super(RenameBreakpoint, self).__init__(*args, **kwargs)
+        self.last_inodes = {}
+        self.n = 0
+
+    def stop(self):
+        xapiandir = '%s/.notmuch/xapian' % maildir
+        if os.path.isfile('%s/iamchert' % xapiandir):
+            # As an optimization, only consider snapshots after a
+            # Xapian has really committed.  The chert backend
+            # overwrites record.base? as the last step in the commit,
+            # so keep an eye on their inumbers.
+            inodes = {}
+            for path in glob.glob('%s/record.base*' % xapiandir):
+                inodes[path] = os.stat(path).st_ino
+            if inodes == self.last_inodes:
+                # Continue
+                return False
+            self.last_inodes = inodes
+
+        # Save a backtrace in case the test does fail
+        backtrace = gdb.execute('backtrace', to_string=True)
+        open('backtrace.%d' % self.n, 'w').write(backtrace)
+
+        # Snapshot the database
+        shutil.rmtree('%s.snap/.notmuch' % maildir)
+        shutil.copytree('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir)
+        # Restore the mtime of $MAIL_DIR.snap/
+        shutil.copystat('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir)
+
+        # Run notmuch new to completion on the snapshot
+        env = os.environ.copy()
+        env.update(NOTMUCH_CONFIG=os.environ['NOTMUCH_CONFIG'] + '.snap',
+                   XAPIAN_FLUSH_THRESHOLD='1000')
+        subprocess.check_call(
+            ['notmuch', 'new'], env=env, stdout=open('/dev/null', 'w'))
+        subprocess.check_call(
+            ['notmuch', 'search', '*'], env=env,
+            stdout=open('search.%d' % self.n, 'w'))
+
+        # Tell the shell how far we've gotten
+        open('outcount', 'w').write(str(self.n + 1))
+
+        # Continue
+        self.n += 1
+        return False
+RenameBreakpoint('rename')
+
+try:
+    gdb.execute('run')
+except Exception:
+    import traceback
+    raise SystemExit(traceback.format_exc())
diff --git a/test/corpora/README b/test/corpora/README
new file mode 100644 (file)
index 0000000..05ea386
--- /dev/null
@@ -0,0 +1,19 @@
+This directory contains email corpora for testing.
+
+default
+  The default corpus is based on about 50 messages from early in the
+  history of the notmuch mailing list, which allows for reliably
+  testing commands that need to operate on a not-totally-trivial
+  number of messages.
+
+broken
+  The broken corpus contains messages that are broken and/or RFC
+  non-compliant, ensuring we deal with them in a sane way.
+
+html
+  The html corpus contains html parts
+
+crypto
+  The crypto corpus contains encrypted messages for testing.
+  It should probably also contain signed messages in the future.
+  Please add them!
diff --git a/test/corpora/broken/broken-cc b/test/corpora/broken/broken-cc
new file mode 100644 (file)
index 0000000..57ae9ba
--- /dev/null
@@ -0,0 +1,9 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Cc: Bob <bob@example.org>
+Subject: wowsers!
+cc: Charles <charles@example.org>
+Message-Id: <multiple-cc@example.org>
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+
+Note the Cc: and cc: headers.
diff --git a/test/corpora/broken/loop/loop-12 b/test/corpora/broken/loop/loop-12
new file mode 100644 (file)
index 0000000..b5c3af7
--- /dev/null
@@ -0,0 +1,8 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: referencing in-reply-to-loop-21
+Message-ID: <mid-loop-12@example.org>
+In-Reply-To: <mid-loop-21@example.org>
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+
+Note Message-ID and In-Reply-To: in file in-reply-to-loop-21
diff --git a/test/corpora/broken/loop/loop-21 b/test/corpora/broken/loop/loop-21
new file mode 100644 (file)
index 0000000..234f032
--- /dev/null
@@ -0,0 +1,8 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: referencing in-reply-to-loop-12
+Message-ID: <mid-loop-21@example.org>
+In-Reply-To: <mid-loop-12@example.org>
+Date: Fri, 17 Jun 2016 22:14:41 -0400
+
+Note Message-ID and In-Reply-To: in file in-reply-to-loop-12
diff --git a/test/corpora/crypto/simple-encrypted b/test/corpora/crypto/simple-encrypted
new file mode 100644 (file)
index 0000000..6869972
--- /dev/null
@@ -0,0 +1,36 @@
+From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+To: dkg@fifthhorseman.net
+Subject: encrypted message
+Date: Mon, 22 Dec 2016 08:34:56 -0400
+Message-ID: <simple-encrypted@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+       protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hQIMAzt6p/AU5ptaAQ/+IDRx5dZWKjUz9qITP76w8OvtmTV9p871UoGWil1DSd3J
+dHgf56rXDWS73dzJ5EigevxLVMD3Xv8QEJBgMWb6yC+uR8ZdJ8h7hlE2lYyEg3Ch
+smqzcaYp2nKWw9JZQqubsMaeIgVu0exb4YE8g/qlUOL2mD64dXhnkJ68GGMmiEPJ
++d0H7fTMwstbxvKPKDmFJUztH43V2NSfxpeQUTi4iWmQtUGw6THYjjAWNFy7jVEE
+ozloT9W3ER5cRaXyKE4GWMBlUAOB0YwwsVnBU2JGUtTBzNHxQAbeoKrG6myrzmnC
+LhUNag0gMuNEbGR78XfWKpCbccEC1VLf9uUXze6yeuRXDuhfsvilKOH9MpaR9n+G
+JuYVAAobDc5wGOt5VGMka50ToaALrNt3FrWqni/0jqwqshEVKM3Kqzq6cSjh1TPf
+pfxE9aUDdvf+Nn16ZBGgyLox2NV4GkSQYq2ySzAk3XwLU80F0nCmtOV3EH+OMrv6
+sZI/Svwzaqzrs/w17cvpf0czXjE/N9V1MHdNtIkfb707WkO0l9/wtYvlrg/KyrU2
+FH5aecEO9VpMumgzBqP1MrrnzVlSM3kgRLIu06oshQYD+jjFn2YzvkwZ+pWoAxsQ
+ninQxoF8Ck2D7s8uGzx+HSQQpRVBM5AGfVdEkmzW/sZrlz63ZGUFh0FYqLl30rfS
+cQGqSoZz0ugTSxnTlg0nuzFmG7ylC1cx3dlSrnfv+l1azwLAr58ptoYZ0mO+D+Fy
+bePpCGoFAPKi0cZ/4eFlLKL7uYPmGeEo5Ku2wXU/SXtPfl4vRxzvPXTJTvD06vIx
+Dh1P0ChAJtxUjGGY6xkCHMY0
+=frmz
+-----END PGP MESSAGE-----
+--=-=-=--
diff --git a/test/corpora/default/01:2, b/test/corpora/default/01:2,
new file mode 100644 (file)
index 0000000..7e9e349
--- /dev/null
@@ -0,0 +1,34 @@
+From: "Mikhail Gusarov" <dottedmag@dottedmag.net>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 21:28:37 +0600
+Subject: [notmuch] [PATCH 1/2] Close message file after parsing message
+       headers
+Message-ID: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+
+Keeping unused files open helps to see "Too many open files" often.
+
+Signed-off-by: Mikhail Gusarov <dottedmag at dottedmag.net>
+---
+ lib/message-file.c |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/lib/message-file.c b/lib/message-file.c
+index 8a3f8ee..197ab01 100644
+--- a/lib/message-file.c
++++ b/lib/message-file.c
+@@ -325,6 +325,11 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
+           return decoded_value;
+     }
++    if (message->parsing_finished) {
++        fclose (message->file);
++        message->file = NULL;
++    }
++
+     if (message->line)
+       free (message->line);
+     message->line = NULL;
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/02:2, b/test/corpora/default/02:2,
new file mode 100644 (file)
index 0000000..dadcdaa
--- /dev/null
@@ -0,0 +1,32 @@
+From: "Mikhail Gusarov" <dottedmag@dottedmag.net>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 21:28:38 +0600
+Subject: [notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++
+       file with gcc 4.4
+In-Reply-To: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+References: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+Message-ID: <1258471718-6781-2-git-send-email-dottedmag@dottedmag.net>
+
+
+Signed-off-by: Mikhail Gusarov <dottedmag at dottedmag.net>
+---
+ lib/message.cc |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/lib/message.cc b/lib/message.cc
+index 72c350f..a4b090b 100644
+--- a/lib/message.cc
++++ b/lib/message.cc
+@@ -21,6 +21,8 @@
+ #include "notmuch-private.h"
+ #include "database-private.h"
++#include <stdint.h>
++
+ #include <gmime/gmime.h>
+ #include <xapian.h>
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/bar/17:2, b/test/corpora/default/bar/17:2,
new file mode 100644 (file)
index 0000000..d3b7568
--- /dev/null
@@ -0,0 +1,23 @@
+From: "Israel Herraiz" <isra@herraiz.org>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 23:57:18 +0100
+Subject: [notmuch] New to the list
+Message-ID: <1258498485-sup-142@elly>
+
+Hi all,
+
+I have subscribed to the list. As suggested by the welcome message, I
+am introducing myself. My name is Israel Herraiz, and I have done a
+couple of contributions to Sup, the probably well-known here e-mail
+client.
+
+"Not much" sounds interesting, and I wonder whether it could be
+integrated with the views of Sup (inbox, threads, etc). So I have
+subscribed to the list to keep an eye on what's going on here.
+
+I have just heard of "Not much". I have not even tried to download the
+code yet.
+
+Cheers,
+Israel
+
diff --git a/test/corpora/default/bar/18:2, b/test/corpora/default/bar/18:2,
new file mode 100644 (file)
index 0000000..f522f69
--- /dev/null
@@ -0,0 +1,12 @@
+From: "Aron Griffis" <agriffis@n01se.net>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 18:21:38 -0500
+Subject: [notmuch] archive
+Message-ID: <20091117232137.GA7669@griffis1.net>
+
+Just subscribed, I'd like to catch up on the previous postings,
+but the archive link seems to be bogus?
+
+Thanks,
+Aron
+
diff --git a/test/corpora/default/bar/baz/05:2, b/test/corpora/default/bar/baz/05:2,
new file mode 100644 (file)
index 0000000..75b05fa
--- /dev/null
@@ -0,0 +1,104 @@
+MIME-Version: 1.0
+Date: Tue, 17 Nov 2009 11:36:14 -0800
+Message-ID: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+From: Alex Botero-Lowry <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Content-Type: multipart/mixed; boundary=0016e687869333b1570478963d35
+Subject: [notmuch] preliminary FreeBSD support
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+--0016e687869333b1570478963d35
+Content-Type: multipart/alternative; boundary=0016e687869333b14e0478963d33
+
+--0016e687869333b14e0478963d33
+Content-Type: text/plain; charset=ISO-8859-1
+
+I saw the announcement this morning, and was very excited, as I had been
+hoping sup would be turned into a library,
+since I like the concept more than the UI (I'd rather an emacs interface).
+
+I did a preliminary compile which worked out fine, but
+sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on
+FreeBSD, so notmuch_config_open segfaulted.
+
+Attached is a patch that supplies a default buffer size of 64 in cases where
+-1 is returned.
+
+http://www.opengroup.org/austin/docs/austin_328.txt - seems to indicate this
+is acceptable behavior,
+and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically
+uses 64 as the
+buffer size.
+
+--0016e687869333b14e0478963d33
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+I saw the announcement this morning, and was very excited, as I had been ho=
+ping sup would be turned into a library,<br>since I like the concept more t=
+han the UI (I&#39;d rather an emacs interface).<br><br>I did a preliminary =
+compile which worked out fine, but sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns=
+ -1 on<br>
+FreeBSD, so notmuch_config_open segfaulted.<br><br>Attached is a patch that=
+ supplies a default buffer size of 64 in cases where -1 is returned.<br><br=
+><a href=3D"http://www.opengroup.org/austin/docs/austin_328.txt">http://www=
+.opengroup.org/austin/docs/austin_328.txt</a> - seems to indicate this is a=
+cceptable behavior,<br>
+and <a href=3D"http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg01680=
+8.html">http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.html<=
+/a> specifically uses 64 as the<br>buffer size.<br><br><br>
+
+--0016e687869333b14e0478963d33--
+--0016e687869333b1570478963d35
+Content-Type: application/octet-stream; 
+       name="0001-Deal-with-situation-where-sysconf-_SC_GETPW_R_SIZE_M.patch"
+Content-Disposition: attachment; 
+       filename="0001-Deal-with-situation-where-sysconf-_SC_GETPW_R_SIZE_M.patch"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_g252e6gs0
+
+RnJvbSBlM2JjNGJiZDdiOWQwZDA4NjgxNmFiNWY4ZjJkNmZmZWExZGQzZWE0IE1vbiBTZXAgMTcg
+MDA6MDA6MDAgMjAwMQpGcm9tOiBBbGV4YW5kZXIgQm90ZXJvLUxvd3J5IDxhbGV4LmJvdGVyb2xv
+d3J5QGdtYWlsLmNvbT4KRGF0ZTogVHVlLCAxNyBOb3YgMjAwOSAxMTozMDozOSAtMDgwMApTdWJq
+ZWN0OiBbUEFUQ0hdIERlYWwgd2l0aCBzaXR1YXRpb24gd2hlcmUgc3lzY29uZihfU0NfR0VUUFdf
+Ul9TSVpFX01BWCkgcmV0dXJucyAtMQoKLS0tCiBub3RtdWNoLWNvbmZpZy5jIHwgICAgMiArKwog
+MSBmaWxlcyBjaGFuZ2VkLCAyIGluc2VydGlvbnMoKyksIDAgZGVsZXRpb25zKC0pCgpkaWZmIC0t
+Z2l0IGEvbm90bXVjaC1jb25maWcuYyBiL25vdG11Y2gtY29uZmlnLmMKaW5kZXggMjQ4MTQ5Yy4u
+ZTcyMjBkOCAxMDA2NDQKLS0tIGEvbm90bXVjaC1jb25maWcuYworKysgYi9ub3RtdWNoLWNvbmZp
+Zy5jCkBAIC03Nyw2ICs3Nyw3IEBAIHN0YXRpYyBjaGFyICoKIGdldF9uYW1lX2Zyb21fcGFzc3dk
+X2ZpbGUgKHZvaWQgKmN0eCkKIHsKICAgICBsb25nIHB3X2J1Zl9zaXplID0gc3lzY29uZihfU0Nf
+R0VUUFdfUl9TSVpFX01BWCk7CisgICAgaWYgKHB3X2J1Zl9zaXplID09IC0xKSBwd19idWZfc2l6
+ZSA9IDY0OwogICAgIGNoYXIgKnB3X2J1ZiA9IHRhbGxvY196ZXJvX3NpemUgKGN0eCwgcHdfYnVm
+X3NpemUpOwogICAgIHN0cnVjdCBwYXNzd2QgcGFzc3dkLCAqaWdub3JlZDsKICAgICBjaGFyICpu
+YW1lOwpAQCAtMTAxLDYgKzEwMiw3IEBAIHN0YXRpYyBjaGFyICoKIGdldF91c2VybmFtZV9mcm9t
+X3Bhc3N3ZF9maWxlICh2b2lkICpjdHgpCiB7CiAgICAgbG9uZyBwd19idWZfc2l6ZSA9IHN5c2Nv
+bmYoX1NDX0dFVFBXX1JfU0laRV9NQVgpOworICAgIGlmIChwd19idWZfc2l6ZSA9PSAtMSkgcHdf
+YnVmX3NpemUgPSA2NDsKICAgICBjaGFyICpwd19idWYgPSB0YWxsb2NfemVyb19zaXplIChjdHgs
+IHB3X2J1Zl9zaXplKTsKICAgICBzdHJ1Y3QgcGFzc3dkIHBhc3N3ZCwgKmlnbm9yZWQ7CiAgICAg
+Y2hhciAqbmFtZTsKLS0gCjEuNi41LjIKCg==
+--0016e687869333b1570478963d35
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--0016e687869333b1570478963d35--
+
diff --git a/test/corpora/default/bar/baz/23:2, b/test/corpora/default/bar/baz/23:2,
new file mode 100644 (file)
index 0000000..9bb62d7
--- /dev/null
@@ -0,0 +1,145 @@
+Date: Tue, 17 Nov 2009 19:58:29 -0500
+From: Lars Kellogg-Stedman <lars@seas.harvard.edu>
+To: notmuch <notmuch@notmuchmail.org>
+Message-ID: <20091118005829.GB25380@dottiness.seas.harvard.edu>
+MIME-Version: 1.0
+User-Agent: Mutt/1.5.19 (2009-01-05)
+Subject: [notmuch] "notmuch help" outputs to stderr?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============1359248349=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+
+--===============1359248349==
+Content-Type: multipart/signed; micalg=pgp-sha256;
+       protocol="application/pgp-signature"; boundary="L6iaP+gRLNZHKoI4"
+Content-Disposition: inline
+
+
+--L6iaP+gRLNZHKoI4
+Content-Type: multipart/mixed; boundary="z6Eq5LdranGa6ru8"
+Content-Disposition: inline
+
+
+--z6Eq5LdranGa6ru8
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+I'm just noticing that 'notmuch help ...' outputs to stderr, which
+isn't terribly intuitive.  For example, the obvious invocation:
+
+  notmuch help | less
+
+=2E..isn't terribly helpful.
+
+I've attached a patch that lets usage() take a FILE * argument so that
+you can output to stderr in response to usage errors, and stdout in
+response to an explicit request.
+
+--=20
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+
+--z6Eq5LdranGa6ru8
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: attachment; filename="notmuch-help.patch"
+Content-Transfer-Encoding: quoted-printable
+
+diff --git a/notmuch.c b/notmuch.c
+index c47e640..a35cb99 100644
+--- a/notmuch.c
++++ b/notmuch.c
+@@ -157,23 +157,23 @@ command_t commands[] =3D {
+ };
+=20
+ static void
+-usage (void)
++usage (FILE *out)
+ {
+     command_t *command;
+     unsigned int i;
+=20
+-    fprintf (stderr, "Usage: notmuch <command> [args...]\n");
+-    fprintf (stderr, "\n");
+-    fprintf (stderr, "Where <command> and [args...] are as follows:\n");
+-    fprintf (stderr, "\n");
++    fprintf (out, "Usage: notmuch <command> [args...]\n");
++    fprintf (out, "\n");
++    fprintf (out, "Where <command> and [args...] are as follows:\n");
++    fprintf (out, "\n");
+=20
+     for (i =3D 0; i < ARRAY_SIZE (commands); i++) {
+       command =3D &commands[i];
+=20
+-      fprintf (stderr, "\t%s\t%s\n\n", command->name, command->summary);
++      fprintf (out, "\t%s\t%s\n\n", command->name, command->summary);
+     }
+=20
+-    fprintf (stderr, "Use \"notmuch help <command>\" for more details on e=
+ach command.\n\n");
++    fprintf (out, "Use \"notmuch help <command>\" for more details on each=
+ command.\n\n");
+ }
+=20
+ static int
+@@ -183,8 +183,8 @@ notmuch_help_command (unused (void *ctx), int argc, cha=
+r *argv[])
+     unsigned int i;
+=20
+     if (argc =3D=3D 0) {
+-      fprintf (stderr, "The notmuch mail system.\n\n");
+-      usage ();
++      fprintf (stdout, "The notmuch mail system.\n\n");
++      usage (stdout);
+       return 0;
+     }
+=20
+
+--z6Eq5LdranGa6ru8--
+
+--L6iaP+gRLNZHKoI4
+Content-Type: application/pgp-signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQEcBAEBCAAGBQJLA0a1AAoJENdGlQYxQazYr78IAJtqTWIpBqSdOWqTzt/r4XNn
+KJ5mWAoNfq4H+3kx3xoWOFYS7qAYeJoHQWCDbMdb+zEXvPX6hMFn9+OxRN+N5FdQ
+uxGTugSG9xSsK28oGDCQUtr5uheo+tH0jygPjI+LTD97vjUYS4K2qzhLGFJmpLcj
+1akMJXM0gSdPZT8dJyjxvC15pgboLspE4+b6jexXmd4UoFvXgqvjkYHeV4Wk+s0L
+xu+HkCGXL9WHYc3t171fFAru4Zd1AUxFQl4BZ2Y+OqRZUrD28Mtz3zGQxbJQoifl
+JFrgPAWioLN71SkVq/y+efjvGSl0osPpKU5dftMmyY1zV7k7mMlO08ZSJU+wANA=
+=Iijt
+-----END PGP SIGNATURE-----
+
+--L6iaP+gRLNZHKoI4--
+
+--===============1359248349==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============1359248349==--
+
diff --git a/test/corpora/default/bar/baz/24:2, b/test/corpora/default/bar/baz/24:2,
new file mode 100644 (file)
index 0000000..c800020
--- /dev/null
@@ -0,0 +1,204 @@
+Return-path: <notmuch-bounces@notmuchmail.org>
+Envelope-to: cworth@localhost
+Delivery-date: Wed, 18 Nov 2009 01:43:47 -0800
+Received: from yoom.home.cworth.org ([127.0.0.1])
+       by yoom.home.cworth.org with esmtp (Exim 4.69)
+       (envelope-from <notmuch-bounces@notmuchmail.org>)
+       id 1NAgpH-0005Ab-20
+       for cworth@localhost; Wed, 18 Nov 2009 01:27:47 -0800
+X-Original-To: cworth@cworth.org
+Delivered-To: cworth@cworth.org
+Received: from olra.theworths.org [82.165.184.25]
+       by yoom.home.cworth.org with IMAP (fetchmail-6.3.9-rc2)
+       for <cworth@localhost> (single-drop); Wed, 18 Nov 2009 01:27:47 -0800 (PST)
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 12248431FC3
+       for <cworth@cworth.org>; Tue, 17 Nov 2009 17:01:22 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+X-Spam-Flag: NO
+X-Spam-Score: -6.17
+X-Spam-Level: 
+X-Spam-Status: No, score=-6.17 tagged_above=-999 required=2 tests=[AWL=0.429,
+       BAYES_00=-2.599, RCVD_IN_DNSWL_MED=-4] autolearn=unavailable
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id TmBdVd1i-Wjb; Tue, 17 Nov 2009 17:01:20 -0800 (PST)
+Received: from olra.theworths.org (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id AF876431FBC;
+       Tue, 17 Nov 2009 17:01:20 -0800 (PST)
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 75784431FBC
+       for <notmuch@notmuchmail.org>; Tue, 17 Nov 2009 17:01:19 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id IoYHzHoKBskU for <notmuch@notmuchmail.org>;
+       Tue, 17 Nov 2009 17:01:18 -0800 (PST)
+Received: from smtp-outbound.seas.harvard.edu (smtp-outbound.seas.harvard.edu
+       [140.247.51.171])
+       by olra.theworths.org (Postfix) with ESMTP id 7E033431FAE
+       for <notmuch@notmuchmail.org>; Tue, 17 Nov 2009 17:01:18 -0800 (PST)
+Received: from dottiness.seas.harvard.edu (dottiness.seas.harvard.edu
+       [140.247.52.224])
+       by smtp-outbound.seas.harvard.edu (8.13.8/8.13.8) with SMTP id
+       nAI11Gkj008772
+       for <notmuch@notmuchmail.org>; Tue, 17 Nov 2009 20:01:16 -0500
+Received: by dottiness.seas.harvard.edu (sSMTP sendmail emulation);
+       Tue, 17 Nov 2009 20:01:16 -0500
+Date: Tue, 17 Nov 2009 20:01:16 -0500
+From: Lars Kellogg-Stedman <lars@seas.harvard.edu>
+To: notmuch <notmuch@notmuchmail.org>
+Message-ID: <20091118010116.GC25380@dottiness.seas.harvard.edu>
+References: <20091118005829.GB25380@dottiness.seas.harvard.edu>
+MIME-Version: 1.0
+In-Reply-To: <20091118005829.GB25380@dottiness.seas.harvard.edu>
+User-Agent: Mutt/1.5.19 (2009-01-05)
+Subject: Re: [notmuch] "notmuch help" outputs to stderr?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============0848253760=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+
+--===============0848253760==
+Content-Type: multipart/signed; micalg=pgp-sha256;
+       protocol="application/pgp-signature"; boundary="ZInfyf7laFu/Kiw7"
+Content-Disposition: inline
+
+
+--ZInfyf7laFu/Kiw7
+Content-Type: multipart/mixed; boundary="KdquIMZPjGJQvRdI"
+Content-Disposition: inline
+
+
+--KdquIMZPjGJQvRdI
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+> I've attached a patch that lets usage() take a FILE * argument so that
+> you can output to stderr in response to usage errors, and stdout in
+> response to an explicit request.
+
+Whoops, missed a couple of stderr's in that last patch.  New one
+attached.
+
+--=20
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+
+--KdquIMZPjGJQvRdI
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: attachment; filename="notmuch-help.patch"
+Content-Transfer-Encoding: quoted-printable
+
+diff --git a/notmuch.c b/notmuch.c
+index c47e640..446c810 100644
+--- a/notmuch.c
++++ b/notmuch.c
+@@ -157,23 +157,23 @@ command_t commands[] =3D {
+ };
+=20
+ static void
+-usage (void)
++usage (FILE *out)
+ {
+     command_t *command;
+     unsigned int i;
+=20
+-    fprintf (stderr, "Usage: notmuch <command> [args...]\n");
+-    fprintf (stderr, "\n");
+-    fprintf (stderr, "Where <command> and [args...] are as follows:\n");
+-    fprintf (stderr, "\n");
++    fprintf (out, "Usage: notmuch <command> [args...]\n");
++    fprintf (out, "\n");
++    fprintf (out, "Where <command> and [args...] are as follows:\n");
++    fprintf (out, "\n");
+=20
+     for (i =3D 0; i < ARRAY_SIZE (commands); i++) {
+       command =3D &commands[i];
+=20
+-      fprintf (stderr, "\t%s\t%s\n\n", command->name, command->summary);
++      fprintf (out, "\t%s\t%s\n\n", command->name, command->summary);
+     }
+=20
+-    fprintf (stderr, "Use \"notmuch help <command>\" for more details on e=
+ach command.\n\n");
++    fprintf (out, "Use \"notmuch help <command>\" for more details on each=
+ command.\n\n");
+ }
+=20
+ static int
+@@ -183,8 +183,8 @@ notmuch_help_command (unused (void *ctx), int argc, cha=
+r *argv[])
+     unsigned int i;
+=20
+     if (argc =3D=3D 0) {
+-      fprintf (stderr, "The notmuch mail system.\n\n");
+-      usage ();
++      fprintf (stdout, "The notmuch mail system.\n\n");
++      usage (stdout);
+       return 0;
+     }
+=20
+@@ -192,8 +192,8 @@ notmuch_help_command (unused (void *ctx), int argc, cha=
+r *argv[])
+       command =3D &commands[i];
+=20
+       if (strcmp (argv[0], command->name) =3D=3D 0) {
+-          fprintf (stderr, "Help for \"notmuch %s\":\n\n", argv[0]);
+-          fprintf (stderr, "\t%s\t%s\n\n%s\n\n", command->name,
++          fprintf (stdout, "Help for \"notmuch %s\":\n\n", argv[0]);
++          fprintf (stdout, "\t%s\t%s\n\n%s\n\n", command->name,
+                    command->summary, command->documentation);
+           return 0;
+       }
+
+--KdquIMZPjGJQvRdI--
+
+--ZInfyf7laFu/Kiw7
+Content-Type: application/pgp-signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQEcBAEBCAAGBQJLA0dcAAoJENdGlQYxQazY4nIIAIBCds86/uTmnouvyoPruUUR
+Bg5mXcnjuopz1Nwotl9s9U5sGeZuZngxyEvDz1Z1aTEjwab8ndNTf1xCwIoqBs+l
+i/sc4nPYubLdy1Ab/84DKVtCSbj+v5rtqhegwUWV7S1BY7t8dKNPNv7YBg7P0Azs
+6s3CUxDV5eJCcxCGxxWHH8JDKRf7rDs6vzDwyPWLxlg1Xb1lEM/sRgPCKiShPdO3
+Ak2hECusjskALhSDYX8/FLMd9HwLBC13sfWuSi/pHUAIOI2jru2p5sXrVSlTnFIJ
+fiMbPhKWiEaJj2kmm4pRwAhbTWp/J8ZvXWp0AyosxXQhQUWqujiyxgfiXS70SdQ=
+=t3Yc
+-----END PGP SIGNATURE-----
+
+--ZInfyf7laFu/Kiw7--
+
+--===============0848253760==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============0848253760==--
+
diff --git a/test/corpora/default/bar/baz/cur/25:2, b/test/corpora/default/bar/baz/cur/25:2,
new file mode 100644 (file)
index 0000000..7378f82
--- /dev/null
@@ -0,0 +1,32 @@
+From: "Stewart Smith" <stewart@flamingspork.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 12:05:53 +1100
+Subject: [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++
+       libs.
+Message-ID: <1258506353-20352-1-git-send-email-stewart@flamingspork.com>
+
+Previously, Ubuntu 9.10, gcc 4.4.1 was getting:
+
+ccache gcc `pkg-config --libs glib-2.0 gmime-2.4 talloc` `xapian-config --libs` notmuch.o notmuch-config.o notmuch-dump.o notmuch-new.o notmuch-reply.o notmuch-restore.o notmuch-search.o notmuch-setup.o notmuch-show.o notmuch-tag.o notmuch-time.o gmime-filter-reply.o query-string.o show-message.o lib/notmuch.a -o notmuch
+/usr/bin/ld: lib/notmuch.a(database.o): in function global constructors keyed to BOOLEAN_PREFIX_INTERNAL:database.cc(.text+0x3a): error: undefined reference to 'std::ios_base::Init::Init()'
+---
+ Makefile.local |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/Makefile.local b/Makefile.local
+index f824bed..dbd3e20 100644
+--- a/Makefile.local
++++ b/Makefile.local
+@@ -18,7 +18,7 @@ notmuch_client_srcs =                \
+ notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
+ notmuch: $(notmuch_client_modules) lib/notmuch.a
+-      $(CC) $(LDFLAGS) $^ -o $@
++      $(CXX) $(LDFLAGS) $^ -o $@
+ notmuch.1.gz:
+       gzip --stdout notmuch.1 > notmuch.1.gz
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/bar/baz/cur/26:2, b/test/corpora/default/bar/baz/cur/26:2,
new file mode 100644 (file)
index 0000000..f3c5f53
--- /dev/null
@@ -0,0 +1,121 @@
+From: "Stewart Smith" <stewart@flamingspork.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 12:56:40 +1100
+Subject: [notmuch] [PATCH 2/2] Read mail directory in inode number order
+Message-ID: <1258509400-32511-1-git-send-email-stewart@flamingspork.com>
+
+This gives a rather decent reduction in number of seeks required when
+reading a Maildir that isn't in pagecache.
+
+Most filesystems give some locality on disk based on inode numbers.
+In ext[234] this is the inode tables, in XFS groups of sequential inode
+numbers are together on disk and the most significant bits indicate
+allocation group (i.e inode 1,000,000 is always after inode 1,000).
+
+With this patch, we read in the whole directory, sort by inode number
+before stat()ing the contents.
+
+Ideally, directory is sequential and then we make one scan through the
+file system stat()ing.
+
+Since the universe is not ideal, we'll probably seek during reading the
+directory and a fair bit while reading the inodes themselves.
+
+However... with readahead, and stat()ing in inode order, we should be
+in the best place possible to hit the cache.
+
+In a (not very good) benchmark of "how long does it take to find the first
+15,000 messages in my Maildir after 'echo 3 > /proc/sys/vm/drop_caches'",
+this patch consistently cut at least 8 seconds off the scan time.
+
+Without patch: 50 seconds
+With patch: 38-42 seconds.
+
+(I did this in a previous maildir reading project and saw large improvements too)
+---
+ notmuch-new.c |   32 +++++++++++++++-----------------
+ 1 files changed, 15 insertions(+), 17 deletions(-)
+
+diff --git a/notmuch-new.c b/notmuch-new.c
+index 83a05ba..11fad8c 100644
+--- a/notmuch-new.c
++++ b/notmuch-new.c
+@@ -73,6 +73,11 @@ add_files_print_progress (add_files_state_t *state)
+     fflush (stdout);
+ }
++static int ino_cmp(const struct dirent **a, const struct dirent **b)
++{
++  return ((*a)->d_ino < (*b)->d_ino)? -1: 1;
++}
++
+ /* Examine 'path' recursively as follows:
+  *
+  *   o Ask the filesystem for the mtime of 'path' (path_mtime)
+@@ -100,13 +105,12 @@ add_files_recursive (notmuch_database_t *notmuch,
+                    add_files_state_t *state)
+ {
+     DIR *dir = NULL;
+-    struct dirent *e, *entry = NULL;
+-    int entry_length;
+-    int err;
++    struct dirent *entry = NULL;
+     char *next = NULL;
+     time_t path_mtime, path_dbtime;
+     notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
+     notmuch_message_t *message = NULL;
++    struct dirent **namelist = NULL;
+     /* If we're told to, we bail out on encountering a read-only
+      * directory, (with this being a clear clue from the user to
+@@ -122,31 +126,23 @@ add_files_recursive (notmuch_database_t *notmuch,
+     path_mtime = st->st_mtime;
+     path_dbtime = notmuch_database_get_timestamp (notmuch, path);
++    int n_entries= scandir(path, &namelist, 0, ino_cmp);
+-    dir = opendir (path);
+-    if (dir == NULL) {
++    if (n_entries == -1) {
+       fprintf (stderr, "Error opening directory %s: %s\n",
+                path, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+     }
+-    entry_length = offsetof (struct dirent, d_name) +
+-      pathconf (path, _PC_NAME_MAX) + 1;
+-    entry = malloc (entry_length);
++    int i=0;
+     while (!interrupted) {
+-      err = readdir_r (dir, entry, &e);
+-      if (err) {
+-          fprintf (stderr, "Error reading directory: %s\n",
+-                   strerror (errno));
+-          ret = NOTMUCH_STATUS_FILE_ERROR;
+-          goto DONE;
+-      }
+-
+-      if (e == NULL)
++      if (i == n_entries)
+           break;
++        entry= namelist[i++];
++
+       /* If this directory hasn't been modified since the last
+        * add_files, then we only need to look further for
+        * sub-directories. */
+@@ -243,6 +239,8 @@ add_files_recursive (notmuch_database_t *notmuch,
+       free (entry);
+     if (dir)
+       closedir (dir);
++    if (namelist)
++      free (namelist);
+     return ret;
+ }
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/bar/baz/new/27:2, b/test/corpora/default/bar/baz/new/27:2,
new file mode 100644 (file)
index 0000000..7f0f045
--- /dev/null
@@ -0,0 +1,21 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 17:59:49 -0800
+Subject: [notmuch] New to the list
+In-Reply-To: <1258498485-sup-142@elly>
+References: <1258498485-sup-142@elly>
+Message-ID: <yun3a4cegoa.fsf@aiko.keithp.com>
+
+On Tue, 17 Nov 2009 23:57:18 +0100, Israel Herraiz <isra at herraiz.org> wrote:
+
+> "Not much" sounds interesting, and I wonder whether it could be
+> integrated with the views of Sup (inbox, threads, etc). So I have
+> subscribed to the list to keep an eye on what's going on here.
+
+We've tried to clone much of the sup UI inside emacs, including the
+inbox and threaded message presentation. Of course, we had to "improve"
+it a bit, as much due to the differences between curses and emacs as due
+to personal preferences...
+
+-keith
+
diff --git a/test/corpora/default/bar/baz/new/28:2, b/test/corpora/default/bar/baz/new/28:2,
new file mode 100644 (file)
index 0000000..83ce01b
--- /dev/null
@@ -0,0 +1,38 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 18:03:17 -0800
+Subject: [notmuch] Introducing myself
+In-Reply-To: <20091118002059.067214ed@hikari>
+References: <20091118002059.067214ed@hikari>
+Message-ID: <yun1vjwegii.fsf@aiko.keithp.com>
+
+On Wed, 18 Nov 2009 00:20:59 +0100, Adrian Perez de Castro <aperez at igalia.com> wrote:
+
+> Some time ago I thought
+> about doing something like Not Much and in fact I played a bit with the
+> Python+Xapian and the Python+Whoosh combinations, because I find relaxing
+> to code things in Python when I am not working and also it is installed
+> by default on most distribution. I got to have some mailboxes indexed and
+> basic searching working a couple of months ago.
+
+Sup certainly started a lot of people thinking...
+
+> Also, I would like to share one idea I had in mind, that you might find
+> interesting: One thing I have found very annoying is having to re-tag my
+> mail when the indexes get b0rked (it happened a couple of times to me while
+> using Sup), so I was planning to mails as read/unread and adding the tags
+> not just to the index, but to the mail text itself, e.g. by adding a
+> "X-Tags" header field or by reusing the "Keywords" one.
+
+Easier than that, notmuch (and sup too), provide a 'dump' command which
+just lists all of the message IDs and their associated tags. Makes
+saving tags easy and doesn't involve rewriting messages. I do this once
+a day just before my computer is backed up to an external drive.
+
+If the index is destroyed, you can reindex the messages and then reapply
+all of the tags with 'notmuch restore'.
+
+--
+keith.packard at intel.com
+
+
diff --git a/test/corpora/default/bar/cur/19:2, b/test/corpora/default/bar/cur/19:2,
new file mode 100644 (file)
index 0000000..1b7872b
--- /dev/null
@@ -0,0 +1,360 @@
+From: "Ingmar Vanhassel" <ingmar@exherbo.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 00:23:42 +0100
+Subject: [notmuch] [PATCH] Typsos
+Message-ID: <1258500222-32066-1-git-send-email-ingmar@exherbo.org>
+
+---
+ Makefile                |    4 ++--
+ README                  |    6 +++---
+ gmime-filter-reply.h    |    2 +-
+ lib/database.cc         |    2 +-
+ lib/index.cc            |    2 +-
+ lib/message.cc          |    2 +-
+ lib/messages.c          |    2 +-
+ lib/notmuch-private.h   |    2 +-
+ lib/notmuch.h           |   10 +++++-----
+ lib/sha1.c              |    2 +-
+ lib/thread.cc           |    2 +-
+ notmuch-completion.bash |    2 +-
+ notmuch-new.c           |    4 ++--
+ notmuch-search.c        |    2 +-
+ notmuch.1               |    4 ++--
+ notmuch.el              |   10 +++++-----
+ show-message.c          |    2 +-
+ 17 files changed, 30 insertions(+), 30 deletions(-)
+
+diff --git a/Makefile b/Makefile
+index 436dacf..96aaa73 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,4 +1,4 @@
+-# Default FLAGS, (can be overriden by user such as "make CFLAGS=-O2")
++# Default FLAGS, (can be overridden by user such as "make CFLAGS=-O2")
+ WARN_FLAGS=-Wall -Wextra -Wmissing-declarations -Wwrite-strings -Wswitch-enum
+ CFLAGS=-O2
+@@ -14,7 +14,7 @@ override CXXFLAGS += $(WARN_FLAGS) $(extra_cflags) $(extra_cxxflags)
+ override LDFLAGS += `pkg-config --libs glib-2.0 gmime-2.4 talloc` \
+                       `xapian-config --libs`
+-# Include our local Makfile.local first so that its first target is default
++# Include our local Makefile.local first so that its first target is default
+ include Makefile.local
+ include lib/Makefile.local
+diff --git a/README b/README
+index 40f05ab..27af77f 100644
+--- a/README
++++ b/README
+@@ -3,7 +3,7 @@ Notmuch - thread-based email index, search and tagging.
+ Notmuch is a system for indexing, searching, reading, and tagging
+ large collections of email messages. It uses the Xapian library to
+ provide fast, full-text search of very large collection of email with
+-a very convenient search syntas.
++a very convenient search syntax.
+ Notmuch is free software, released under the GNU General Public
+ License version 3 (or later).
+@@ -45,7 +45,7 @@ obtaining a more sophisticated interface:
+       notmuch.el file in this distribution.
+       If someone were to write a curses-based interface, or similar,
+-      it might also be reasonable to buil on the "notmuch"
++      it might also be reasonable to build on the "notmuch"
+       command-line interface.
+      2. Build on top of the notmuch library interface.
+@@ -67,4 +67,4 @@ still in development. We would appreciate any contributions to these
+ efforts.
+-      
+\ No newline at end of file
++      
+diff --git a/gmime-filter-reply.h b/gmime-filter-reply.h
+index 41cbc13..b7cbc6b 100644
+--- a/gmime-filter-reply.h
++++ b/gmime-filter-reply.h
+@@ -40,7 +40,7 @@ typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass;
+  * @saw_nl: previous char was a \n
+  * @saw_angle: previous char was a >
+  *
+- * A filter to insert/remove reply markers (lines begining with >)
++ * A filter to insert/remove reply markers (lines beginning with >)
+  **/
+ struct _GMimeFilterReply {
+       GMimeFilter parent_object;
+diff --git a/lib/database.cc b/lib/database.cc
+index 3c8d626..27597cf 100644
+--- a/lib/database.cc
++++ b/lib/database.cc
+@@ -180,7 +180,7 @@ notmuch_status_to_string (notmuch_status_t status)
+     case NOTMUCH_STATUS_TAG_TOO_LONG:
+       return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+     case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+-      return "Unblanced number of calls to notmuch_message_freeze/thaw";
++      return "Unbalanced number of calls to notmuch_message_freeze/thaw";
+     default:
+     case NOTMUCH_STATUS_LAST_STATUS:
+       return "Unknown error status value";
+diff --git a/lib/index.cc b/lib/index.cc
+index 65b83b3..80df64b 100644
+--- a/lib/index.cc
++++ b/lib/index.cc
+@@ -198,7 +198,7 @@ _index_mime_part (notmuch_message_t *message,
+               if (i == 1)
+                   continue;
+               if (i > 1)
+-                  fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Indexing anyway.\n");
++                  fprintf (stderr, "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+           }
+           _index_mime_part (message,
+                             g_mime_multipart_get_part (multipart, i));
+diff --git a/lib/message.cc b/lib/message.cc
+index a4b090b..1d6623f 100644
+--- a/lib/message.cc
++++ b/lib/message.cc
+@@ -144,7 +144,7 @@ _notmuch_message_create (const void *talloc_owner,
+ }
+ /* Create a new notmuch_message_t object for a specific message ID,
+- * (which may or may not already exist in the databas).
++ * (which may or may not already exist in the database).
+  *
+  * Here, 'talloc owner' is an optional talloc context to which the new
+  * message will belong. This allows for the caller to not bother
+diff --git a/lib/messages.c b/lib/messages.c
+index a588f8f..2f7c283 100644
+--- a/lib/messages.c
++++ b/lib/messages.c
+@@ -47,7 +47,7 @@ _notmuch_message_list_create (const void *ctx)
+     return list;
+ }
+-/* Append 'node' (which can of course point to an aribtrarily long
++/* Append 'node' (which can of course point to an arbitrarily long
+  * list of nodes) to the end of 'list'.
+  */
+ void
+diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
+index 6036ce4..af82e58 100644
+--- a/lib/notmuch-private.h
++++ b/lib/notmuch-private.h
+@@ -235,7 +235,7 @@ notmuch_message_file_open (const char *filename);
+ notmuch_message_file_t *
+ _notmuch_message_file_open_ctx (void *ctx, const char *filename);
+-/* Close a notmuch message preivously opened with notmuch_message_open. */
++/* Close a notmuch message previously opened with notmuch_message_open. */
+ void
+ notmuch_message_file_close (notmuch_message_file_t *message);
+diff --git a/lib/notmuch.h b/lib/notmuch.h
+index 32b5332..384c177 100644
+--- a/lib/notmuch.h
++++ b/lib/notmuch.h
+@@ -222,7 +222,7 @@ notmuch_database_get_timestamp (notmuch_database_t *database,
+ /* Add a new message to the given notmuch database.
+  *
+- * Here,'filename' should be a path relative to the the path of
++ * Here,'filename' should be a path relative to the path of
+  * 'database' (see notmuch_database_get_path), or else should be an
+  * absolute filename with initial components that match the path of
+  * 'database'.
+@@ -258,7 +258,7 @@ notmuch_database_add_message (notmuch_database_t *database,
+                             const char *filename,
+                             notmuch_message_t **message);
+-/* Find a message with the given messsage_id.
++/* Find a message with the given message_id.
+  *
+  * If the database contains a message with the given message_id, then
+  * a new notmuch_message_t object is returned. The caller should call
+@@ -620,7 +620,7 @@ notmuch_messages_advance (notmuch_messages_t *messages);
+ /* Destroy a notmuch_messages_t object.
+  *
+  * It's not strictly necessary to call this function. All memory from
+- * the notmuch_messages_t object will be reclaimed when the containg
++ * the notmuch_messages_t object will be reclaimed when the containing
+  * query object is destroyed.
+  */
+ void
+@@ -865,7 +865,7 @@ notmuch_tags_has_more (notmuch_tags_t *tags);
+ /* Get the current tag from 'tags' as a string.
+  *
+  * Note: The returned string belongs to 'tags' and has a lifetime
+- * identical to it (and the query to which it utlimately belongs).
++ * identical to it (and the query to which it ultimately belongs).
+  *
+  * See the documentation of notmuch_message_get_tags for example code
+  * showing how to iterate over a notmuch_tags_t object.
+@@ -884,7 +884,7 @@ notmuch_tags_advance (notmuch_tags_t *tags);
+ /* Destroy a notmuch_tags_t object.
+  *
+  * It's not strictly necessary to call this function. All memory from
+- * the notmuch_tags_t object will be reclaimed when the containg
++ * the notmuch_tags_t object will be reclaimed when the containing
+  * message or query objects are destroyed.
+  */
+ void
+diff --git a/lib/sha1.c b/lib/sha1.c
+index ff4dd16..cc48108 100644
+--- a/lib/sha1.c
++++ b/lib/sha1.c
+@@ -43,7 +43,7 @@ _hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
+     return result;
+ }
+-/* Create a hexadcimal string version of the SHA-1 digest of 'str'
++/* Create a hexadecimal string version of the SHA-1 digest of 'str'
+  * (including its null terminating character).
+  *
+  * This function returns a newly allocated string which the caller
+diff --git a/lib/thread.cc b/lib/thread.cc
+index 4411d64..da58edc 100644
+--- a/lib/thread.cc
++++ b/lib/thread.cc
+@@ -190,7 +190,7 @@ _resolve_thread_relationships (unused (notmuch_thread_t *thread))
+  * subject line, the total count of messages, and all authors). The
+  * second search is for all messages that are in the thread and that
+  * also match the given query_string. This is to allow for a separate
+- * count of matched messages, and to allow a viewer to diplay these
++ * count of matched messages, and to allow a viewer to display these
+  * messages differently.
+  *
+  * Here, 'ctx' is talloc context for the resulting thread object.
+diff --git a/notmuch-completion.bash b/notmuch-completion.bash
+index ad55f6d..cdad05d 100644
+--- a/notmuch-completion.bash
++++ b/notmuch-completion.bash
+@@ -1,4 +1,4 @@
+-# Bash completion for notmutch
++# Bash completion for notmuch
+ #
+ # Copyright ?? 2009 Carl Worth
+ #
+diff --git a/notmuch-new.c b/notmuch-new.c
+index 83a05ba..5405a9f 100644
+--- a/notmuch-new.c
++++ b/notmuch-new.c
+@@ -303,7 +303,7 @@ add_files (notmuch_database_t *notmuch,
+ /* XXX: This should be merged with the add_files function since it
+  * shares a lot of logic with it. */
+-/* Recursively count all regular files in path and all sub-direcotries
++/* Recursively count all regular files in path and all sub-directories
+  * of path.  The result is added to *count (which should be
+  * initialized to zero by the top-level caller before calling
+  * count_files). */
+@@ -469,7 +469,7 @@ notmuch_new_command (void *ctx,
+     if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
+       printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
+-              "they will never receive new mail), marking these directores as\n"
++              "they will never receive new mail), marking these directories as\n"
+               "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
+               "much more efficient (it won't even look in those directories).\n");
+     }
+diff --git a/notmuch-search.c b/notmuch-search.c
+index 8db09c7..ac81372 100644
+--- a/notmuch-search.c
++++ b/notmuch-search.c
+@@ -76,7 +76,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
+     query_str = query_string_from_args (ctx, argc, argv);
+     if (query_str == NULL) {
+-      fprintf (stderr, "Out of moemory.\n");
++      fprintf (stderr, "Out of memory.\n");
+       return 1;
+     }
+diff --git a/notmuch.1 b/notmuch.1
+index 6c3d10f..86d5f59 100644
+--- a/notmuch.1
++++ b/notmuch.1
+@@ -60,7 +60,7 @@ archives, and will then proceed to build a database that indexes the
+ mail to allow for fast search of the archive.
+ This directory can contain any number of sub-directories and should
+-primarily contain only files with indvidual email messages
++primarily contain only files with individual email messages
+ (eg. maildir or mh archives are perfect). If there are other,
+ non-email files (such as indexes maintained by other email programs)
+ then notmuch will do its best to detect those and ignore them.
+@@ -173,7 +173,7 @@ Constructs a reply template for a set of messages.
+ See the documentation of
+ .B search
+-for deatils of the supported syntax of search terms.
++for details of the supported syntax of search terms.
+ To make replying to email easier,
+ .B notmuch reply
+diff --git a/notmuch.el b/notmuch.el
+index 8894a8e..7e01ed6 100644
+--- a/notmuch.el
++++ b/notmuch.el
+@@ -205,7 +205,7 @@ Unlike builtin `next-line' this version accepts no arguments."
+ (defun notmuch-show-mark-read-then-archive-thread ()
+   "Remove \"unread\" tag from each message, then archive and show next thread.
+-Archive each message currrently shown by removing the \"unread\"
++Archive each message currently shown by removing the \"unread\"
+ and \"inbox\" tag from each. Then kill this buffer and show the
+ next thread from the search from which this thread was originally
+ shown.
+@@ -220,7 +220,7 @@ buffer."
+ (defun notmuch-show-archive-thread ()
+   "Archive each message in thread, and show next thread from search.
+-Archive each message currrently shown by removing the \"inbox\"
++Archive each message currently shown by removing the \"inbox\"
+ tag from each. Then kill this buffer and show the next thread
+ from the search from which this thread was originally shown.
+@@ -340,7 +340,7 @@ there are no more unread messages past the current point."
+       (notmuch-show-next-message)))
+ (defun notmuch-show-next-open-message ()
+-  "Advance to the the next message which is not hidden.
++  "Advance to the next message which is not hidden.
+ If read messages are currently hidden, advance to the next unread
+ message. Otherwise, advance to the next message."
+@@ -674,7 +674,7 @@ thread from that buffer can be show when done with this one)."
+       )))
+ (defvar notmuch-search-authors-width 40
+-  "Number of columns to use to diplay authors in a notmuch-search buffer.")
++  "Number of columns to use to display authors in a notmuch-search buffer.")
+ (defvar notmuch-search-mode-map
+   (let ((map (make-sparse-keymap)))
+@@ -910,7 +910,7 @@ the beginning of the buffer).
+ This command toggles the sort order for the current search.
+-Note that any fitlered searches created by
++Note that any filtered searches created by
+ `notmuch-search-filter' retain the search order of the parent
+ search."
+   (interactive)
+diff --git a/show-message.c b/show-message.c
+index 79b02e2..38f5897 100644
+--- a/show-message.c
++++ b/show-message.c
+@@ -38,7 +38,7 @@ show_message_part (GMimeObject *part, int *part_count,
+               if (i == 1)
+                   continue;
+               if (i > 1)
+-                  fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
++                  fprintf (stderr, "Warning: Unexpected extra parts of multipart/signed. Continuing.\n");
+           }
+           show_message_part (g_mime_multipart_get_part (multipart, i),
+                              part_count, show_part);
+-- 
+1.6.5.2.433.g23cdb
+
+
diff --git a/test/corpora/default/bar/cur/20:2, b/test/corpora/default/bar/cur/20:2,
new file mode 100644 (file)
index 0000000..f08a314
--- /dev/null
@@ -0,0 +1,101 @@
+Date: Wed, 18 Nov 2009 00:20:59 +0100
+From: Adrian Perez de Castro <aperez@igalia.com>
+To: notmuch@notmuchmail.org
+Message-ID: <20091118002059.067214ed@hikari>
+Organization: Igalia
+X-Mailer: Claws Mail 3.7.3 (GTK+ 2.18.3; x86_64-redhat-linux-gnu)
+Face: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAAXNSR0IArs4c6QAAADBQTFRFBwcHFhYWKCgoNzc3SEhIV1dXaGhod3d3iIiIlpaWqKiouLi4x8fH2NjY5+fn/v7+rSjDkgAAAjVJREFUOE9l07tvE0EQwOHfrkV9O+eko7g701BBfECJsIigT2IpooIqaSiRUEB0REj00FBQgYSCkhry+gecUPJybJeIxLumTbilsH2PMNXufDOa3ZVW+1JkpbUmD/8+vXR3c7or4Gz93mH309Kz8/C9/RQge7VfhW/LW+PF8IkrQ7Z6OKmQr1tl+LU/yWP9mxJka9O88fZHPwf/7u0kLyCnX3I4fQhgjAgIfi+HHw5A1Y2ggIMcFKAEnRoL0M3BosI4TI2IATjuT8DvSNJoNNJgkIhxlr9TUHeSpDnfohlIrMBlU+BGmsZqfr69FMfGMw4NoG835+J62riWyjQ/uXlTQjNUIoYegMsBM0pCD8oDas7n4HQsBghXFxJTW42KDs+4XLfjsN0wOYgABqARjMKIHIaAQnmHjsI5Cvi9Cf6k03OoWBkpIP3Q7354+dEimFBKHbMP9oKjwfd9gbrxR5KDToczK4uPF8UgNomKU2GaENRi77zyDKICxKBS4xXYbONPMQMdYZTBwMiMWiUg9g6UJ3OBogzjV8E7sBVwyvfAOYdQhsABzuOxI1MGZbs98Q6Md5UOfbbR2R0eWOesrnRw5ajT6f60LrNhWIHZpBnUWv2s14ukArWWTqTes3YQxRXgFkcMu70TPYqqUBs0YwmO967OVIdTG4bY4a7WLaqgLm5vbHdH5np0Dri//fmg7y8scB4u3+zsuNlH0X+g19bby69b+TYH6isvns8VdQWgxj9tHP8AR5/hSdYqkwsAAAAASUVORK5CYII=
+Mime-Version: 1.0
+Subject: [notmuch] Introducing myself
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============1167731900=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+--===============1167731900==
+Content-Type: multipart/signed; micalg=PGP-SHA1;
+ boundary="Sig_/ayZz9m37AOMROJCyUudvXvZ"; protocol="application/pgp-signature"
+
+--Sig_/ayZz9m37AOMROJCyUudvXvZ
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: quoted-printable
+
+
+Hello to all,
+
+I have just heard about Not Much today in some random Linux-related news
+site (LWN?), my name is Adrian Perez and I work as systems administrator
+(although I can do some code as well :P). I have always thought that the
+ideas behind Sup were great, but after some time using it, I got tired of
+the oddities that it has. I also do not like doing things like having to
+install Ruby just for reading and sorting mails. Some time ago I thought
+about doing something like Not Much and in fact I played a bit with the
+Python+Xapian and the Python+Whoosh combinations, because I find relaxing
+to code things in Python when I am not working and also it is installed
+by default on most distribution. I got to have some mailboxes indexed and
+basic searching working a couple of months ago. Lately I have been very
+busy and had no time for coding, and them... boom! Not Much appears -- and
+it is almost exactly what I was trying to do, but faster. I have been
+playing a bit with Not Much today, and I think it has potential.
+
+Also, I would like to share one idea I had in mind, that you might find
+interesting: One thing I have found very annoying is having to re-tag my
+mail when the indexes get b0rked (it happened a couple of times to me while
+using Sup), so I was planning to mails as read/unread and adding the tags
+not just to the index, but to the mail text itself, e.g. by adding a
+"X-Tags" header field or by reusing the "Keywords" one. This way, the index
+could be totally recreated by re-reading the mail directories, and this
+would also allow to a tools like OfflineIMAP [1] to get the mails into a
+local maildir, tagging and indexing the mails with the e-mail reader and
+then syncing back the messages with the "X-Tags" header to the IMAP server.
+This would allow to use the mail reader from a different computer and still
+have everything tagged finely.
+
+Best regards,
+
+
+---
+[1] http://software.complete.org/software/projects/show/offlineimap
+
+--=20
+Adrian Perez de Castro <aperez@igalia.com>
+Igalia - Free Software Engineering
+
+--Sig_/ayZz9m37AOMROJCyUudvXvZ
+Content-Type: application/pgp-signature; name=signature.asc
+Content-Disposition: attachment; filename=signature.asc
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.13 (GNU/Linux)
+
+iEYEARECAAYFAksDL+AACgkQkcVZ2+TJEjtsuQCfXmilW8WpMQHCnwwJjRE1PWZy
+oFAAn3MmXC5sW7MvCFjs7ks6U16zgMEg
+=eL9p
+-----END PGP SIGNATURE-----
+
+--Sig_/ayZz9m37AOMROJCyUudvXvZ--
+
+--===============1167731900==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============1167731900==--
+
diff --git a/test/corpora/default/bar/new/21:2, b/test/corpora/default/bar/new/21:2,
new file mode 100644 (file)
index 0000000..7ff55cc
--- /dev/null
@@ -0,0 +1,102 @@
+MIME-Version: 1.0
+Date: Tue, 17 Nov 2009 16:23:53 -0800
+Message-ID: <cf0c4d610911171623q3e27a0adx802e47039b57604b@mail.gmail.com>
+From: Alex Botero-Lowry <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Content-Type: multipart/mixed; boundary=0016e64ca4d8f27a4804789a4139
+Subject: [notmuch] [PATCH] Error out if no query is supplied to search
+       instead of going into an infinite loop
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+--0016e64ca4d8f27a4804789a4139
+Content-Type: multipart/alternative; boundary=0016e64ca4d8f27a3604789a4137
+
+--0016e64ca4d8f27a3604789a4137
+Content-Type: text/plain; charset=ISO-8859-1
+
+In this case error out when no query is supplied. There seems to be an
+infinite-loop casued by i think notmuch_query_search_threads having
+an exception:
+
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+A Xapian exception occurred: Syntax: <expression> AND <expression>
+
+I'll look into that bug specifically a bit later.
+
+It might be better to do a usage instead of just throwing an error here?
+
+alex
+
+--0016e64ca4d8f27a3604789a4137
+Content-Type: text/html; charset=ISO-8859-1
+
+In this case error out when no query is supplied. There seems to be an infinite-loop casued by i think notmuch_query_search_threads having<br>an exception:<br><br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>
+A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>
+A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>
+A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>
+A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>
+A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br>A Xapian exception occurred: Syntax: &lt;expression&gt; AND &lt;expression&gt;<br><br>I&#39;ll look into that bug specifically a bit later.<br>
+<br>It might be better to do a usage instead of just throwing an error here?<br><br>alex<br>
+
+--0016e64ca4d8f27a3604789a4137--
+--0016e64ca4d8f27a4804789a4139
+Content-Type: application/octet-stream; 
+       name="0001-Error-out-if-no-query-is-supplied-to-search-instead-.patch"
+Content-Disposition: attachment; 
+       filename="0001-Error-out-if-no-query-is-supplied-to-search-instead-.patch"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_g25cms190
+
+RnJvbSAzZjk0MzFmNzRhNWZmNjZjODRjODY5YTNlMjZjMmJhZDQyYmVkMWIxIE1vbiBTZXAgMTcg
+MDA6MDA6MDAgMjAwMQpGcm9tOiBBbGV4YW5kZXIgQm90ZXJvLUxvd3J5IDxhbGV4LmJvdGVyb2xv
+d3J5QGdtYWlsLmNvbT4KRGF0ZTogVHVlLCAxNyBOb3YgMjAwOSAxNjoyMDoyOCAtMDgwMApTdWJq
+ZWN0OiBbUEFUQ0hdIEVycm9yIG91dCBpZiBubyBxdWVyeSBpcyBzdXBwbGllZCB0byBzZWFyY2gg
+aW5zdGVhZCBvZiBnb2luZyBpbnRvIGFuIGluZmluaXRlIGxvb3AKCi0tLQogbm90bXVjaC1zZWFy
+Y2guYyB8ICAgIDUgKysrKysKIDEgZmlsZXMgY2hhbmdlZCwgNSBpbnNlcnRpb25zKCspLCAwIGRl
+bGV0aW9ucygtKQoKZGlmZiAtLWdpdCBhL25vdG11Y2gtc2VhcmNoLmMgYi9ub3RtdWNoLXNlYXJj
+aC5jCmluZGV4IDhkYjA5YzcuLmQ5NGZjY2QgMTAwNjQ0Ci0tLSBhL25vdG11Y2gtc2VhcmNoLmMK
+KysrIGIvbm90bXVjaC1zZWFyY2guYwpAQCAtNjYsNiArNjYsMTEgQEAgbm90bXVjaF9zZWFyY2hf
+Y29tbWFuZCAodm9pZCAqY3R4LCBpbnQgYXJnYywgY2hhciAqYXJndltdKQogICAgIGFyZ2MgLT0g
+aTsKICAgICBhcmd2ICs9IGk7CiAKKyAgICBpZiAoYXJnYyA9PSAwKSB7CisgICAgICAgIGZwcmlu
+dGYgKHN0ZGVyciwgIk5vIHF1ZXJ5IHByb3ZpZGVkXG4iKTsKKyAgICAgICAgcmV0dXJuIDE7Cisg
+ICAgfQorCiAgICAgY29uZmlnID0gbm90bXVjaF9jb25maWdfb3BlbiAoY3R4LCBOVUxMLCBOVUxM
+KTsKICAgICBpZiAoY29uZmlnID09IE5VTEwpCiAJcmV0dXJuIDE7Ci0tIAoxLjYuNS4yCgo=
+--0016e64ca4d8f27a4804789a4139
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--0016e64ca4d8f27a4804789a4139--
+
diff --git a/test/corpora/default/bar/new/22:2, b/test/corpora/default/bar/new/22:2,
new file mode 100644 (file)
index 0000000..08adada
--- /dev/null
@@ -0,0 +1,84 @@
+Date: Tue, 17 Nov 2009 19:50:40 -0500
+From: Lars Kellogg-Stedman <lars@seas.harvard.edu>
+To: Keith Packard <keithp@keithp.com>
+Message-ID: <20091118005040.GA25380@dottiness.seas.harvard.edu>
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+       <87iqd9rn3l.fsf@vertex.dottedmag>
+       <20091117203301.GV3165@dottiness.seas.harvard.edu>
+       <yunaayketfm.fsf@aiko.keithp.com>
+MIME-Version: 1.0
+In-Reply-To: <yunaayketfm.fsf@aiko.keithp.com>
+User-Agent: Mutt/1.5.19 (2009-01-05)
+Cc: notmuch@notmuchmail.org
+Subject: Re: [notmuch] Working with Maildir storage?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============1483126515=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+
+--===============1483126515==
+Content-Type: multipart/signed; micalg=pgp-sha256;
+       protocol="application/pgp-signature"; boundary="9amGYk9869ThD9tj"
+Content-Disposition: inline
+
+
+--9amGYk9869ThD9tj
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+> I've also pushed a slightly more complicated (and complete) fix to my
+> private notmuch repository
+
+The version of lib/messages.cc in your repo doesn't build because it's
+missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+--=20
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+
+--9amGYk9869ThD9tj
+Content-Type: application/pgp-signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQEcBAEBCAAGBQJLA0TgAAoJENdGlQYxQazYsG0IAJ1t9h4Q3ma8z8ejeKR22Xh0
+WcuRX2x9yEXy/+aG9W7Mot0mqUQCiLdmHM/2h5N9BFHyJvfOUf8lmssrJ5OS/kp5
+j7FIx3GUELBmEZqFUPjRSQPk1hZURYdRsloKkrbQ2kAivjjb50zAAQ8Av4Cgj6cS
+3HvNNmeVfJt1NS75vm+/wn48M8Vrcdv4gvNlSOhgFOixknvRoxSyNDOHYBKvHnSV
+2HnO0GzhAQzDZAwdHBzJtb8vRmglrH33TVdrE7OW+sojYB3Wyz8r9+HIt8Q8ovzX
+nQ8p0Nf5DlF7tye3JYo0EeNm5EQJ4q0YyVYInhmtpi3A5Cyu50GcB/GZ5Sd6ajo=
+=WULe
+-----END PGP SIGNATURE-----
+
+--9amGYk9869ThD9tj--
+
+--===============1483126515==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============1483126515==--
+
diff --git a/test/corpora/default/cur/29:2, b/test/corpora/default/cur/29:2,
new file mode 100644 (file)
index 0000000..c76eff3
--- /dev/null
@@ -0,0 +1,21 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 18:04:31 -0800
+Subject: [notmuch] archive
+In-Reply-To: <20091117232137.GA7669@griffis1.net>
+References: <20091117232137.GA7669@griffis1.net>
+Message-ID: <yunzl6kd1w0.fsf@aiko.keithp.com>
+
+On Tue, 17 Nov 2009 18:21:38 -0500, Aron Griffis <agriffis at n01se.net> wrote:
+
+> Just subscribed, I'd like to catch up on the previous postings,
+> but the archive link seems to be bogus?
+
+Yeah, the archive appears broken and will need to wait until Carl
+arrives in Barcelona to get fixed.
+
+--
+keith.packard at intel.com
+
+
+
diff --git a/test/corpora/default/cur/30:2, b/test/corpora/default/cur/30:2,
new file mode 100644 (file)
index 0000000..a5b94a0
--- /dev/null
@@ -0,0 +1,75 @@
+From: "Stewart Smith" <stewart@flamingspork.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 13:22:20 +1100
+Subject: [notmuch] [PATCH] count_files: sort directory in inode order before
+       statting
+Message-ID: <1258510940-7018-1-git-send-email-stewart@flamingspork.com>
+
+---
+ notmuch-new.c |   30 ++++++++++--------------------
+ 1 files changed, 10 insertions(+), 20 deletions(-)
+
+diff --git a/notmuch-new.c b/notmuch-new.c
+index 11fad8c..c5f841a 100644
+--- a/notmuch-new.c
++++ b/notmuch-new.c
+@@ -308,36 +308,26 @@ add_files (notmuch_database_t *notmuch,
+ static void
+ count_files (const char *path, int *count)
+ {
+-    DIR *dir;
+-    struct dirent *e, *entry = NULL;
+-    int entry_length;
+-    int err;
++    struct dirent *entry = NULL;
+     char *next;
+     struct stat st;
++    struct dirent **namelist = NULL;
+-    dir = opendir (path);
++    int n_entries= scandir(path, &namelist, 0, ino_cmp);
+-    if (dir == NULL) {
++    if (n_entries == -1) {
+       fprintf (stderr, "Warning: failed to open directory %s: %s\n",
+                path, strerror (errno));
+       goto DONE;
+     }
+-    entry_length = offsetof (struct dirent, d_name) +
+-      pathconf (path, _PC_NAME_MAX) + 1;
+-    entry = malloc (entry_length);
++    int i=0;
+     while (!interrupted) {
+-      err = readdir_r (dir, entry, &e);
+-      if (err) {
+-          fprintf (stderr, "Error reading directory: %s\n",
+-                   strerror (errno));
+-          free (entry);
+-          goto DONE;
+-      }
++        if (i == n_entries)
++            break;
+-      if (e == NULL)
+-          break;
++        entry= namelist[i++];
+       /* Ignore special directories to avoid infinite recursion.
+        * Also ignore the .notmuch directory.
+@@ -376,8 +366,8 @@ count_files (const char *path, int *count)
+   DONE:
+     if (entry)
+       free (entry);
+-
+-    closedir (dir);
++    if (namelist)
++        free (namelist);
+ }
+ int
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/cur/31:2, b/test/corpora/default/cur/31:2,
new file mode 100644 (file)
index 0000000..88f17ca
--- /dev/null
@@ -0,0 +1,31 @@
+From: "Jjgod Jiang" <gzjjgod@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 11:50:17 +0800
+Subject: [notmuch] Mac OS X/Darwin compatibility issues
+Message-ID: <ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com>
+
+Hi,
+
+When I tried to compile notmuch under Mac OS X 10.6, several issues
+arisen:
+
+1. g++ reports 'warning: command line option "-Wmissing-declarations"
+is valid for C/ObjC but not for C++'
+
+2.
+notmuch-reply.c: In function ?address_is_users?:
+notmuch-reply.c:87: warning: passing argument 2 of
+?notmuch_config_get_user_other_email? from incompatible pointer type
+
+That's due to the size incompatibility of 'unsigned int' and 'size_t'
+(size_t is uint64_t in Mac OS X).
+
+3. Several errors about missing GNU extensions like getline() and strndup():
+
+warning: implicit declaration of function ?getline?
+error: ?strndup? was not declared in this scope
+
+We can implement these with fgets() and strncpy() though.
+
+- Jiang
+
diff --git a/test/corpora/default/cur/32:2, b/test/corpora/default/cur/32:2,
new file mode 100644 (file)
index 0000000..c1633cd
--- /dev/null
@@ -0,0 +1,165 @@
+From: "Jan Janak" <jan@ryngle.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 05:57:03 +0100
+Subject: [notmuch] [PATCH] notmuch new: Support for conversion of spool
+       subdirectories into tags
+Message-ID: <1258520223-15328-1-git-send-email-jan@ryngle.com>
+
+This patch makes it possible to convert subdirectory names inside the
+spool directory into message tags. Messages stored in subdirectory
+"foo" will be marked with tag "foo". Message duplicates found in several
+subdirectories will be given one tag per subdirectory.
+
+This feature can be used to synchronize notmuch's tags with with gmail
+tags, for example. Gmail IMAP servers convert tags to IMAP
+subdirectories and those subdirectories can be converted back to tags
+in notmuch.
+
+The patch modifies notmuch_database_add_message function to return a
+pointer to the message even if a message duplicate was found in the
+database. This is needed if we want to add a tag for each subdirectory
+in which a message duplicate was found.
+
+In addition to that, it makes the pointer to notmuch_config_t global
+(previously it was a local variable in notmuch_new_command). The
+configuration data structure is used by the function which converts
+subdirectory names to tags.
+
+Finally, there is a new function called subdir_to_tag. The function
+extracts the name of the subdirectory inside the spool from the full
+path of the message (also removing Maildir's cur,dir,and tmp
+subdirectories) and adds it as a new tag to the message.
+
+Signed-off-by: Jan Janak <jan at ryngle.com>
+---
+ lib/database.cc |    3 +-
+ notmuch-new.c   |   74 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 74 insertions(+), 3 deletions(-)
+
+diff --git a/lib/database.cc b/lib/database.cc
+index 3c8d626..f7799d2 100644
+--- a/lib/database.cc
++++ b/lib/database.cc
+@@ -949,7 +949,8 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
+   DONE:
+     if (message) {
+-      if (ret == NOTMUCH_STATUS_SUCCESS && message_ret)
++              if ((ret == NOTMUCH_STATUS_SUCCESS ||
++                       ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
+           *message_ret = message;
+       else
+           notmuch_message_destroy (message);
+diff --git a/notmuch-new.c b/notmuch-new.c
+index 83a05ba..d94ce16 100644
+--- a/notmuch-new.c
++++ b/notmuch-new.c
+@@ -19,6 +19,9 @@
+  */
+ #include "notmuch-client.h"
++#include <libgen.h>
++
++static notmuch_config_t *config = 0;
+ static volatile sig_atomic_t do_add_files_print_progress = 0;
+@@ -45,6 +48,69 @@ tag_inbox_and_unread (notmuch_message_t *message)
+     notmuch_message_add_tag (message, "unread");
+ }
++/*
++ * Extracts the sub-directory from the filename and adds it as a new tag to
++ * the message. The filename must begin with the database directory configured
++ * in the configuration file. This prefix is then removed. If the remaining
++ * sub-directory ends with one of the Maildir special directories (/tmp, /new,
++ * /cur) then they are removed as well. If there is anything left then the
++ * function adds it as a new tag to the message.
++ *
++ * The function does nothing if it cannot extract a sub-directory from
++ * filename.
++ */
++static void
++subdir_to_tag (char* filename, notmuch_message_t *message)
++{
++      const char* db_path;
++      char *msg_dir, *tmp;
++      int db_path_len, msg_dir_len;
++
++      if (config == NULL) return;
++    db_path = notmuch_config_get_database_path (config);
++      if (db_path == NULL) return;
++      db_path_len = strlen(db_path);
++
++      /* Make a copy of the string as dirname may need to modify it. */
++      tmp = talloc_strdup(message, filename);
++      msg_dir = dirname(tmp);
++      msg_dir_len = strlen(msg_dir);
++
++      /* If msg_dir starts with db_path, remove it, including the / which delimits
++       * it from the rest of the directory name. */
++      if (db_path_len < msg_dir_len &&
++              !strncmp(db_path, msg_dir, db_path_len)) {
++              msg_dir += db_path_len + 1;
++              msg_dir_len -= db_path_len + 1;
++      } else {
++              /* If we get here, either the message filename is not inside the
++               * database directory configured in the configuration file, or it is a
++               * file in the root directory of the database. Either way we just skip
++               * it because we do not know how to convert it to a meaningful
++               * subdirectory string that we could add as tag. */
++              goto out;
++      }
++
++      /* Special conditioning for Maildirs. If the remainder of the directory
++       * name ends with /new, /cur, or /tmp then remove it. */
++      if ((msg_dir_len >= 4) &&
++              (!strncmp(msg_dir + msg_dir_len - 4, "/new", 4) ||
++               !strncmp(msg_dir + msg_dir_len - 4, "/cur", 4) ||
++               !strncmp(msg_dir + msg_dir_len - 4, "/tmp", 4))) {
++              msg_dir[msg_dir_len - 4] = '\0';
++      }
++
++      /* If, after all the modifications, we still have a subdirectory, add it
++       * as tag. */
++      if (strlen(msg_dir)) {
++              notmuch_message_add_tag (message, msg_dir);
++      }
++
++out:
++      talloc_free(tmp);
++}
++
++
+ static void
+ add_files_print_progress (add_files_state_t *state)
+ {
+@@ -186,10 +252,15 @@ add_files_recursive (notmuch_database_t *notmuch,
+                   case NOTMUCH_STATUS_SUCCESS:
+                       state->added_messages++;
+                       tag_inbox_and_unread (message);
++                      subdir_to_tag(next, message);
+                       break;
+                   /* Non-fatal issues (go on to next file) */
+                   case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+-                      /* Stay silent on this one. */
++                      /* Stay silent on this one. The message already exists in the
++                               * database, that means we may have found a duplicate in
++                               * another directory. If that's the case then we add another
++                               * tag to the message with the sub-directory. */
++                              subdir_to_tag(next, message);
+                       break;
+                   case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+                       fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
+@@ -386,7 +457,6 @@ int
+ notmuch_new_command (void *ctx,
+                    unused (int argc), unused (char *argv[]))
+ {
+-    notmuch_config_t *config;
+     notmuch_database_t *notmuch;
+     add_files_state_t add_files_state;
+     double elapsed;
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/cur/33:2, b/test/corpora/default/cur/33:2,
new file mode 100644 (file)
index 0000000..a9b3252
--- /dev/null
@@ -0,0 +1,13 @@
+From: "Rolland Santimano" <rollandsantimano@yahoo.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 21:12:23 -0800 (PST)
+Subject: [notmuch] Link to mailing list archives ?
+Message-ID: <736613.51770.qm@web113505.mail.gq1.yahoo.com>
+
+The link[1] provided on the list page[2] is broken:
+[1] http://notmuchmail.org/pipermail/notmuch/
+[2] http://notmuchmail.org/mailman/listinfo/notmuch
+
+
+      
+
diff --git a/test/corpora/default/cur/34:2, b/test/corpora/default/cur/34:2,
new file mode 100644 (file)
index 0000000..b94dd06
--- /dev/null
@@ -0,0 +1,46 @@
+From: "Alexander Botero-Lowry" <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 21:45:36 -0800
+Subject: [notmuch] Mac OS X/Darwin compatibility issues
+In-Reply-To: <ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com>
+References: <ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com>
+Message-ID: <86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+
+On Wed, 18 Nov 2009 11:50:17 +0800, Jjgod Jiang <gzjjgod at gmail.com> wrote:
+> Hi,
+> 
+> When I tried to compile notmuch under Mac OS X 10.6, several issues
+> arisen:
+> 
+> 1. g++ reports 'warning: command line option "-Wmissing-declarations"
+> is valid for C/ObjC but not for C++'
+> 
+I got that too. I presume it's newly supported in GCC4.4?
+
+> 3. Several errors about missing GNU extensions like getline() and strndup():
+> 
+strndup from V8:
+
+char* strndup(char* str, size_t n) {
+  // Stupid implementation of strndup since macos isn't born with
+  // one.
+  size_t len = strlen(str);
+  if (len <= n)
+    return StrDup(str);
+  char* result = new char[n+1];
+  size_t i;
+  for (i = 0; i <= n; i++)
+    result[i] = str[i];
+  result[i] = '\0';
+  return result;
+}
+
+> warning: implicit declaration of function ?getline?
+> error: ?strndup? was not declared in this scope
+> 
+for getline do you mind trying #define _GNU_SOURCE 1
+before #include <stdio.h> in the offending files? The FreeBSD man pages
+mentions that as a way of enabling the GNU version of getline().
+
+Alex
+
diff --git a/test/corpora/default/cur/35:2, b/test/corpora/default/cur/35:2,
new file mode 100644 (file)
index 0000000..d727670
--- /dev/null
@@ -0,0 +1,24 @@
+From: "Jjgod Jiang" <gzjjgod@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 14:14:27 +0800
+Subject: [notmuch] Mac OS X/Darwin compatibility issues
+In-Reply-To: <86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+References: <ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com>
+       <86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+Message-ID: <ddd65cda0911172214t60d22b63hcfeb5a19ab54a39b@mail.gmail.com>
+
+Hi,
+
+On Wed, Nov 18, 2009 at 1:45 PM, Alexander Botero-Lowry
+<alex.boterolowry at gmail.com> wrote:
+> for getline do you mind trying #define _GNU_SOURCE 1
+> before #include <stdio.h> in the offending files? The FreeBSD man pages
+> mentions that as a way of enabling the GNU version of getline().
+
+It seems even _GNU_SOURCE is defined, getline is still not present.
+the C lib in Mac OS X simply doesn't have it. See also [1].
+
+- Jiang
+
+[1] http://stackoverflow.com/questions/1117108/compiling-c-code-using-gnu-c-getline-on-mac-osx
+
diff --git a/test/corpora/default/cur/36:2, b/test/corpora/default/cur/36:2,
new file mode 100644 (file)
index 0000000..4cd0d20
--- /dev/null
@@ -0,0 +1,25 @@
+From: "Alexander Botero-Lowry" <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 22:19:29 -0800
+Subject: [notmuch] Mac OS X/Darwin compatibility issues
+In-Reply-To: <ddd65cda0911172214t60d22b63hcfeb5a19ab54a39b@mail.gmail.com>
+References: <ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com>
+       <86einw2xof.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+       <ddd65cda0911172214t60d22b63hcfeb5a19ab54a39b@mail.gmail.com>
+Message-ID: <86d43g2w3y.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+
+On Wed, 18 Nov 2009 14:14:27 +0800, Jjgod Jiang <gzjjgod at gmail.com> wrote:
+> Hi,
+> 
+> On Wed, Nov 18, 2009 at 1:45 PM, Alexander Botero-Lowry
+> <alex.boterolowry at gmail.com> wrote:
+> > for getline do you mind trying #define _GNU_SOURCE 1
+> > before #include <stdio.h> in the offending files? The FreeBSD man pages
+> > mentions that as a way of enabling the GNU version of getline().
+> 
+> It seems even _GNU_SOURCE is defined, getline is still not present.
+> the C lib in Mac OS X simply doesn't have it. See also [1].
+> 
+Alas. Since it's ostensibly based on the FreeBSD one, I figured there
+was a chance that would fix the problem. :/
+
diff --git a/test/corpora/default/cur/37:2, b/test/corpora/default/cur/37:2,
new file mode 100644 (file)
index 0000000..4e17e82
--- /dev/null
@@ -0,0 +1,22 @@
+From: "Alexander Botero-Lowry" <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 00:02:56 -0800
+Subject: [notmuch] request for pull
+Message-ID: <86aayk2rbj.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+
+The following changes since commit e8c9c3e6a534fc6c2919c2c1de63cea7250eb488:
+  Ingmar Vanhassel (1):
+        Makefile: Manual pages shouldn't be executable
+
+are available in the git repository at:
+
+  git://alexbl.net/notmuch.git master
+
+Alexander Botero-Lowry (2):
+      Error out if no query is supplied to search instead of going into an infinite loop
+      set a local truncate-line variable in notmuch-search-mode, so that subjects don't wrap and make the output look weird
+
+ notmuch-search.c |    5 +++++
+ notmuch.el       |    1 +
+ 2 files changed, 6 insertions(+), 0 deletions(-)
+
diff --git a/test/corpora/default/cur/38:2, b/test/corpora/default/cur/38:2,
new file mode 100644 (file)
index 0000000..f5537ff
--- /dev/null
@@ -0,0 +1,40 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 00:29:59 -0800
+Subject: [notmuch] [PATCH] Create a default notmuch-show-hook that
+       highlights URLs and uses word-wrap
+Message-ID: <1258532999-9316-1-git-send-email-keithp@keithp.com>
+
+I created the notmuch-show-hook precisely so I could add these two
+options, but I suspect most people will want them, so I just made them
+the default. If you don't want them, you can use remove-hook to get
+rid of this.
+
+Signed-off-by: Keith Packard <keithp at keithp.com>
+---
+ notmuch.el |    8 ++++++++
+ 1 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/notmuch.el b/notmuch.el
+index 1bb1294..c95cb43 100644
+--- a/notmuch.el
++++ b/notmuch.el
+@@ -698,6 +698,14 @@ view, (remove the \"inbox\" tag from each), with either
+   :options '(goto-address)
+   :group 'notmuch)
++; Make show mode a bit prettier, highlighting URLs and using word wrap
++
++(defun notmuch-show-pretty-hook ()
++  (goto-address-mode 1)
++  (visual-line-mode))
++
++(add-hook 'notmuch-show-hook 'notmuch-show-pretty-hook)
++
+ (defun notmuch-show (thread-id &optional parent-buffer)
+   "Run \"notmuch show\" with the given thread ID and display results.
+-- 
+1.6.5.2
+
+
diff --git a/test/corpora/default/cur/39:2, b/test/corpora/default/cur/39:2,
new file mode 100644 (file)
index 0000000..637b3c7
--- /dev/null
@@ -0,0 +1,32 @@
+From: "Alexander Botero-Lowry" <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 00:52:34 -0800
+Subject: [notmuch] [PATCH] Create a default notmuch-show-hook that
+ highlights URLs and uses word-wrap
+In-Reply-To: <1258532999-9316-1-git-send-email-keithp@keithp.com>
+References: <1258532999-9316-1-git-send-email-keithp@keithp.com>
+Message-ID: <867hto2p0t.fsf@fortitudo.i-did-not-set--mail-host-address--so-tickle-me>
+
+On Wed, 18 Nov 2009 00:29:59 -0800, Keith Packard <keithp at keithp.com> wrote:
+> I created the notmuch-show-hook precisely so I could add these two
+> options, but I suspect most people will want them, so I just made them
+> the default. If you don't want them, you can use remove-hook to get
+> rid of this.
+> 
+Yes, hooks should be added for search as well. :)
+
+> +; Make show mode a bit prettier, highlighting URLs and using word wrap
+> +
+> +(defun notmuch-show-pretty-hook ()
+> +  (goto-address-mode 1)
+> +  (visual-line-mode))
+> +
+visual-line-mode turns out to make subject look pretty ugly if there is a
+continuation. It doesn't do much good for the citation headers
+either. We probably need to do our own intelligent wrapping rather then
+use visual-line-mode to make this actually look right.
+
+goto-address-mode is important though. :)
+
+alex
+
diff --git a/test/corpora/default/cur/40:2, b/test/corpora/default/cur/40:2,
new file mode 100644 (file)
index 0000000..91a15a8
--- /dev/null
@@ -0,0 +1,31 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 01:42:02 -0800
+Subject: [notmuch] [PATCH 1/2] Close message file after parsing message
+ headers
+In-Reply-To: <yunbpj0etua.fsf@aiko.keithp.com>
+References: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+       <87lji5cbwo.fsf@yoom.home.cworth.org> <yunbpj0etua.fsf@aiko.keithp.com>
+Message-ID: <87pr7gqidx.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 13:15:25 -0800, Keith Packard <keithp at keithp.com> wrote:
+> Threading the message also involves displaying the from and to contents,
+> which requires opening the message file. The alternative to the fix I
+> provided is to just parse all of the message headers when first opening
+> the message; it could then be immediately closed and the hash referred
+> to for all header data. Given the choice, just having the caller say
+> when it has finished with a message is probably a reasonable option...
+
+Hi Keith,
+
+Once I finally got back on the ground again, I pushed out a revised
+version of your patch, (didn't need the reply-to stuff anymore since I
+had fixed that differently in the meantime).
+
+I'm pretty happy with the state of this portion of the code now.
+
+Thanks Keith and Mikhail for your input on and code to fix this bug.
+
+-Carl
+
+
diff --git a/test/corpora/default/cur/41:2, b/test/corpora/default/cur/41:2,
new file mode 100644 (file)
index 0000000..da22cc0
--- /dev/null
@@ -0,0 +1,37 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:08:10 -0800
+Subject: [notmuch] Working with Maildir storage?
+In-Reply-To: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+Message-ID: <87ocn0qh6d.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+> I saw the LWN article and decided to take a look at notmuch.  I'm
+> currently using mutt and mairix to index and read a collection of
+> Maildir mail folders (around 40,000 messages total).
+
+Welcome, Lars!
+
+I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+That's very interesting. So, thanks for coming and trying out notmuch.
+
+>   Error opening
+>   /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+>   Too many open files
+
+Sadly, the lwn article coincided with me having just introduced this
+bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+fairly quickly, but there was quite a bit of latency before I could push
+the fix out. It should be fixed now.
+
+> I'm curious if this is expected behavior (i.e., notmuch does not work
+> with Maildir) or if something else is going on.
+
+Notmuch works just fine with maildir---it's one of the things that it
+likes the best.
+
+Happy hacking,
+
+-Carl
+
diff --git a/test/corpora/default/cur/42:2, b/test/corpora/default/cur/42:2,
new file mode 100644 (file)
index 0000000..98fa75f
--- /dev/null
@@ -0,0 +1,30 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:19:26 -0800
+Subject: [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands
+ remove inbox (and unread) tags
+In-Reply-To: <1258493565-13508-1-git-send-email-keithp@keithp.com>
+References: <1258493565-13508-1-git-send-email-keithp@keithp.com>
+Message-ID: <87k4xoqgnl.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 13:32:45 -0800, Keith Packard <keithp at keithp.com> wrote:
+> When closing a thread view, mark the thread as archived by removing
+> the "inbox" tag, and for the 'x' variant, the "unread" tag as well,
+> then kill the buffer and update the search window view as well.
+> 
+> This makes 'x' much the same as 'a', but instead of taking you to the
+> next message, it takes you back to the search window instead.
+
+I don't like this---but that's because I use 'x' precisely *because* it
+preserves these tags.
+
+Otherwise, you might as well just remove inbox and unread as soon as the
+message is presented to the user. And that's a bug in a lot of other
+email programs that I'm unwilling to replicate.
+
+We may run into a need to define different ways that people like to work
+with their email here. (I know that so far I've just been coding up the
+way I want my mail to work.)
+
+-Carl
+
diff --git a/test/corpora/default/cur/43:2, b/test/corpora/default/cur/43:2,
new file mode 100644 (file)
index 0000000..2f6c8bc
--- /dev/null
@@ -0,0 +1,26 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:22:12 -0800
+Subject: [notmuch] archive
+In-Reply-To: <yunzl6kd1w0.fsf@aiko.keithp.com>
+References: <20091117232137.GA7669@griffis1.net>
+       <yunzl6kd1w0.fsf@aiko.keithp.com>
+Message-ID: <87iqd8qgiz.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 18:04:31 -0800, Keith Packard <keithp at keithp.com> wrote:
+> On Tue, 17 Nov 2009 18:21:38 -0500, Aron Griffis <agriffis at n01se.net> wrote:
+> 
+> > Just subscribed, I'd like to catch up on the previous postings,
+> > but the archive link seems to be bogus?
+> 
+> Yeah, the archive appears broken and will need to wait until Carl
+> arrives in Barcelona to get fixed.
+
+Fixed it in transit in Frankfurt---with only moments to spare on my
+battery and no outlets in sight.
+
+Thanks for the report, Aron. And welcome to notmuch!
+
+-Carl (who wants to reply to a lot more mail, but will have to wait
+ until later for that)
+
diff --git a/test/corpora/default/cur/44:2, b/test/corpora/default/cur/44:2,
new file mode 100644 (file)
index 0000000..c896c18
--- /dev/null
@@ -0,0 +1,29 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:43:50 -0800
+Subject: [notmuch] [PATCH] Older versions of install do not support -C.
+In-Reply-To: <1258496327-12086-1-git-send-email-jan@ryngle.com>
+References: <1258496327-12086-1-git-send-email-jan@ryngle.com>
+Message-ID: <87hbssqfix.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 23:18:47 +0100, Jan Janak <jan at ryngle.com> wrote:
+> Do not use -C cmdline option of install, older versions, commonly found in
+> distributions like Debian, do not seem to support it. Running make install
+> on such systems (tested on Debian Lenny) fails.
+> 
+> Signed-off-by: Jan Janak <jan at ryngle.com>
+
+Thanks, Jan. This is pushed now.
+
+And did I say welcome to notmuch yet? (It's easy to lose track with all
+the newcomers---which I'm not complaining about---especially since so
+many are sharing code.)
+
+-Carl
+
+PS. I actually really like the behavior of -C (especially when
+installing a low-level library to avoid big waterfalls of needless
+recompiles). But since we're *not* actually installing a library (yet)
+I'm happy with this patch rather than writing code in configure to check
+if "install -C" works or not.
+
diff --git a/test/corpora/default/cur/45:2, b/test/corpora/default/cur/45:2,
new file mode 100644 (file)
index 0000000..806b0e8
--- /dev/null
@@ -0,0 +1,41 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:49:52 -0800
+Subject: [notmuch] What a great idea!
+In-Reply-To: <f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com>
+References: <f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com>
+Message-ID: <87fx8cqf8v.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 23:35:30 +0100, Jan Janak <jan at ryngle.com> wrote:
+> First of all, notmuch is a wonderful idea, both the cmdline tool and
+> the emacs interface! Thanks a lot for writing it, I was really excited
+> when I read the announcement today.
+
+Ah, here's where I planned a nice welcome. So welcome (again), Jan! :-)
+
+I've been having a lot of fun with notmuch already, (though there have
+been some days of pain before it was functional enough and my
+email-reply latency went way up). But regardless---I got through that,
+and I'm able to work more efficiently with notmuch now than I could with
+sup before. So I'm happy.
+
+And I'm delighted when other people find this interesting as well.
+
+> Have you considered sending an announcement to the org-mode mailing list?
+> http://orgmode.org
+
+Thanks for the idea. I think I may have looked into org-mode years ago,
+(when I was investigating planner-mode and various emacs "personal wiki"
+systems for keeping random notes and what-not).
+
+> Various ways of searching/referencing emails from emacs were discussed
+> there several times and none of them were as elegant as notmuch (not
+> even close). Maybe notmuch would attract some of the developers
+> there..
+
+Yeah. I'll drop them a mail. Having a real emacs wizard on board would
+be nice. (I'm afraid the elisp I've written so far for this project is
+fairly grim.)
+
+-Carl
+
diff --git a/test/corpora/default/cur/46:2, b/test/corpora/default/cur/46:2,
new file mode 100644 (file)
index 0000000..bbd1b37
--- /dev/null
@@ -0,0 +1,57 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 03:02:43 -0800
+Subject: [notmuch] New to the list
+In-Reply-To: <1258498485-sup-142@elly>
+References: <1258498485-sup-142@elly>
+Message-ID: <87bpj0qeng.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 23:57:18 +0100, Israel Herraiz <isra at herraiz.org> wrote:
+> I have subscribed to the list. As suggested by the welcome message, I
+> am introducing myself. My name is Israel Herraiz, and I have done a
+> couple of contributions to Sup, the probably well-known here e-mail
+> client.
+
+Welcome, Israel!
+
+I'm glad people read that little bit of text in the welcome email and
+are introducing themselves. I like to think of our new notmuch community
+as a very personable place.
+
+> "Not much" sounds interesting, and I wonder whether it could be
+> integrated with the views of Sup (inbox, threads, etc). So I have
+> subscribed to the list to keep an eye on what's going on here.
+> 
+> I have just heard of "Not much". I have not even tried to download the
+> code yet.
+
+Yes, take a look. If you're already an emacs user, then you'll find the
+interface of notmuch very comfortable, (looks a lot like sup, but lives
+inside of emacs). Even outside of emacs, the command line interface of
+notmuch gives view *fairly* similar to those of sup:
+
+    notmuch search tag:inbox           # Very much like sup's inbox
+
+    notmuch show thread:some-thread-id # A lot like sup's thread -view
+
+The command-line output right now isn't nearly as neat as sup's, (it
+doesn't elide comments--it doesn't do the indenting of threads, etc.),
+even though the command-line interface has all the information it needs
+to do that. The reason for that is to let the emacs code own most of the
+formatting, (so that it can be more flexible--such as making hidden
+things visible, changing column widths, etc.).
+
+But one thing I wonder is if there would be situations where it would
+make sense to get the cleaner output directly out of the command-line
+tool.
+
+For example, for someone who isn't an emacs user, the command-line
+interface might be their only introduction to what the "notmuch
+experience" is like. So maybe "notmuch show" should give nice clean
+output by default and then the emacs code could call "notmuch show
+--format=emacs-friendly" or something to get the current output.
+
+That's an idea anyway.
+
+-Carl
+
diff --git a/test/corpora/default/cur/47:2, b/test/corpora/default/cur/47:2,
new file mode 100644 (file)
index 0000000..9de5532
--- /dev/null
@@ -0,0 +1,84 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 03:15:31 -0800
+Subject: [notmuch] Introducing myself
+In-Reply-To: <20091118002059.067214ed@hikari>
+References: <20091118002059.067214ed@hikari>
+Message-ID: <87aaykqe24.fsf@yoom.home.cworth.org>
+
+On Wed, 18 Nov 2009 00:20:59 +0100, Adrian Perez de Castro <aperez at igalia.com> wrote:
+> I have just heard about Not Much today in some random Linux-related news
+> site (LWN?), my name is Adrian Perez and I work as systems administrator
+
+Welcome to notmuch, Adrian! We're glad to have you here.
+
+> by default on most distribution. I got to have some mailboxes indexed and
+> basic searching working a couple of months ago. Lately I have been very
+> busy and had no time for coding, and them... boom! Not Much appears -- and
+> it is almost exactly what I was trying to do, but faster. I have been
+> playing a bit with Not Much today, and I think it has potential.
+
+It's funny, because I had the exact same experience with sup a couple of
+months ago. I had been frustrated for years with email programs, and had
+been thinking about how I'd like things to work n the back of my mind
+for a long time, (but never *quite* getting to the point where I would
+commit to writing an email system myself).
+
+And then... boom! I found sup and was instantly hooked. It had so much
+of what I had imagined, (and much of what I hadn't yet imagined) that I
+was quite delighted.
+
+It was really quite by accident that I ended up inventing a different
+system. I had started out just trying to speedup index creation for sup.
+If I hadn't run into the problem that it was very difficult[*] to create a
+sup-compatible index from C code, I might have stopped there.
+
+So I'd written a bunch of functional code, only to find myself stuck at
+the very last step, (hooking it up to the existing sup interface). Then
+Keith suggested emacs and it all seemed pretty easy since I'd already
+done all the Xapian work. So it's funny, I was only willing to commit to
+this project because I wasn't consciously aware I was working on it.
+Otherwise it would have seemed to overwhelming to start. :-)
+
+Anyway, that's a lot of off-topic rambling off of your introduction. But
+I'm glad that notmuch can now give that same "boom!" to others, and I'm
+glad you see potential in it.
+
+> Also, I would like to share one idea I had in mind, that you might find
+> interesting: One thing I have found very annoying is having to re-tag my
+> mail when the indexes get b0rked (it happened a couple of times to me while
+> using Sup), so I was planning to mails as read/unread and adding the tags
+> not just to the index, but to the mail text itself, e.g. by adding a
+> "X-Tags" header field or by reusing the "Keywords" one. This way, the index
+> could be totally recreated by re-reading the mail directories, and this
+> would also allow to a tools like OfflineIMAP [1] to get the mails into a
+> local maildir, tagging and indexing the mails with the e-mail reader and
+> then syncing back the messages with the "X-Tags" header to the IMAP server.
+> This would allow to use the mail reader from a different computer and still
+> have everything tagged finely.
+
+It is an interesting idea. But there's also something really comforting
+about the email indexed never modifying the mail files. If you're
+reading the notmuch commit logs closely you'll see that I'm not actually
+careful enough to be trusted with your mail (but I try). So I like that
+I don't even have to trust myself---the worst that happens is that I
+have to recreate my index.
+
+And as Keith mentioned, we've got the "notmuch dump; notmuch restore"
+idea working exactly as it did in sup. (Though I am thinking of also
+adding thread IDs to that now---more on that later.)
+
+The big annoyance I had with sup index creation, (I ended up having to
+do it more than once too), was that it takes *forever*. Right now,
+notmuch is a little bit faster, but not a lot faster. And I've got some
+ideas to fix that. It would be really nice if index creation were pain
+free. (And maybe it is for some user with small amounts of mail---oh, to
+have only 40000 messages to have to index!).
+
+-Carl
+
+[*] The problem here is that sup puts serialized ruby data structures
+into the data field of its Xapian documents. So being compatible with
+sup means being able to recreate serialized data structures for a
+particular version of ruby.
+
diff --git a/test/corpora/default/cur/48:2, b/test/corpora/default/cur/48:2,
new file mode 100644 (file)
index 0000000..419e21d
--- /dev/null
@@ -0,0 +1,17 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 03:22:32 -0800
+Subject: [notmuch] [PATCH] Typsos
+In-Reply-To: <1258500222-32066-1-git-send-email-ingmar@exherbo.org>
+References: <1258500222-32066-1-git-send-email-ingmar@exherbo.org>
+Message-ID: <878we4qdqf.fsf@yoom.home.cworth.org>
+
+On Wed, 18 Nov 2009 00:23:42 +0100, Ingmar Vanhassel <ingmar at exherbo.org> wrote:
+>  17 files changed, 30 insertions(+), 30 deletions(-)
+
+Yikes. That's a lot of typos.
+
+Thanks Ingmar, for cleaning up after my sloppy keyboarding. Pushed.
+
+-Carl
+
diff --git a/test/corpora/default/cur/49:2, b/test/corpora/default/cur/49:2,
new file mode 100644 (file)
index 0000000..b244f8c
--- /dev/null
@@ -0,0 +1,33 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 03:31:23 -0800
+Subject: [notmuch] [PATCH] Error out if no query is supplied to search
+ instead of going into an infinite loop
+In-Reply-To: <cf0c4d610911171623q3e27a0adx802e47039b57604b@mail.gmail.com>
+References: <cf0c4d610911171623q3e27a0adx802e47039b57604b@mail.gmail.com>
+Message-ID: <877htoqdbo.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 16:23:53 -0800, Alex Botero-Lowry <alex.boterolowry at gmail.com> wrote:
+> In this case error out when no query is supplied. There seems to be an
+> infinite-loop casued by i think notmuch_query_search_threads having
+> an exception:
+> A Xapian exception occurred: Syntax: <expression> AND <expression>
+> A Xapian exception occurred: Syntax: <expression> AND <expression>
+> A Xapian exception occurred: Syntax: <expression> AND <expression>
+> 
+> I'll look into that bug specifically a bit later.
+> 
+> It might be better to do a usage instead of just throwing an error here?
+
+Definitely.
+
+Priit Laes reported the same thing in IRC and I've just committed a
+patch to give a nice error message:
+
+$ ./notmuch search
+Error: notmuch search requires at least one search term.
+
+Thanks for the report!
+
+-Carl
+
diff --git a/test/corpora/default/cur/50:2, b/test/corpora/default/cur/50:2,
new file mode 100644 (file)
index 0000000..44e8be5
--- /dev/null
@@ -0,0 +1,39 @@
+From: "Chris Wilson" <chris@chris-wilson.co.uk>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 11:34:54 +0000
+Subject: [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once
+Message-ID: <1258544095-16616-1-git-send-email-chris@chris-wilson.co.uk>
+
+Currently the same `pkg-config ...` is executed for every target, so
+just store the results in a variable.
+---
+ Makefile |    9 +++++----
+ 1 files changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/Makefile b/Makefile
+index 96aaa73..023b2ec 100644
+--- a/Makefile
++++ b/Makefile
+@@ -4,15 +4,16 @@ CFLAGS=-O2
+ # Additional flags that we will append to whatever the user set.
+ # These aren't intended for the user to manipulate.
+-extra_cflags = `pkg-config --cflags glib-2.0 gmime-2.4 talloc`
+-extra_cxxflags = `xapian-config --cxxflags`
++extra_cflags := $(shell pkg-config --cflags glib-2.0 gmime-2.4 talloc)
++extra_cxxflags := $(shell xapian-config --cxxflags)
+ # Now smash together user's values with our extra values
+ override CFLAGS += $(WARN_FLAGS) $(extra_cflags)
+ override CXXFLAGS += $(WARN_FLAGS) $(extra_cflags) $(extra_cxxflags)
+-override LDFLAGS += `pkg-config --libs glib-2.0 gmime-2.4 talloc` \
+-                      `xapian-config --libs`
++override LDFLAGS += \
++      $(shell pkg-config --libs glib-2.0 gmime-2.4 talloc) \
++      $(shell xapian-config --libs)
+ # Include our local Makefile.local first so that its first target is default
+ include Makefile.local
+-- 
+1.6.5.2
diff --git a/test/corpora/default/cur/51:2, b/test/corpora/default/cur/51:2,
new file mode 100644 (file)
index 0000000..f522f69
--- /dev/null
@@ -0,0 +1,12 @@
+From: "Aron Griffis" <agriffis@n01se.net>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 18:21:38 -0500
+Subject: [notmuch] archive
+Message-ID: <20091117232137.GA7669@griffis1.net>
+
+Just subscribed, I'd like to catch up on the previous postings,
+but the archive link seems to be bogus?
+
+Thanks,
+Aron
+
diff --git a/test/corpora/default/cur/52:2, b/test/corpora/default/cur/52:2,
new file mode 100644 (file)
index 0000000..6028340
--- /dev/null
@@ -0,0 +1,39 @@
+Message-ID: <4EFC743A.3060609@april.org>
+Date: Thu, 29 Dec 2010 15:07:54 +0100
+From: "=?ISO-8859-1?Q?Fran=E7ois_Boulogne?=" <boulogne.f@gmail.com>
+User-Agent: Mozilla/5.0 (X11; Linux i686;
+ rv:9.0) Gecko/20111224 Thunderbird/9.0.1
+MIME-Version: 1.0
+To: Allan McRae <allan@archlinux.org>, 
+ "Discussion about the Arch User Repository (AUR)" <aur-general@archlinux.org>
+References: <4EFC3931.6030007@april.org> <4EFC3D62.4030202@archlinux.org>
+In-Reply-To: <4EFC3D62.4030202@archlinux.org>
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: 8bit
+Subject: Re: [aur-general] Guidelines: cp, mkdir vs install
+
+Le 29/12/2011 11:13, Allan McRae a écrit :
+> On 29/12/11 19:56, François Boulogne wrote:
+>> Hi,
+>>
+>> Looking to improve the quality of my packages, I read again the guidelines.
+>> https://wiki.archlinux.org/index.php/Arch_Packaging_Standards
+>>
+>> However, it don't see anything about the install command like
+>> install -d $pkgdir/usr/{bin,share/man/man1,share/locale}
+>>
+>> Some contributors on AUR use cp or mkdir to install files/dir (when no
+>> makefile is provided) and others use install command.
+>>
+>> What's the opinion of TU on this point?
+>>
+> 
+> Use install with -m specifying the correct permissions
+> 
+
+Thank you Allan
+
+
+-- 
+François Boulogne.
+https://www.sciunto.org
diff --git a/test/corpora/default/cur/53:2, b/test/corpora/default/cur/53:2,
new file mode 100644 (file)
index 0000000..7a1e2e5
--- /dev/null
@@ -0,0 +1,20 @@
+From: Olivier Berger <olivier.berger@it-sudparis.eu>
+To: olivier.berger@it-sudparis.eu
+Subject: Essai =?iso-8859-1?Q?accentu=E9?=
+User-Agent: Notmuch/0.10.1 (http://notmuchmail.org) Emacs/23.3.1 (i486-pc-linux-gnu)
+X-Draft-From: ("nnimap+localdovecot:INBOX" 44228)
+Date: Fri, 16 Dec 2010 16:49:59 +0100
+Message-ID: <877h1wv7mg.fsf@inf-8657.int-evry.fr>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+Du texte accentu=E9 pour =E7a ...
+
+=E0 la bonne heure !
+--=20
+Olivier BERGER=20
+http://www-public.it-sudparis.eu/~berger_o/ - OpenPGP-Id: 2048R/5819D7E8
+Ingenieur Recherche - Dept INF
+Institut TELECOM, SudParis (http://www.it-sudparis.eu/), Evry (France)
+
diff --git a/test/corpora/default/foo/06:2, b/test/corpora/default/foo/06:2,
new file mode 100644 (file)
index 0000000..3baad49
--- /dev/null
@@ -0,0 +1,36 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 12:19:24 -0800
+Subject: [notmuch] preliminary FreeBSD support
+In-Reply-To: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+References: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+Message-ID: <87lji4lx9v.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 11:36:14 -0800, Alex Botero-Lowry <alex.boterolowry at gmail.com> wrote:
+> I saw the announcement this morning, and was very excited, as I had been
+> hoping sup would be turned into a library,
+> since I like the concept more than the UI (I'd rather an emacs interface).
+
+Hi Alex,
+
+That's great! It's good to hear that there are like-minded people out
+there. I hope that Notmuch will be useful for you.
+
+> I did a preliminary compile which worked out fine, but
+> sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on
+> FreeBSD, so notmuch_config_open segfaulted.
+> 
+> Attached is a patch that supplies a default buffer size of 64 in cases where
+> -1 is returned.
+
+Thanks for the patch. As we discussed in IRC[*], we should probably
+do the correct thing and check for ERANGE and loop as necessary (even if
+sysconf returns a positive value). Example code here:
+
+http://www.opengroup.org/austin/docs/austin_328.txt
+
+-Carl
+
+[*] #notmuch on irc.freenode.net for those who didn't just guess that
+already, (and I'll add that to the website soon).
+
diff --git a/test/corpora/default/foo/baz/11:2, b/test/corpora/default/foo/baz/11:2,
new file mode 100644 (file)
index 0000000..c0701de
--- /dev/null
@@ -0,0 +1,27 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 13:15:25 -0800
+Subject: [notmuch] [PATCH 1/2] Close message file after parsing message
+ headers
+In-Reply-To: <87lji5cbwo.fsf@yoom.home.cworth.org>
+References: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+       <87lji5cbwo.fsf@yoom.home.cworth.org>
+Message-ID: <yunbpj0etua.fsf@aiko.keithp.com>
+
+On Tue, 17 Nov 2009 09:13:27 -0800, Carl Worth <cworth at cworth.org> wrote:
+
+> I didn't apply Keith's fix yet, because I think I'd rather just fix the
+> indexer to store the In-Reply-To header in a separate term prefix from
+> the term used for the References header[*]. That will then let us lookup
+> the in-reply-to value later for thread constructions without having to
+> open the original email file at all.
+
+Threading the message also involves displaying the from and to contents,
+which requires opening the message file. The alternative to the fix I
+provided is to just parse all of the message headers when first opening
+the message; it could then be immediately closed and the hash referred
+to for all header data. Given the choice, just having the caller say
+when it has finished with a message is probably a reasonable option...
+
+-keith
+
diff --git a/test/corpora/default/foo/baz/12:2, b/test/corpora/default/foo/baz/12:2,
new file mode 100644 (file)
index 0000000..fbc604c
--- /dev/null
@@ -0,0 +1,27 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 13:24:13 -0800
+Subject: [notmuch] Working with Maildir storage?
+In-Reply-To: <20091117203301.GV3165@dottiness.seas.harvard.edu>
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+       <87iqd9rn3l.fsf@vertex.dottedmag>
+       <20091117203301.GV3165@dottiness.seas.harvard.edu>
+Message-ID: <yunaayketfm.fsf@aiko.keithp.com>
+
+On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at seas.harvard.edu> wrote:
+> > See the patch just posted here.
+
+I've also pushed a slightly more complicated (and complete) fix to my
+private notmuch repository
+
+git://keithp.com/git/notmuch
+
+> Is the list archived anywhere?
+
+Oops. Looks like Carl's mail server is broken. He's traveling to
+Barcelona today and so it won't get fixed for a while.
+
+Thanks to everyone for trying out notmuch!
+
+-keith
+
diff --git a/test/corpora/default/foo/baz/cur/13:2, b/test/corpora/default/foo/baz/cur/13:2,
new file mode 100644 (file)
index 0000000..03cb374
--- /dev/null
@@ -0,0 +1,178 @@
+From: "Keith Packard" <keithp@keithp.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 13:32:45 -0800
+Subject: [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove
+       inbox (and unread) tags
+Message-ID: <1258493565-13508-1-git-send-email-keithp@keithp.com>
+
+When closing a thread view, mark the thread as archived by removing
+the "inbox" tag, and for the 'x' variant, the "unread" tag as well,
+then kill the buffer and update the search window view as well.
+
+This makes 'x' much the same as 'a', but instead of taking you to the
+next message, it takes you back to the search window instead.
+
+Signed-off-by: Keith Packard <keithp at keithp.com>
+---
+ notmuch.el |   86 ++++++++++++++++++++++++++++++++++++++++++++++-------------
+ 1 files changed, 67 insertions(+), 19 deletions(-)
+
+diff --git a/notmuch.el b/notmuch.el
+index 638d49d..7b0d72c 100644
+--- a/notmuch.el
++++ b/notmuch.el
+@@ -31,8 +31,8 @@
+     ; Will be much preferable to switch to direct manipulation for
+     ; toggling visibility of these components. Probably using
+     ; overlays-at to query and manipulate the current overlay.
+-    (define-key map "a" 'notmuch-show-archive-thread)
+-    (define-key map "A" 'notmuch-show-mark-read-then-archive-thread)
++    (define-key map "a" 'notmuch-show-mark-read-archive-thread-next-thread)
++    (define-key map "A" 'notmuch-show-archive-thread-next-thread)
+     (define-key map "b" 'notmuch-show-toggle-body-read-visible)
+     (define-key map "c" 'notmuch-show-toggle-citations-visible)
+     (define-key map "h" 'notmuch-show-toggle-headers-visible)
+@@ -47,7 +47,8 @@
+     (define-key map "s" 'notmuch-show-toggle-signatures-visible)
+     (define-key map "v" 'notmuch-show-view-all-mime-parts)
+     (define-key map "w" 'notmuch-show-view-raw-message)
+-    (define-key map "x" 'kill-this-buffer)
++    (define-key map "x" 'notmuch-show-mark-read-archive-thread-kill-buffer)
++    (define-key map "X" 'notmuch-show-archive-thread-kill-buffer)
+     (define-key map "+" 'notmuch-show-add-tag)
+     (define-key map "-" 'notmuch-show-remove-tag)
+     (define-key map (kbd "DEL") 'notmuch-show-rewind)
+@@ -183,7 +184,33 @@ Unlike builtin `next-line' this version accepts no arguments."
+                        (cons (notmuch-show-get-message-id) nil)))
+         (notmuch-show-set-tags (sort (set-difference tags toremove :test 'string=) 'string<))))))
+-(defun notmuch-show-archive-thread-maybe-mark-read (markread)
++(defun notmuch-show-next-thread (markread)
++  (let ((parent-buffer notmuch-show-parent-buffer))
++    (kill-this-buffer)
++    (if parent-buffer
++      (progn
++        (switch-to-buffer parent-buffer)
++        (forward-line)
++        (notmuch-search-show-thread)))))
++  
++(defun notmuch-delete-tags (to-remove from)
++  (if to-remove
++      (delete (car to-remove) (notmuch-delete-tags (cdr to-remove) from))
++    from))
++
++(defun notmuch-kill-message-buffer (markread)
++  (let ((parent-buffer notmuch-show-parent-buffer))
++    (kill-this-buffer)
++    (if parent-buffer
++      (progn
++        (switch-to-buffer parent-buffer)
++        (let ((tags (notmuch-search-get-tags)))
++          (setq tags (delete "inbox" tags))
++          (if markread (setq tags (delete "unread" tags)))
++          (notmuch-search-set-tags tags))
++        (forward-line)))))
++
++(defun notmuch-show-archive-thread-maybe-mark-read (markread shownext)
+   (save-excursion
+     (goto-char (point-min))
+     (while (not (eobp))
+@@ -194,15 +221,9 @@ Unlike builtin `next-line' this version accepts no arguments."
+         (forward-char))
+       (if (not (re-search-forward notmuch-show-message-begin-regexp nil t))
+         (goto-char (point-max)))))
+-  (let ((parent-buffer notmuch-show-parent-buffer))
+-    (kill-this-buffer)
+-    (if parent-buffer
+-      (progn
+-        (switch-to-buffer parent-buffer)
+-        (forward-line)
+-        (notmuch-search-show-thread)))))
++  (if shownext (notmuch-show-next-thread markread) (notmuch-kill-message-buffer markread)))
+-(defun notmuch-show-mark-read-then-archive-thread ()
++(defun notmuch-show-mark-read-archive-thread-next-thread ()
+   "Remove \"unread\" tag from each message, then archive and show next thread.
+ Archive each message currrently shown by removing the \"unread\"
+@@ -215,9 +236,22 @@ being delivered to the same thread. It does not archive the
+ entire thread, but only the messages shown in the current
+ buffer."
+   (interactive)
+-  (notmuch-show-archive-thread-maybe-mark-read t))
++  (notmuch-show-archive-thread-maybe-mark-read t t))
++
++(defun notmuch-show-mark-read-archive-thread-kill-buffer ()
++  "Remove \"unread\" tag from each message, then archive and kill the buffer.
++
++Archive each message currrently shown by removing the \"unread\"
++and \"inbox\" tag from each. Then kill this buffer.
++
++Note: This command is safe from any race condition of new messages
++being delivered to the same thread. It does not archive the
++entire thread, but only the messages shown in the current
++buffer."
++  (interactive)
++  (notmuch-show-archive-thread-maybe-mark-read t nil))
+-(defun notmuch-show-archive-thread ()
++(defun notmuch-show-archive-thread-next-thread ()
+   "Archive each message in thread, and show next thread from search.
+ Archive each message currrently shown by removing the \"inbox\"
+@@ -229,7 +263,20 @@ being delivered to the same thread. It does not archive the
+ entire thread, but only the messages shown in the current
+ buffer."
+   (interactive)
+-  (notmuch-show-archive-thread-maybe-mark-read nil))
++  (notmuch-show-archive-thread-maybe-mark-read nil t))
++
++(defun notmuch-show-archive-thread-kill-buffer ()
++  "Archive each message in thread, and kill the thread buffer.
++
++Archive each message currrently shown by removing the \"inbox\"
++tag from each. Then kill this buffer.
++
++Note: This command is safe from any race condition of new messages
++being delivered to the same thread. It does not archive the
++entire thread, but only the messages shown in the current
++buffer."
++  (interactive)
++  (notmuch-show-archive-thread-maybe-mark-read nil t))
+ (defun notmuch-show-view-raw-message ()
+   "View the raw email of the current message."
+@@ -297,7 +344,7 @@ by searching backward)."
+       (not (re-search-forward notmuch-show-message-begin-regexp nil t)))))
+ (defun notmuch-show-message-unread-p ()
+-  "Preficate testing whether current message is unread."
++  "Predicate testing whether current message is unread."
+   (member "unread" (notmuch-show-get-tags)))
+ (defun notmuch-show-next-message ()
+@@ -434,7 +481,7 @@ which this thread was originally shown."
+       (let ((last (notmuch-show-last-message-p)))
+       (notmuch-show-mark-read-then-next-open-message)
+       (if last
+-          (notmuch-show-archive-thread))))))
++          (notmuch-show-archive-thread-next-thread))))))
+ (defun notmuch-show-markup-citations-region (beg end depth)
+   (goto-char beg)
+@@ -618,8 +665,9 @@ messages. Each time you navigate away from a message with
+ You can add or remove tags from the current message with '+' and
+ '-'.  You can also archive all messages in the current
+-view, (remove the \"inbox\" tag from each), with
+-`notmuch-show-archive-thread' (bound to 'a' by default).
++view, (remove the \"inbox\" tag from each), with either
++`notmuch-show-archive-thread-next-thread' (bound to 'a' by default) or
++`notmuch-show-archive-thread-kill-buffer' (bound to 'x' by default).
+ \\{notmuch-show-mode-map}"
+   (interactive)
+-- 
+1.6.5.2
+
+
diff --git a/test/corpora/default/foo/baz/cur/14:2, b/test/corpora/default/foo/baz/cur/14:2,
new file mode 100644 (file)
index 0000000..d3fe78d
--- /dev/null
@@ -0,0 +1,39 @@
+From: "Jan Janak" <jan@ryngle.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 23:18:47 +0100
+Subject: [notmuch] [PATCH] Older versions of install do not support -C.
+Message-ID: <1258496327-12086-1-git-send-email-jan@ryngle.com>
+
+Do not use -C cmdline option of install, older versions, commonly found in
+distributions like Debian, do not seem to support it. Running make install
+on such systems (tested on Debian Lenny) fails.
+
+Signed-off-by: Jan Janak <jan at ryngle.com>
+---
+ Makefile.local |    8 ++++----
+ 1 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/Makefile.local b/Makefile.local
+index f824bed..f51f1d1 100644
+--- a/Makefile.local
++++ b/Makefile.local
+@@ -27,11 +27,11 @@ install: all notmuch.1.gz
+       for d in $(DESTDIR)$(prefix)/bin/ $(DESTDIR)$(prefix)/share/man/man1 \
+               $(DESTDIR)/etc/bash_completion.d/ ; \
+       do \
+-              install -C -d $$d ; \
++              install -d $$d ; \
+       done ;
+-      install -C notmuch $(DESTDIR)$(prefix)/bin/
+-      install -C -m0644 notmuch.1.gz $(DESTDIR)$(prefix)/share/man/man1/
+-      install -C notmuch-completion.bash \
++      install notmuch $(DESTDIR)$(prefix)/bin/
++      install -m0644 notmuch.1.gz $(DESTDIR)$(prefix)/share/man/man1/
++      install notmuch-completion.bash \
+               $(DESTDIR)/etc/bash_completion.d/notmuch
+ SRCS  := $(SRCS) $(notmuch_client_srcs)
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/foo/baz/new/15:2, b/test/corpora/default/foo/baz/new/15:2,
new file mode 100644 (file)
index 0000000..6824d5e
--- /dev/null
@@ -0,0 +1,22 @@
+From: "Jan Janak" <jan@ryngle.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 23:35:30 +0100
+Subject: [notmuch] What a great idea!
+Message-ID: <f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com>
+
+Hello,
+
+First of all, notmuch is a wonderful idea, both the cmdline tool and
+the emacs interface! Thanks a lot for writing it, I was really excited
+when I read the announcement today.
+
+Have you considered sending an announcement to the org-mode mailing list?
+http://org-mode.org
+
+Various ways of searching/referencing emails from emacs were discussed
+there several times and none of them were as elegant as notmuch (not
+even close). Maybe notmuch would attract some of the developers
+there..
+
+   -- Jan
+
diff --git a/test/corpora/default/foo/baz/new/16:2, b/test/corpora/default/foo/baz/new/16:2,
new file mode 100644 (file)
index 0000000..f531eb9
--- /dev/null
@@ -0,0 +1,27 @@
+From: "Jan Janak" <jan@ryngle.com>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 23:38:47 +0100
+Subject: [notmuch] What a great idea!
+In-Reply-To: <f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com>
+References: <f35dbb950911171435ieecd458o853c873e35f4be95@mail.gmail.com>
+Message-ID: <f35dbb950911171438k5df6eb56k77b6c0944e2e79ae@mail.gmail.com>
+
+On Tue, Nov 17, 2009 at 11:35 PM, Jan Janak <jan at ryngle.com> wrote:
+> Hello,
+>
+> First of all, notmuch is a wonderful idea, both the cmdline tool and
+> the emacs interface! Thanks a lot for writing it, I was really excited
+> when I read the announcement today.
+>
+> Have you considered sending an announcement to the org-mode mailing list?
+> http://org-mode.org
+
+Sorry, wrong URL, the correct one is: http://orgmode.org
+
+> Various ways of searching/referencing emails from emacs were discussed
+> there several times and none of them were as elegant as notmuch (not
+> even close). Maybe notmuch would attract some of the developers
+> there..
+
+  -- Jan
+
diff --git a/test/corpora/default/foo/cur/07:2, b/test/corpora/default/foo/cur/07:2,
new file mode 100644 (file)
index 0000000..7b1e2bb
--- /dev/null
@@ -0,0 +1,57 @@
+From: "Carl Worth" <cworth@cworth.org>
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 09:13:27 -0800
+Subject: [notmuch] [PATCH 1/2] Close message file after parsing message
+ headers
+In-Reply-To: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+References: <1258471718-6781-1-git-send-email-dottedmag@dottedmag.net>
+Message-ID: <87lji5cbwo.fsf@yoom.home.cworth.org>
+
+On Tue, 17 Nov 2009 21:28:37 +0600, Mikhail Gusarov <dottedmag at dottedmag.net> wrote:
+> Keeping unused files open helps to see "Too many open files" often.
+> 
+> Signed-off-by: Mikhail Gusarov <dottedmag at dottedmag.net>
+...
+On Tue, 17 Nov 2009 21:28:38 +0600, Mikhail Gusarov <dottedmag at dottedmag.net> wrote:
+> 
+> Signed-off-by: Mikhail Gusarov <dottedmag at dottedmag.net>
+> ---
+>  lib/message.cc |    2 ++
+>  1 files changed, 2 insertions(+), 0 deletions(-)
+
+Hi Mikhail,
+
+Welcome to notmuch, and thanks for these patches! I've pushed both of
+them out now.
+
+Keith ran into the same problem of "too many open files" and wrote a
+more complex fix, (which included what you did here). His code can be
+seen at:
+
+       git://keithp.com/git/notmuch
+
+I didn't apply Keith's fix yet, because I think I'd rather just fix the
+indexer to store the In-Reply-To header in a separate term prefix from
+the term used for the References header[*]. That will then let us lookup
+the in-reply-to value later for thread constructions without having to
+open the original email file at all.
+
+-Carl
+
+[*] Yes, this is my first post to our new mailing list and I'm already
+spouting off about "terms" and "prefixes" without any definitions. I
+apologize for that. I hope that people will ask questions freely here on
+the list whenever anything is not clear, and I'll be glad to explain
+things as needed. (Then when can shove answers into a HACKING document.)
+
+PS. This reply is a great example of a feature that notmuch *almost*
+supports already---repling to multiple messages at once. The "notmuch
+reply" command line does everything necessary to make this work, but we
+haven't yet hooked up any keybindings for this in the emacs client yet.
+Obviously, 'r' from the search view could reply to the entire thread.
+But when viewing a thread, anyone have a good keybinding suggestion?
+(There's obviously 'R' as opposed to 'r', but I think we'll probably
+want to distinguish "reply to sender" from "reply to all" before trying
+to distinguish "reply to message" from "reply to thread" (which I
+imagine is more rare of an operation).
+
diff --git a/test/corpora/default/foo/cur/08:2, b/test/corpora/default/foo/cur/08:2,
new file mode 100644 (file)
index 0000000..baf34d1
--- /dev/null
@@ -0,0 +1,87 @@
+Date: Tue, 17 Nov 2009 15:33:01 -0500
+From: Lars Kellogg-Stedman <lars@seas.harvard.edu>
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Message-ID: <20091117203301.GV3165@dottiness.seas.harvard.edu>
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+       <87iqd9rn3l.fsf@vertex.dottedmag>
+MIME-Version: 1.0
+In-Reply-To: <87iqd9rn3l.fsf@vertex.dottedmag>
+User-Agent: Mutt/1.5.19 (2009-01-05)
+Cc: notmuch@notmuchmail.org
+Subject: Re: [notmuch] Working with Maildir storage?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============0063752545=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+
+--===============0063752545==
+Content-Type: multipart/signed; micalg=pgp-sha256;
+       protocol="application/pgp-signature"; boundary="GGxZz/e2pmGePzrA"
+Content-Disposition: inline
+
+
+--GGxZz/e2pmGePzrA
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+> See the patch just posted here.
+
+Is the list archived anywhere?  The obvious archives
+(http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+think I subscribed too late to get the patch (I only just saw the
+discussion about it).
+
+It doesn't look like the patch is in git yet.
+
+-- Lars
+
+--=20
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+
+--GGxZz/e2pmGePzrA
+Content-Type: application/pgp-signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQEcBAEBCAAGBQJLAwh9AAoJENdGlQYxQazYHJMIAI+XTPOyBTZIxEGTdgVKd2fR
+k27ucKs6lXozfMIIGchNUDXQho+KmiuTfX1XFJeBkqOlhrd9zlGjBGoBM0YBq/Gs
+aStPdonREzsHORjmyQCCpjg4AcqCRTXFbDXzAeXlxMPOrZ3P0XNPzTEM1mVksbmg
+mBBDLdHncy4sSCfFgXwRGGgLv9z5Acqm8xGYr68c9PIXY939ozIKV9LVUhxiNz9g
+We2a9rLDhfwxUqDlGdiNwZZimiKvD/fsYSrBZMDb5HgIYkeNZ4SD8Xu+OgB550wN
+OFfwGi3o8WFK2AyDe5QJDh9Ub+euPNlVzePoGpkltZEHuCcLFJqCHv5XYpbxcjA=
+=GO2Q
+-----END PGP SIGNATURE-----
+
+--GGxZz/e2pmGePzrA--
+
+--===============0063752545==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============0063752545==--
+
diff --git a/test/corpora/default/foo/new/03:2, b/test/corpora/default/foo/new/03:2,
new file mode 100644 (file)
index 0000000..c154ac5
--- /dev/null
@@ -0,0 +1,93 @@
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+From: Lars Kellogg-Stedman <lars@seas.harvard.edu>
+To: notmuch@notmuchmail.org
+Message-ID: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+MIME-Version: 1.0
+User-Agent: Mutt/1.5.19 (2009-01-05)
+Subject: [notmuch] Working with Maildir storage?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============1685355122=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+
+--===============1685355122==
+Content-Type: multipart/signed; micalg=pgp-sha256;
+       protocol="application/pgp-signature"; boundary="5Dr6Wqe9hdyl7LAI"
+Content-Disposition: inline
+
+
+--5Dr6Wqe9hdyl7LAI
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=
+=3D3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+--=20
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+
+
+--5Dr6Wqe9hdyl7LAI
+Content-Type: application/pgp-signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQEcBAEBCAAGBQJLAvLmAAoJENdGlQYxQazYRtcH/0usClQ1Z+EoTsA+URwIK6hD
+FsZUxFxRjMuOQRn2idZ/zhhg5jJj11ZaHjqxSkDvi2ywkTKUf1vX9LLzVy5hSR9M
+E6XQUd5QWAQXo1VsTeKkukIL0YqsPjdgrT8+Yt+OS2NvhEncql23oxnL2/pHkIFq
+r0NdTmVV5Jcar7w9J6X1Mi9m229a/9jV5FImsWISkIhIWznXU5SiU6zIw8xhP4E0
+xhvVSNJnFryjVHtva870aSQduhHfeLPzpYhqbkMPvlq+bcz6Q/Q2SwxJcGLNMPHa
+os9s9FGhCvFKUhVzezHWPgXNCcNT8qK89rcUldb5Oq4jaJb8RCZCYABplfoyaFs=
+=vO4s
+-----END PGP SIGNATURE-----
+
+--5Dr6Wqe9hdyl7LAI--
+
+--===============1685355122==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============1685355122==--
+
diff --git a/test/corpora/default/foo/new/09:2, b/test/corpora/default/foo/new/09:2,
new file mode 100644 (file)
index 0000000..26b51b1
--- /dev/null
@@ -0,0 +1,33 @@
+From: "Mikhail Gusarov" <dottedmag@dottedmag.net>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:50:48 +0600
+Subject: [notmuch] Working with Maildir storage?
+In-Reply-To: <20091117203301.GV3165@dottiness.seas.harvard.edu> (Lars
+       Kellogg-Stedman's message of "Tue, 17 Nov 2009 15:33:01 -0500")
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+       <87iqd9rn3l.fsf@vertex.dottedmag>
+       <20091117203301.GV3165@dottiness.seas.harvard.edu>
+Message-ID: <87fx8can9z.fsf@vertex.dottedmag>
+
+
+Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did gyre and gimble:
+
+ LK> Is the list archived anywhere?  The obvious archives
+ LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+ LK> think I subscribed too late to get the patch (I only just saw the
+ LK> discussion about it).
+
+ LK> It doesn't look like the patch is in git yet.
+
+Just has been pushed
+
+-- 
+  http://fossarchy.blogspot.com/
+-------------- next part --------------
+A non-text attachment was scrubbed...
+Name: not available
+Type: application/pgp-signature
+Size: 834 bytes
+Desc: not available
+URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+
diff --git a/test/corpora/default/foo/new/10:2, b/test/corpora/default/foo/new/10:2,
new file mode 100644 (file)
index 0000000..4211d73
--- /dev/null
@@ -0,0 +1,54 @@
+From: "Mikhail Gusarov" <dottedmag@dottedmag.net>
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:51:18 +0600
+Subject: [notmuch] [PATCH] Handle rename of message file
+Message-ID: <1258491078-29658-1-git-send-email-dottedmag@dottedmag.net>
+
+If message file has been renamed, just update filename in the DB.
+
+Signed-off-by: Mikhail Gusarov <dottedmag at dottedmag.net>
+---
+ lib/database.cc |   21 ++++++++++++---------
+ 1 files changed, 12 insertions(+), 9 deletions(-)
+
+diff --git a/lib/database.cc b/lib/database.cc
+index 3c8d626..c4eb8b6 100644
+--- a/lib/database.cc
++++ b/lib/database.cc
+@@ -925,20 +925,23 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
+       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           _notmuch_message_set_filename (message, filename);
+           _notmuch_message_add_term (message, "type", "mail");
++
++          ret = _notmuch_database_link_message (notmuch, message, message_file);
++          if (ret)
++              goto DONE;
++
++          date = notmuch_message_file_get_header (message_file, "date");
++          _notmuch_message_set_date (message, date);
++
++          _notmuch_message_index_file (message, filename);
++      } else if (strcmp(notmuch_message_get_filename(message), filename)) {
++          /* Message file has been moved/renamed */
++          _notmuch_message_set_filename (message, filename);
+       } else {
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+           goto DONE;
+       }
+-      ret = _notmuch_database_link_message (notmuch, message, message_file);
+-      if (ret)
+-          goto DONE;
+-
+-      date = notmuch_message_file_get_header (message_file, "date");
+-      _notmuch_message_set_date (message, date);
+-
+-      _notmuch_message_index_file (message, filename);
+-
+       _notmuch_message_sync (message);
+     } catch (const Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s.\n",
+-- 
+1.6.3.3
+
+
diff --git a/test/corpora/default/new/04:2, b/test/corpora/default/new/04:2,
new file mode 100644 (file)
index 0000000..0ce678b
--- /dev/null
@@ -0,0 +1,84 @@
+From: Mikhail Gusarov <dottedmag@dottedmag.net>
+To: notmuch@notmuchmail.org
+References: <20091117190054.GU3165@dottiness.seas.harvard.edu>
+Date: Wed, 18 Nov 2009 01:02:38 +0600
+In-Reply-To: <20091117190054.GU3165@dottiness.seas.harvard.edu> (Lars
+       Kellogg-Stedman's message of "Tue, 17 Nov 2009 14:00:54 -0500")
+Message-ID: <87iqd9rn3l.fsf@vertex.dottedmag>
+User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.1 (gnu/linux)
+MIME-Version: 1.0
+Subject: Re: [notmuch] Working with Maildir storage?
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Content-Type: multipart/mixed; boundary="===============1958295626=="
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+--===============1958295626==
+Content-Type: multipart/signed; boundary="=-=-=";
+       micalg=pgp-sha1; protocol="application/pgp-signature"
+
+--=-=-=
+Content-Transfer-Encoding: quoted-printable
+
+
+Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did g=
+yre and gimble:
+
+ LK> Resulted in 4604 lines of errors along the lines of:
+
+ LK>   Error opening
+ LK>   /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostna=
+me,U=3D3026:2,S:
+ LK>   Too many open files
+
+See the patch just posted here.
+
+=2D-=20
+  http://fossarchy.blogspot.com/
+
+--=-=-=
+Content-Type: application/pgp-signature
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iQIcBAEBAgAGBQJLAvNOAAoJEJ0g9lA+M4iIjLYQAKp0PXEgl3JMOEBisH52AsIK
+CzzfP4Fzd41K9VH/c1EdQWDYR6FCAA4IUSNICnJhITsYUb0eC5AKJiey3JP0+rmd
+s4qEFBKH2iuphv8Llltcv2Q8DyPuJBkVa3mO9XCCeABZ6v4UvnTSWRVG12csSEih
+ScgienU8sMrM9LwvvVI1ZB2flm2TzsH2hWi30jIgmtBntIKJaTgbFXB50FYFwULa
+gGL/oH3u+YpumedWzPZdCJrw2q7nMvYx8aQ29EDCNLZibAZe+6oDTa6Fv6/0ldpQ
+U+DptR0nJGbJTWa26OTSvmyeIORjAfM+TEI68n7KO9VHYPmVh6awcf0MNKYh2xWk
+eRQNBcKyQNWxeKyCCpT/rrTlpxBWahpvg+V8lkDH2W09wjRp6CUKvifK3Sz3am9m
+5ZUMpvXbwkZD6Ci6l/QytbYK50e8UpvFSu5DBaxBz59ykoypuNg2ayO5Kdi6IF5d
+T+Sw6wo8UKn9a33+vheIc0fkhZXbeSotEmDm7huazm6CgM3dcWXUpTuJvik1cSWp
+4buv98gY6IKWKoUTXODWUr+7VR4gei8du8qOsKem+QDfNX7tmaIRjhrbB24B91Wy
+td3MTJD7GjMNid0INqRY1CRMLo8YlPaq6NBZfcYtYgwa6gpJijz1/MAn8+GMrfhF
+9LI8b9jopNP+pMYBohLA
+=/ksP
+-----END PGP SIGNATURE-----
+--=-=-=--
+
+--===============1958295626==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--===============1958295626==--
+
diff --git a/test/corpora/html/attribute-text b/test/corpora/html/attribute-text
new file mode 100644 (file)
index 0000000..6dae819
--- /dev/null
@@ -0,0 +1,15 @@
+From: David Bremner <david@example.net>
+To: David Bremner <david@example.net>
+Subject: test html attachment
+Date: Tue, 17 Nov 2009 21:28:38 +0600
+Message-ID: <87d1dajhgf.fsf@example.net>
+MIME-Version: 1.0
+Content-Type: text/html
+Content-Disposition: inline; filename=test.html
+
+<html>
+  <body>
+    <input value="a>swordfish">
+  </body>
+  hunter2
+</html>
diff --git a/test/corpora/html/embedded-image b/test/corpora/html/embedded-image
new file mode 100644 (file)
index 0000000..4085153
--- /dev/null
@@ -0,0 +1,69 @@
+From: =?utf-8?b?bWFsbW9ib3Jn?= <daemon@lublin.se>
+To: =?utf-8?b?Ym9lbmRlLm1hbG1vYm9yZw==?= <daemon@lublin.se>
+Date: Tue, 19 Jul 2016 11:54:24 +0200
+X-Feed2Imap-Version: 1.2.5
+Message-Id: <boendemalmoborg-1834@eltanin.uberspace.de>
+Subject: =?utf-8?b?VGFjayBhbGxhIHRyYWZpa2FudGVyIG9jaCBmb3Rnw6RuZ2FyZSE=?=
+Content-Type: multipart/alternative; boundary="=-1468922508-176605-12427-9500-21-="
+MIME-Version: 1.0
+
+
+--=-1468922508-176605-12427-9500-21-=
+Content-Type: text/plain; charset=utf-8; format=flowed
+Content-Transfer-Encoding: 8bit
+
+<http://malmoborg.se/2016/07/tack-alla-trafikanter-och-fotgangare/>
+
+Malmö 2016-07-09
+
+I skrivande stund är vi i färd med att avetablera vår entreprenad på 
+Tigern 3, Regementsgatan 6 i Malmö. Fastigheten har genomgått ett större 
+dräneringsarbete som i sin tur har inneburit vissa 
+trafikbegränsningar på Regementsgatan samt Davidshallsgatan under några 
+veckors tid. Fastighetsägaren är mycket nöjd med vår arbetsinsats och vi 
+kan glatt meddela att båda vägfilerna kommer att öppnas inom kort. Nu 
+kommer den vackra fastigheten att klara sig torrskodd under många år 
+framöver [A]
+
+
+[A] http://malmoborg.se/wp-includes/images/smilies/icon_smile.gif
+-- 
+Feed: Förvaltnings AB Malmöborg
+<http://malmoborg.se>
+Item: Tack alla trafikanter och fotgängare!
+<http://malmoborg.se/2016/07/tack-alla-trafikanter-och-fotgangare/>
+Date: 2016-07-19 11:54:24 +0200
+Author: malmoborg
+Filed under: Nyheter
+
+--=-1468922508-176605-12427-9500-21-=
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+<table border="1" width="100%" cellpadding="0" cellspacing="0" borderspacing="0"><tr><td>
+<table width="100%" bgcolor="#EDEDED" cellpadding="4" cellspacing="2">
+<tr><td align="right"><b>Feed:</b></td>
+<td width="100%"><a href="http://malmoborg.se">
+<b>Förvaltnings AB Malmöborg</b>
+</a>
+</td></tr><tr><td align="right"><b>Item:</b></td>
+<td width="100%"><a href="http://malmoborg.se/2016/07/tack-alla-trafikanter-och-fotgangare/"><b>Tack alla trafikanter och fotgängare!</b>
+</a>
+</td></tr></table></td></tr></table>
+
+<p>Malmö 2016-07-09</p>
+<p>I skrivande stund är vi i färd med att avetablera vår entreprenad på Tigern 3, Regementsgatan 6 i Malmö. Fastigheten har genomgått ett större dräneringsarbete som i sin tur har inneburit vissa trafikbegränsningar på Regementsgatan samt Davidshallsgatan under några veckors tid. Fastighetsägaren är mycket nöjd med vår arbetsinsats och vi kan glatt meddela att båda vägfilerna kommer att öppnas inom kort. Nu kommer den vackra fastigheten att klara sig torrskodd under många år framöver <img src="data:image/gif;base64,R0lGODlhDwAPALMOAP/qAEVFRQAAAP/OAP/JAP+0AP6dAP/+k//9E///////
+xzMzM///6//lAAAAAAAAACH5BAEAAA4ALAAAAAAPAA8AAARb0EkZap3YVabO
+GRcWcAgCnIMRTEEnCCfwpqt2mHEOagoOnz+CKnADxoKFyiHHBBCSAdOiCVg8
+KwPZa7sVrgJZQWI8FhB2msGgwTXTWGqCXP4WBQr4wjDDstQmEQA7
+" alt=":-)" class="wp-smiley" /> </p>
+<p>&nbsp;</p>
+<hr width="100%"/>
+<table width="100%" cellpadding="0" cellspacing="0">
+<tr><td align="right"><font color="#ababab">Date:</font>&nbsp;&nbsp;</td><td><font color="#ababab">2016-07-19 11:54:24 +0200</font></td></tr>
+<tr><td align="right"><font color="#ababab">Author:</font>&nbsp;&nbsp;</td><td><font color="#ababab">malmoborg</font></td></tr>
+<tr><td align="right"><font color="#ababab">Filed under:</font>&nbsp;&nbsp;</td><td><font color="#ababab">Nyheter</font></td></tr>
+</table>
+
+--=-1468922508-176605-12427-9500-21-=--
diff --git a/test/corpora/lkml/cur/1354585346.000260:2, b/test/corpora/lkml/cur/1354585346.000260:2,
new file mode 100644 (file)
index 0000000..04664e5
--- /dev/null
@@ -0,0 +1,99 @@
+Return-Path: <stefan@datenfreihafen.org>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 055BC431FBF
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:11:31 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id Vz+mNzdau2Gh for <notmuch@notmuchmail.org>;
+       Sat, 21 Nov 2009 16:11:30 -0800 (PST)
+Received: from sirius.lasnet.de (sirius.lasnet.de [78.47.116.19])
+       by olra.theworths.org (Postfix) with ESMTP id CFD61431FAE
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:11:29 -0800 (PST)
+Received: from p5b034af6.dip.t-dialin.net ([91.3.74.246] helo=excalibur)
+       by sirius.lasnet.de with esmtpsa 
+       (Cipher TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.63 #1)
+       id 1NC032-0000td-2v by authid <stefan@sostec.de> with cram_md5;
+       Sun, 22 Nov 2009 01:11:28 +0100
+Received: from stefan by excalibur with local (Exim 4.69)
+       (envelope-from <stefan@excalibur.local>)
+       id 1NC031-0001Dm-H7; Sun, 22 Nov 2009 01:11:23 +0100
+From: Stefan Schmidt <stefan@datenfreihafen.org>
+To: notmuch@notmuchmail.org
+Date: Sun, 22 Nov 2009 01:11:01 +0100
+Message-Id: <1258848661-4660-2-git-send-email-stefan@datenfreihafen.org>
+X-Mailer: git-send-email 1.6.5.3
+In-Reply-To: <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+References: <yes> <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+Subject: [notmuch] [PATCH 2/2] notmuch-new: Tag mails not as unread when the
+       seen flag in the maildir is set.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 00:11:31 -0000
+
+With the new notmuch_message_get_flags() function we can get the information if
+a message was already flagged as seen in maildir by another mailer or tool. This
+gives a more realistic picture instead of flagging all as unread.
+
+Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>
+---
+ Makefile      |    2 +-
+ notmuch-new.c |   16 +++++++++++++++-
+ 2 files changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/Makefile b/Makefile
+index 3fedcf1..dfcfc70 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,6 +1,6 @@
+ # Default FLAGS, (can be overridden by user such as "make CFLAGS=-O2")
+ WARN_FLAGS=-Wall -Wextra -Wmissing-declarations -Wwrite-strings -Wswitch-enum
+-CFLAGS=-O2
++CFLAGS=-O0 -ggdb3
+ # Additional flags that we will append to whatever the user set.
+ # These aren't intended for the user to manipulate.
+diff --git a/notmuch-new.c b/notmuch-new.c
+index bc35b4e..ef4429d 100644
+--- a/notmuch-new.c
++++ b/notmuch-new.c
+@@ -41,8 +41,22 @@ handle_sigint (unused (int sig))
+ static void
+ tag_inbox_and_unread (notmuch_message_t *message)
+ {
+-    notmuch_message_add_tag (message, "inbox");
++    char *buf;
++    int i;
++
++    buf = notmuch_message_get_flags (message);
++    for (i = 0; i < strlen (buf); i++) {
++        /* If the S flag is set the message can be tagged as read */
++        if (buf[i] == 'S') {
++            notmuch_message_add_tag (message, "read");
++            goto inbox;
++        }
++    }
++
+     notmuch_message_add_tag (message, "unread");
++
++inbox:
++    notmuch_message_add_tag (message, "inbox");
+ }
+ static void
+-- 
+1.6.5.3
+
+
diff --git a/test/corpora/lkml/cur/1354585346.000261:2, b/test/corpora/lkml/cur/1354585346.000261:2,
new file mode 100644 (file)
index 0000000..5ca8132
--- /dev/null
@@ -0,0 +1,131 @@
+Return-Path: <stefan@datenfreihafen.org>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id E4203431FBF
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:11:31 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id k6PahtnYXl0O for <notmuch@notmuchmail.org>;
+       Sat, 21 Nov 2009 16:11:30 -0800 (PST)
+Received: from sirius.lasnet.de (sirius.lasnet.de [78.47.116.19])
+       by olra.theworths.org (Postfix) with ESMTP id 1BAB6431FBC
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:11:30 -0800 (PST)
+Received: from p5b034af6.dip.t-dialin.net ([91.3.74.246] helo=excalibur)
+       by sirius.lasnet.de with esmtpsa 
+       (Cipher TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.63 #1)
+       id 1NC02v-0000t5-LF by authid <stefan@sostec.de> with cram_md5;
+       Sun, 22 Nov 2009 01:11:29 +0100
+Received: from stefan by excalibur with local (Exim 4.69)
+       (envelope-from <stefan@excalibur.local>)
+       id 1NC02u-0001Dj-V9; Sun, 22 Nov 2009 01:11:16 +0100
+From: Stefan Schmidt <stefan@datenfreihafen.org>
+To: notmuch@notmuchmail.org
+Date: Sun, 22 Nov 2009 01:11:00 +0100
+Message-Id: <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+X-Mailer: git-send-email 1.6.5.3
+In-Reply-To: <yes>
+References: <yes>
+Subject: [notmuch] [PATCH 1/2] lib/message: Add function to get maildir
+       flags.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 00:11:32 -0000
+
+With notmuch_message_get_flags() we gain the information if the message was
+flagged as read, draft, trashed, etc. Handy for big mail spooles that were used
+with another mailer.
+
+Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>
+---
+ lib/message.cc |   26 ++++++++++++++++++++++++++
+ lib/notmuch.h  |   10 ++++++++++
+ 2 files changed, 36 insertions(+), 0 deletions(-)
+
+diff --git a/lib/message.cc b/lib/message.cc
+index 069cedb..9bec61e 100644
+--- a/lib/message.cc
++++ b/lib/message.cc
+@@ -35,6 +35,7 @@ struct _notmuch_message {
+     char *thread_id;
+     char *in_reply_to;
+     char *filename;
++    char *flags;
+     notmuch_message_file_t *message_file;
+     notmuch_message_list_t *replies;
+@@ -114,6 +115,7 @@ _notmuch_message_create (const void *talloc_owner,
+     message->thread_id = NULL;
+     message->in_reply_to = NULL;
+     message->filename = NULL;
++    message->flags = NULL;
+     message->message_file = NULL;
+     message->replies = _notmuch_message_list_create (message);
+@@ -438,6 +440,30 @@ notmuch_message_get_filename (notmuch_message_t *message)
+     return message->filename;
+ }
++const char *
++notmuch_message_get_flags (notmuch_message_t *message)
++{
++    std::string filename_str, flags;
++    size_t position;
++    const char *db_path;
++
++    if (message->flags)
++      return message->flags;
++
++    filename_str = message->doc.get_data ();
++    db_path = notmuch_database_get_path (message->notmuch);
++
++    if (filename_str[0] != '/')
++      filename_str.insert (0, db_path);
++
++    /* Flags are everything behind ":" */
++    position = filename_str.find (":");
++    flags = filename_str.substr (position + 3); /* We don't want :2, */
++    message->flags = talloc_strdup (message, flags.c_str ());
++
++    return message->flags;
++}
++
+ time_t
+ notmuch_message_get_date (notmuch_message_t *message)
+ {
+diff --git a/lib/notmuch.h b/lib/notmuch.h
+index a61cd02..1da5dfd 100644
+--- a/lib/notmuch.h
++++ b/lib/notmuch.h
+@@ -694,6 +694,16 @@ notmuch_message_get_replies (notmuch_message_t *message);
+ const char *
+ notmuch_message_get_filename (notmuch_message_t *message);
++/* Get the maildir flags for the email corresponding to 'message'.
++ *
++ * The returned flags will be a string of ascii format flags.
++ *
++ * The returned string belongs to the message so should not be
++ * modified or freed by the caller (nor should it be referenced after
++ * the message is destroyed). */
++const char *
++notmuch_message_get_flags (notmuch_message_t *message);
++
+ /* Get the date of 'message' as a time_t value.
+  *
+  * For the original textual representation of the Date header from the
+-- 
+1.6.5.3
+
+
diff --git a/test/corpora/lkml/cur/1354585346.000265:2, b/test/corpora/lkml/cur/1354585346.000265:2,
new file mode 100644 (file)
index 0000000..7f3acd4
--- /dev/null
@@ -0,0 +1,85 @@
+Return-Path: <keithp@keithp.com>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 5656D431FBC
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:28:35 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id Dam69vzNZiE1 for <notmuch@notmuchmail.org>;
+       Sat, 21 Nov 2009 16:28:34 -0800 (PST)
+Received: from keithp.com (home.keithp.com [63.227.221.253])
+       by olra.theworths.org (Postfix) with ESMTP id AA991431FAE
+       for <notmuch@notmuchmail.org>; Sat, 21 Nov 2009 16:28:34 -0800 (PST)
+Received: from localhost (localhost [127.0.0.1])
+       by keithp.com (Postfix) with ESMTP id 18FC076012A;
+       Sat, 21 Nov 2009 16:28:34 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at keithp.com
+Received: from keithp.com ([127.0.0.1])
+       by localhost (keithp.com [127.0.0.1]) (amavisd-new, port 10024)
+       with LMTP id tw1iDvWYNGRC; Sat, 21 Nov 2009 16:28:31 -0800 (PST)
+Received: by keithp.com (Postfix, from userid 1033)
+       id 60F3176012B; Sat, 21 Nov 2009 16:28:31 -0800 (PST)
+Received: from keithp.com (localhost [127.0.0.1])
+       by keithp.com (Postfix) with ESMTP id 5330D76012A;
+       Sat, 21 Nov 2009 16:28:31 -0800 (PST)
+From: Keith Packard <keithp@keithp.com>
+To: Stefan Schmidt <stefan@datenfreihafen.org>, notmuch@notmuchmail.org
+In-Reply-To: <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+References: <yes> <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+Date: Sat, 21 Nov 2009 16:28:30 -0800
+Message-ID: <yunvdh3pfm9.fsf@aiko.keithp.com>
+MIME-Version: 1.0
+Content-Type: multipart/signed; boundary="=-=-=";
+       micalg=pgp-sha1; protocol="application/pgp-signature"
+Subject: Re: [notmuch] [PATCH 1/2] lib/message: Add function to get maildir
+ flags.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 00:28:35 -0000
+
+--=-=-=
+Content-Transfer-Encoding: quoted-printable
+
+On Sun, 22 Nov 2009 01:11:00 +0100, Stefan Schmidt <stefan@datenfreihafen.o=
+rg> wrote:
+
+> +const char *
+> +notmuch_message_get_flags (notmuch_message_t *message)
+
+This function should interpret the flags that it finds and return a
+suitable set of notmuch tags. I'd suggest that 'unread' messages get
+both 'unread' and 'inbox' tags, as Maildir doesn't distinguish between
+'don't show this to be by default again please' and 'I've read this
+message'. It seems best to hide the maildir-specific details inside the
+library instead of exposing them.
+
+Also, we have only the 'unread' tag; we don't have a 'read' tag, which
+would simply be the inverse of 'unread'.
+
+=2D-=20
+keith.packard@intel.com
+
+--=-=-=
+Content-Type: application/pgp-signature
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iD8DBQFLCIWuQp8BWwlsTdMRAvcTAKDXHYc6MTuuZFMzHvZFs1omBbr9CACdEY/b
+jqyT/QmmgoA/GtIcs/DfLMY=
+=LVlh
+-----END PGP SIGNATURE-----
+--=-=-=--
+
diff --git a/test/corpora/lkml/cur/1354585346.000323:2, b/test/corpora/lkml/cur/1354585346.000323:2,
new file mode 100644 (file)
index 0000000..b7fc28f
--- /dev/null
@@ -0,0 +1,105 @@
+Return-Path: <stefan@datenfreihafen.org>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 912FF431FBF
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 10:33:44 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id 9T+Abdbhab0i for <notmuch@notmuchmail.org>;
+       Sun, 22 Nov 2009 10:33:43 -0800 (PST)
+Received: from sirius.lasnet.de (sirius.lasnet.de [78.47.116.19])
+       by olra.theworths.org (Postfix) with ESMTP id 39D1C431FAE
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 10:33:43 -0800 (PST)
+Received: from p5b0353d3.dip.t-dialin.net ([91.3.83.211] helo=excalibur)
+       by sirius.lasnet.de with esmtpsa 
+       (Cipher TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.63 #1)
+       id 1NCHFh-0000dR-It by authid <stefan@sostec.de> with cram_md5;
+       Sun, 22 Nov 2009 19:33:40 +0100
+Received: from stefan by excalibur with local (Exim 4.69)
+       (envelope-from <stefan@excalibur.local>)
+       id 1NCHFi-0002ot-2C; Sun, 22 Nov 2009 19:33:38 +0100
+Date: Sun, 22 Nov 2009 19:33:38 +0100
+From: Stefan Schmidt <stefan@datenfreihafen.org>
+To: Keith Packard <keithp@keithp.com>
+Message-ID: <20091122183338.GB5735@excalibur.local>
+References: <yes> <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+       <yunvdh3pfm9.fsf@aiko.keithp.com>
+MIME-Version: 1.0
+Content-Type: multipart/signed; micalg=pgp-sha1;
+       protocol="application/pgp-signature"; boundary="C7zPtVaVf+AK4Oqc"
+Content-Disposition: inline
+In-Reply-To: <yunvdh3pfm9.fsf@aiko.keithp.com>
+X-Mailer: Mutt http://www.mutt.org/
+X-KeyID: 0xDDF51665
+X-Website: http://www.datenfreihafen.org/
+User-Agent: Mutt/1.5.20 (2009-06-14)
+Cc: notmuch@notmuchmail.org
+Subject: Re: [notmuch] [PATCH 1/2] lib/message: Add function to get maildir
+ flags.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 18:33:44 -0000
+
+
+--C7zPtVaVf+AK4Oqc
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+Hello.
+
+On Sat, 2009-11-21 at 16:28, Keith Packard wrote:
+> On Sun, 22 Nov 2009 01:11:00 +0100, Stefan Schmidt <stefan@datenfreihafen=
+=2Eorg> wrote:
+>=20
+> > +const char *
+> > +notmuch_message_get_flags (notmuch_message_t *message)
+>=20
+> This function should interpret the flags that it finds and return a
+> suitable set of notmuch tags. I'd suggest that 'unread' messages get
+> both 'unread' and 'inbox' tags, as Maildir doesn't distinguish between
+> 'don't show this to be by default again please' and 'I've read this
+> message'. It seems best to hide the maildir-specific details inside the
+> library instead of exposing them.
+
+Thanks for the review. On a second thought the interface was really a bit u=
+gly.
+:)
+
+I'm just back to my box and going through the outstanding mails shows me th=
+at
+Michiel Buddingh has a more complete patch on the
+convert-maildir-flags-into-tags issue which Carl has tagged for review. Will
+wait what comes out of it and if anything is left for me to. :)
+
+regards
+Stefan Schmidt
+
+--C7zPtVaVf+AK4Oqc
+Content-Type: application/pgp-signature; name="signature.asc"
+Content-Description: Digital signature
+Content-Disposition: inline
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+Comment: http://www.datenfreihafen.org/contact.html
+
+iEYEARECAAYFAksJhAIACgkQbNSsvd31FmWDDgCgswbE3BE2XeExPzBBJf86efDw
+aFwAoMc3vaBmTjB2kG5ORUmk1E/ICBXK
+=k8v5
+-----END PGP SIGNATURE-----
+
+--C7zPtVaVf+AK4Oqc--
+
diff --git a/test/corpora/lkml/cur/1354585346.000324:2, b/test/corpora/lkml/cur/1354585346.000324:2,
new file mode 100644 (file)
index 0000000..a72ef9a
--- /dev/null
@@ -0,0 +1,71 @@
+Return-Path: <michiel@michielbuddingh.net>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id B580E431FBC
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 10:55:27 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id JHZeV0d6+Q8a for <notmuch@notmuchmail.org>;
+       Sun, 22 Nov 2009 10:55:26 -0800 (PST)
+Received: from aegir.org.uk (aegir.org.uk [87.238.170.13])
+       by olra.theworths.org (Postfix) with ESMTP id C6AAC431FAE
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 10:55:26 -0800 (PST)
+Received: from localhost.localdomain (109-9-ftth.onsnetstudenten.nl
+       [145.120.9.109])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by aegir.org.uk (Postfix) with ESMTPSA id 48CE72E02A;
+       Sun, 22 Nov 2009 19:55:26 +0100 (CET)
+Date: Sun, 22 Nov 2009 19:55:26 +0100
+From: Michiel Buddingh' <michiel@michielbuddingh.net>
+To: notmuch@notmuchmail.org, stefan@datenfreihafen.org,
+ keithp@keithp.com
+Message-ID: <4b09891e.YhJ/aJZOBwneOaFr%michiel@michielbuddingh.net>
+References: <yes> <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+       <yunvdh3pfm9.fsf@aiko.keithp.com>
+       <20091122183338.GB5735@excalibur.local>
+In-Reply-To: <20091122183338.GB5735@excalibur.local>
+User-Agent: Heirloom mailx 12.4 7/29/08
+MIME-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Subject: Re: [notmuch] [PATCH 1/2] lib/message: Add function to get maildir
+ flags.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 18:55:27 -0000
+
+Stefan Schmidt <stefan@datenfreihafen.org> wrote:
+> > This function should interpret the flags that it finds and return a
+> > suitable set of notmuch tags. I'd suggest that 'unread' messages get
+> > both 'unread' and 'inbox' tags, as Maildir doesn't distinguish between
+> > 'don't show this to be by default again please' and 'I've read this
+> > message'. It seems best to hide the maildir-specific details inside the
+> > library instead of exposing them.
+>
+> Thanks for the review. On a second thought the interface was really a bit ugly.
+> :)
+>
+> I'm just back to my box and going through the outstanding mails shows me that
+> Michiel Buddingh has a more complete patch on the
+> convert-maildir-flags-into-tags issue which Carl has tagged for review. Will
+> wait what comes out of it and if anything is left for me to. :)
+
+Apologies.  In my haste to cover up my appalling and incorrect first patch, I
+neglected to review the archives to see if someone had already done this. Sorry
+for stealing your thunder.
+
+Michiel
+
diff --git a/test/corpora/lkml/cur/1354585346.000325:2, b/test/corpora/lkml/cur/1354585346.000325:2,
new file mode 100644 (file)
index 0000000..fc2da16
--- /dev/null
@@ -0,0 +1,86 @@
+Return-Path: <stefan@datenfreihafen.org>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id B2DE3431FBC
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 11:52:54 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id TrLH7uZhkvFU for <notmuch@notmuchmail.org>;
+       Sun, 22 Nov 2009 11:52:53 -0800 (PST)
+Received: from sirius.lasnet.de (sirius.lasnet.de [78.47.116.19])
+       by olra.theworths.org (Postfix) with ESMTP id 7024B431FAE
+       for <notmuch@notmuchmail.org>; Sun, 22 Nov 2009 11:52:53 -0800 (PST)
+Received: from p5b0353d3.dip.t-dialin.net ([91.3.83.211] helo=excalibur)
+       by sirius.lasnet.de with esmtpsa 
+       (Cipher TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.63 #1)
+       id 1NCIUJ-0002QO-OM by authid <stefan@sostec.de> with cram_md5;
+       Sun, 22 Nov 2009 20:52:50 +0100
+Received: from stefan by excalibur with local (Exim 4.69)
+       (envelope-from <stefan@excalibur.local>)
+       id 1NCIUI-0003ON-Sr; Sun, 22 Nov 2009 20:52:46 +0100
+Date: Sun, 22 Nov 2009 20:52:46 +0100
+From: Stefan Schmidt <stefan@datenfreihafen.org>
+To: Michiel Buddingh' <michiel@michielbuddingh.net>
+Message-ID: <20091122195246.GC5735@excalibur.local>
+References: <yes> <1258848661-4660-1-git-send-email-stefan@datenfreihafen.org>
+       <yunvdh3pfm9.fsf@aiko.keithp.com>
+       <20091122183338.GB5735@excalibur.local>
+       <4b09891e.YhJ/aJZOBwneOaFr%michiel@michielbuddingh.net>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+In-Reply-To: <4b09891e.YhJ/aJZOBwneOaFr%michiel@michielbuddingh.net>
+X-Mailer: Mutt http://www.mutt.org/
+X-KeyID: 0xDDF51665
+X-Website: http://www.datenfreihafen.org/
+User-Agent: Mutt/1.5.20 (2009-06-14)
+Cc: notmuch@notmuchmail.org
+Subject: Re: [notmuch] [PATCH 1/2] lib/message: Add function to get maildir
+ flags.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Sun, 22 Nov 2009 19:52:54 -0000
+
+Hello.
+
+On Sun, 2009-11-22 at 19:55, Michiel Buddingh' wrote:
+> Stefan Schmidt <stefan@datenfreihafen.org> wrote:
+> > > This function should interpret the flags that it finds and return a
+> > > suitable set of notmuch tags. I'd suggest that 'unread' messages get
+> > > both 'unread' and 'inbox' tags, as Maildir doesn't distinguish between
+> > > 'don't show this to be by default again please' and 'I've read this
+> > > message'. It seems best to hide the maildir-specific details inside the
+> > > library instead of exposing them.
+> >
+> > Thanks for the review. On a second thought the interface was really a bit ugly.
+> > :)
+> >
+> > I'm just back to my box and going through the outstanding mails shows me that
+> > Michiel Buddingh has a more complete patch on the
+> > convert-maildir-flags-into-tags issue which Carl has tagged for review. Will
+> > wait what comes out of it and if anything is left for me to. :)
+> 
+> Apologies.  In my haste to cover up my appalling and incorrect first patch, I
+> neglected to review the archives to see if someone had already done this. Sorry
+> for stealing your thunder.
+
+No need to be sorry. I'm interestecd in having this itch scratched, but don't
+care who is doing it. :)
+
+Just go ahead and get it in after Carl's review.
+
+regards
+Stefan Schmidt
+
diff --git a/test/corpora/lkml/cur/1354585346.000539:2, b/test/corpora/lkml/cur/1354585346.000539:2,
new file mode 100644 (file)
index 0000000..daa5673
--- /dev/null
@@ -0,0 +1,81 @@
+Return-Path: <ingmar@exherbo.org>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id BAA52431FBC
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 00:43:44 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id 0U+OLchDCS5T for <notmuch@notmuchmail.org>;
+       Thu, 26 Nov 2009 00:43:44 -0800 (PST)
+Received: from bach.exherbo.org (bach.exherbo.org [78.47.197.147])
+       by olra.theworths.org (Postfix) with ESMTP id 05223431FAE
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 00:43:43 -0800 (PST)
+Received: from [83.101.72.69] (helo=localhost)
+       by bach.exherbo.org with esmtpsa (TLSv1:AES256-SHA:256) (Exim 4.69)
+       (envelope-from <ingmar@exherbo.org>) id 1NDZx1-0000VV-3u
+       for notmuch@notmuchmail.org; Thu, 26 Nov 2009 08:43:43 +0000
+Content-Type: text/plain; charset=utf8
+From: Ingmar Vanhassel <ingmar@exherbo.org>
+To: notmuch <notmuch@notmuchmail.org>
+In-reply-to: <1259223435-29656-1-git-send-email-stefan@datenfreihafen.org>
+References: <yes> <1259223435-29656-1-git-send-email-stefan@datenfreihafen.org>
+Date: Thu, 26 Nov 2009 09:43:42 +0100
+Message-Id: <1259224970-sup-5259@cannonball>
+User-Agent: Sup/git
+Content-Transfer-Encoding: 8bit
+Subject: Re: [notmuch] [PATCH] Makefile: Enable backslash escapes for echo.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Thu, 26 Nov 2009 08:43:44 -0000
+
+Excerpts from Stefan Schmidt's message of Thu Nov 26 09:17:15 +0100 2009:
+> This fixes a visual glitch during a silent compile.
+> Before:
+> Use "make V=1" to see the verbose compile lines.\n  CC  debugger.o
+>   CC    gmime-filter-reply.o
+> 
+> After:
+> Use "make V=1" to see the verbose compile lines.
+>   CC    debugger.o
+>   CC    gmime-filter-reply.o
+> 
+> Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>
+
+Looks right, works here with bash, dash & zsh, so:
+
+Reviewed-by: Ingmar Vanhassel <ingmar@exherbo.org>
+
+Thanks!
+
+> ---
+>  Makefile |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+> diff --git a/Makefile b/Makefile
+> index 2cd1b1b..2d19a6e 100644
+> --- a/Makefile
+> +++ b/Makefile
+> @@ -41,7 +41,7 @@ include Makefile.config
+>  # user how to enable verbose compiles.
+>  ifeq ($(V),)
+>  quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
+> -quiet = @echo $(quiet_DOC)$(eval quiet_DOC:=)"  $1    $@"; $($1)
+> +quiet = @echo -e $(quiet_DOC)$(eval quiet_DOC:=)"  $1    $@"; $($1)
+>  endif
+>  # The user has explicitly enabled quiet compilation.
+>  ifeq ($(V),0)
+-- 
+Exherbo KDE, X.org maintainer
+
diff --git a/test/corpora/lkml/cur/1354585346.000541:2, b/test/corpora/lkml/cur/1354585346.000541:2,
new file mode 100644 (file)
index 0000000..3e70055
--- /dev/null
@@ -0,0 +1,64 @@
+Return-Path: <kha@treskal.com>
+X-Original-To: notmuch@notmuchmail.org
+Delivered-To: notmuch@notmuchmail.org
+Received: from localhost (localhost [127.0.0.1])
+       by olra.theworths.org (Postfix) with ESMTP id 54307431FBC
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 03:41:18 -0800 (PST)
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
+Received: from olra.theworths.org ([127.0.0.1])
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
+       with ESMTP id TBdvxm5kBScu for <notmuch@notmuchmail.org>;
+       Thu, 26 Nov 2009 03:41:13 -0800 (PST)
+Received: from mail1.space2u.com (mail1.space2u.com [62.20.1.135])
+       by olra.theworths.org (Postfix) with ESMTP id 80538431FAE
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 03:41:13 -0800 (PST)
+Received: from mail-bw0-f224.google.com (mail-bw0-f224.google.com
+       [209.85.218.224]) (authenticated bits=0)
+       by mail1.space2u.com (8.14.3/8.14.3) with ESMTP id nAQBf0Ar018995
+       (version=TLSv1/SSLv3 cipher=DES-CBC3-SHA bits=168 verify=NOT)
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 12:41:01 +0100
+Received: by bwz24 with SMTP id 24so480173bwz.30
+       for <notmuch@notmuchmail.org>; Thu, 26 Nov 2009 03:41:11 -0800 (PST)
+MIME-Version: 1.0
+Received: by 10.204.153.3 with SMTP id i3mr2263267bkw.26.1259235670122; Thu, 
+       26 Nov 2009 03:41:10 -0800 (PST)
+In-Reply-To: <20091126110505.GI25119@ryngle.com>
+References: <1259223435-29656-1-git-send-email-stefan@datenfreihafen.org>
+       <20091126110505.GI25119@ryngle.com>
+Date: Thu, 26 Nov 2009 12:41:10 +0100
+Message-ID: <b8197bcb0911260341o480edc2bof8a30f0b724dd96@mail.gmail.com>
+From: Karl Wiberg <kha@treskal.com>
+To: Jan Janak <jan@ryngle.com>
+Content-Type: text/plain; charset=UTF-8
+Cc: notmuch@notmuchmail.org
+Subject: Re: [notmuch] [PATCH] Makefile: Enable backslash escapes for echo.
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+X-List-Received-Date: Thu, 26 Nov 2009 11:41:18 -0000
+
+On Thu, Nov 26, 2009 at 12:05 PM, Jan Janak <jan@ryngle.com> wrote:
+
+> I sent exactly the same patch a couple of days ago and it was
+> rejected because it does not work everywhere, see:
+>
+> http://notmuchmail.org/pipermail/notmuch/2009/000370.html
+
+And as I said in that thread, I believe you should use printf instead.
+(http://www.in-ulm.de/~mascheck/various/echo+printf/ seems like a good
+reference in this matter.)
+
+-- 
+Karl Wiberg, kha@treskal.com
+   subrabbit.wordpress.com
+   www.treskal.com/kalle
+
diff --git a/test/corpora/lkml/cur/1382298587.001724:2, b/test/corpora/lkml/cur/1382298587.001724:2,
new file mode 100644 (file)
index 0000000..69c794c
--- /dev/null
@@ -0,0 +1,104 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Tue, 22 Jun 2010 20:55:09 +0530
+Lines: 66
+Message-ID: <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:25:29 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5Ls-0004PS-BM
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:25:28 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1755015Ab0FVPZ1 (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:25:27 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:48639 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1754070Ab0FVPZ1 (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:25:27 -0400
+X-Greylist: delayed 316 seconds by postgrey-1.27 at vger.kernel.org; Tue, 22 Jun 2010 11:25:26 EDT
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:25:11 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001750>
+
+Add a mount option 'fsc' to enable local caching on CIFS.
+
+As the cifs-utils (userspace) changes are not done yet, this patch enables
+'fsc' by default to assist testing.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/cifs_fs_sb.h |    1 +
+ fs/cifs/connect.c    |    8 ++++++++
+ 2 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
+index 246a167..9e77145 100644
+--- a/fs/cifs/cifs_fs_sb.h
++++ b/fs/cifs/cifs_fs_sb.h
+@@ -35,6 +35,7 @@
+ #define CIFS_MOUNT_DYNPERM      0x1000 /* allow in-memory only mode setting   */
+ #define CIFS_MOUNT_NOPOSIXBRL   0x2000 /* mandatory not posix byte range lock */
+ #define CIFS_MOUNT_NOSSYNC      0x4000 /* don't do slow SMBflush on every sync*/
++#define CIFS_MOUNT_FSCACHE    0x8000 /* local caching enabled */
+ struct cifs_sb_info {
+       struct cifsTconInfo *tcon;      /* primary mount */
+diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
+index 4844dbd..6c6ff3c 100644
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -98,6 +98,7 @@ struct smb_vol {
+       bool noblocksnd:1;
+       bool noautotune:1;
+       bool nostrictsync:1; /* do not force expensive SMBflush on every sync */
++      bool fsc:1;     /* enable fscache */
+       unsigned int rsize;
+       unsigned int wsize;
+       bool sockopt_tcp_nodelay:1;
+@@ -843,6 +844,9 @@ cifs_parse_mount_options(char *options, const char *devname,
+       /* default to using server inode numbers where available */
+       vol->server_ino = 1;
++      /* XXX: default to fsc for testing until mount.cifs pieces are done */
++      vol->fsc = 1;
++
+       if (!options)
+               return 1;
+@@ -1332,6 +1336,8 @@ cifs_parse_mount_options(char *options, const char *devname,
+                       printk(KERN_WARNING "CIFS: Mount option noac not "
+                               "supported. Instead set "
+                               "/proc/fs/cifs/LookupCacheEnabled to 0\n");
++              } else if (strnicmp(data, "fsc", 3) == 0) {
++                      vol->fsc = true;
+               } else
+                       printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
+                                               data);
+@@ -2405,6 +2411,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+       if (pvolume_info->dynperm)
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
++      if (pvolume_info->fsc)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE;
+       if (pvolume_info->direct_io) {
+               cFYI(1, "mounting share using direct i/o");
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001730:2, b/test/corpora/lkml/cur/1382298587.001730:2,
new file mode 100644 (file)
index 0000000..840be2e
--- /dev/null
@@ -0,0 +1,103 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 00/10] cifs: local caching support using FS-Cache
+Date: Tue, 22 Jun 2010 20:50:05 +0530
+Lines: 66
+Message-ID: <1277220005-3322-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:40:38 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5aY-00055O-BD
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:40:38 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751889Ab0FVPkf (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:40:35 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:50040 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751554Ab0FVPkf (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:40:35 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:20:07 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001756>
+
+This patchset is a first stab at adding persistent, local caching facility for
+CIFS using the FS-Cache interface.
+
+The index hierarchy which is mainly used to locate a file object or discard
+a certain subset of the files cached, currently has three levels:
+       - Server
+       - Share 
+       - File
+
+The server index object is keyed by hostname of the server. The superblock
+index object is keyed by the sharename and the inode object is keyed by the
+UniqueId. The cache coherency is ensured by checking the 'LastWriteTime' and
+size of file.
+
+To use this, apply this patchset in order, mount the share with rsize=4096 and
+try copying a huge file (say few hundred MBs) from mount point to local
+filesystem. During the first time, the cache will be initialized. When you copy
+the second time, it should read from the local cache.
+
+To reduce the impact of page cache and see the local caching in action
+readily, try doing a sync and drop the caches by doing:
+       sync; echo 3 > /proc/sys/vm/drop_caches
+
+Known issues
+-------------
+       - the cache coherency check may not be reliable always as some
+         CIFS servers are known not to update mtime until the filehandle is
+         closed.
+       - not all the Servers under all circumstances provide a unique
+         'UniqueId'.
+
+Todo's
+-------
+       - improvements to avoid potential key collisions
+       - address the above known issues
+
+This set is lightly tested and all the bugs seen during my testing have been
+fixed. However, this can be considered as an RFC for now.
+
+Any Comments or Suggestions are welcome.
+
+Suresh Jayaraman (10)
+  cifs: add kernel config option for CIFS Client caching support
+  cifs: guard cifsglob.h against multiple inclusion
+  cifs: register CIFS for caching
+  cifs: define server-level cache index objects and register them with FS-Cache
+  cifs: define superblock-level cache index objects and register them
+  cifs: define inode-level cache object and register them
+  cifs: FS-Cache page management
+  cifs: store pages into local cache
+  cifs: read pages from FS-Cache
+  cifs: add mount option to enable local caching
+
+ Kconfig      |    9 ++
+ Makefile     |    2 
+ cache.c      |  251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ cifs_fs_sb.h |    1 
+ cifsfs.c     |   15 +++
+ cifsglob.h   |   14 +++
+ connect.c    |   16 +++
+ file.c       |   51 +++++++++++
+ fscache.c    |  244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fscache.h    |  135 +++++++++++++++++++++++++++++++
+ inode.c      |    4 
+ 11 files changed, 742 insertions(+)
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001731:2, b/test/corpora/lkml/cur/1382298587.001731:2,
new file mode 100644 (file)
index 0000000..d8b3168
--- /dev/null
@@ -0,0 +1,67 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 01/10] cifs: add kernel config option for CIFS Client caching support
+Date: Tue, 22 Jun 2010 20:52:38 +0530
+Lines: 30
+Message-ID: <1277220158-3405-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:43:27 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5dG-0007m9-Ij
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:43:26 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1751536Ab0FVPnS (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:18 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:51303 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1750800Ab0FVPnR (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:43:17 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:22:40 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001757>
+
+Add a kernel config option to enable local caching for CIFS.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/Kconfig |    9 +++++++++
+ 1 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/Kconfig b/fs/cifs/Kconfig
+index 80f3525..5739fd7 100644
+--- a/fs/cifs/Kconfig
++++ b/fs/cifs/Kconfig
+@@ -131,6 +131,15 @@ config CIFS_DFS_UPCALL
+           IP addresses) which is needed for implicit mounts of DFS junction
+           points. If unsure, say N.
++config CIFS_FSCACHE
++        bool "Provide CIFS client caching support (EXPERIMENTAL)"
++        depends on EXPERIMENTAL
++        depends on CIFS=m && FSCACHE || CIFS=y && FSCACHE=y
++        help
++          Makes CIFS FS-Cache capable. Say Y here if you want your CIFS data
++          to be cached locally on disk through the general filesystem cache
++          manager. If unsure, say N.
++
+ config CIFS_EXPERIMENTAL
+         bool "CIFS Experimental Features (EXPERIMENTAL)"
+         depends on CIFS && EXPERIMENTAL
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001732:2, b/test/corpora/lkml/cur/1382298587.001732:2,
new file mode 100644 (file)
index 0000000..8850953
--- /dev/null
@@ -0,0 +1,73 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 02/10] cifs: guard cifsglob.h against multiple inclusion
+Date: Tue, 22 Jun 2010 20:52:50 +0530
+Lines: 36
+Message-ID: <1277220170-3442-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-fsdevel-owner@vger.kernel.org Tue Jun 22 17:43:39 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OR5dT-0007sB-18
+       for lnx-linux-fsdevel@lo.gmane.org; Tue, 22 Jun 2010 17:43:39 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752441Ab0FVPn3 (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:29 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:41538 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751889Ab0FVPn2 (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:43:28 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:22:52 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001758>
+
+Add conditional compile macros to guard the header file against multiple
+inclusion.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cifsglob.h |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
+index a88479c..6b2c39d 100644
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -16,6 +16,9 @@
+  *   the GNU Lesser General Public License for more details.
+  *
+  */
++#ifndef _CIFS_GLOB_H
++#define _CIFS_GLOB_H
++
+ #include <linux/in.h>
+ #include <linux/in6.h>
+ #include <linux/slab.h>
+@@ -733,3 +736,5 @@ GLOBAL_EXTERN unsigned int cifs_min_small;  /* min size of small buf pool */
+ GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/
+ extern const struct slow_work_ops cifs_oplock_break_ops;
++
++#endif        /* _CIFS_GLOB_H */
+-- 
+1.6.4.2
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001733:2, b/test/corpora/lkml/cur/1382298587.001733:2,
new file mode 100644 (file)
index 0000000..d782f90
--- /dev/null
@@ -0,0 +1,211 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 03/10] cifs: register CIFS for caching
+Date: Tue, 22 Jun 2010 20:53:09 +0530
+Lines: 174
+Message-ID: <1277220189-3485-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:43:52 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5de-0007xC-Ov
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:43:51 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753125Ab0FVPnt (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:49 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:55866 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751261Ab0FVPnt (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:43:49 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:11 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001759>
+
+Define CIFS for FS-Cache and register for caching. Upon registration the
+top-level index object cookie will be stuck to the netfs definition by
+FS-Cache.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/Makefile  |    2 ++
+ fs/cifs/cache.c   |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsfs.c  |    8 ++++++++
+ fs/cifs/fscache.h |   40 ++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 103 insertions(+), 0 deletions(-)
+ create mode 100644 fs/cifs/cache.c
+ create mode 100644 fs/cifs/fscache.h
+
+diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile
+index 9948c00..e2de709 100644
+--- a/fs/cifs/Makefile
++++ b/fs/cifs/Makefile
+@@ -11,3 +11,5 @@ cifs-y := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o \
+ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
+ cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
++
++cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
+diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c
+new file mode 100644
+index 0000000..1080b96
+--- /dev/null
++++ b/fs/cifs/cache.c
+@@ -0,0 +1,53 @@
++/*
++ *   fs/cifs/cache.c - CIFS filesystem cache index structure definitions
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/mm.h>
++
++#include "fscache.h"
++#include "cifsglob.h"
++#include "cifs_debug.h"
++
++/*
++ * CIFS filesystem definition for FS-Cache
++ */
++struct fscache_netfs cifs_fscache_netfs = {
++      .name = "cifs",
++      .version = 0,
++};
++
++/*
++ * Register CIFS for caching with FS-Cache
++ */
++int cifs_fscache_register(void)
++{
++      return fscache_register_netfs(&cifs_fscache_netfs);
++}
++
++/*
++ * Unregister CIFS for caching
++ */
++void cifs_fscache_unregister(void)
++{
++      fscache_unregister_netfs(&cifs_fscache_netfs);
++}
++
+diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
+index 484e52b..c2a7aa9 100644
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -47,6 +47,7 @@
+ #include <linux/key-type.h>
+ #include "dns_resolve.h"
+ #include "cifs_spnego.h"
++#include "fscache.h"
+ #define CIFS_MAGIC_NUMBER 0xFF534D42  /* the first four bytes of SMB PDUs */
+ int cifsFYI = 0;
+@@ -902,6 +903,10 @@ init_cifs(void)
+               cFYI(1, "cifs_max_pending set to max of 256");
+       }
++      rc = cifs_fscache_register();
++      if (rc)
++              goto out;
++
+       rc = cifs_init_inodecache();
+       if (rc)
+               goto out_clean_proc;
+@@ -949,8 +954,10 @@ init_cifs(void)
+       cifs_destroy_mids();
+  out_destroy_inodecache:
+       cifs_destroy_inodecache();
++      cifs_fscache_unregister();
+  out_clean_proc:
+       cifs_proc_clean();
++ out:
+       return rc;
+ }
+@@ -959,6 +966,7 @@ exit_cifs(void)
+ {
+       cFYI(DBG2, "exit_cifs");
+       cifs_proc_clean();
++      cifs_fscache_unregister();
+ #ifdef CONFIG_CIFS_DFS_UPCALL
+       cifs_dfs_release_automount_timer();
+       unregister_key_type(&key_type_dns_resolver);
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+new file mode 100644
+index 0000000..cec9e2b
+--- /dev/null
++++ b/fs/cifs/fscache.h
+@@ -0,0 +1,40 @@
++/*
++ *   fs/cifs/fscache.h - CIFS filesystem cache interface definitions
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#ifndef _CIFS_FSCACHE_H
++#define _CIFS_FSCACHE_H
++
++#include <linux/fscache.h>
++#include "cifsglob.h"
++
++#ifdef CONFIG_CIFS_FSCACHE
++
++extern struct fscache_netfs cifs_fscache_netfs;
++
++extern int cifs_fscache_register(void);
++extern void cifs_fscache_unregister(void);
++
++#else /* CONFIG_CIFS_FSCACHE */
++static inline int cifs_fscache_register(void) { return 0; }
++static inline void cifs_fscache_unregister(void) {}
++
++#endif /* CONFIG_CIFS_FSCACHE */
++
++#endif /* _CIFS_FSCACHE_H */
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001734:2, b/test/corpora/lkml/cur/1382298587.001734:2,
new file mode 100644 (file)
index 0000000..4b64bc3
--- /dev/null
@@ -0,0 +1,223 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 04/10] cifs: define server-level cache index objects and register them with FS-Cache
+Date: Tue, 22 Jun 2010 20:53:18 +0530
+Lines: 186
+Message-ID: <1277220198-3522-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:44:26 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5eD-0008G7-KP
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:44:26 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753942Ab0FVPoC (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:02 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:58783 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751265Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:20 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001760>
+
+Define server-level cache index objects (as managed by TCP_ServerInfo structs).
+Each server object is created in the CIFS top-level index object and is itself
+an index into which superblock-level objects are inserted.
+
+Currently, the server objects are keyed by hostname.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/Makefile   |    2 +-
+ fs/cifs/cache.c    |   25 +++++++++++++++++++++++++
+ fs/cifs/cifsglob.h |    3 +++
+ fs/cifs/connect.c  |    4 ++++
+ fs/cifs/fscache.c  |   47 +++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h  |   12 ++++++++++++
+ 6 files changed, 92 insertions(+), 1 deletion(-)
+ create mode 100644 fs/cifs/fscache.c
+
+Index: cifs-2.6/fs/cifs/Makefile
+===================================================================
+--- cifs-2.6.orig/fs/cifs/Makefile
++++ cifs-2.6/fs/cifs/Makefile
+@@ -12,4 +12,4 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spneg
+ cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
+-cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
++cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -51,3 +51,28 @@ void cifs_fscache_unregister(void)
+       fscache_unregister_netfs(&cifs_fscache_netfs);
+ }
++/*
++ * Server object currently keyed by hostname
++ */
++static uint16_t cifs_server_get_key(const void *cookie_netfs_data,
++                                 void *buffer, uint16_t maxbuf)
++{
++      const struct TCP_Server_Info *server = cookie_netfs_data;
++      uint16_t len = strnlen(server->hostname, sizeof(server->hostname));
++
++      if (len > maxbuf)
++              return 0;
++
++      memcpy(buffer, server->hostname, len);
++
++      return len;
++}
++
++/*
++ * Server object for FS-Cache
++ */
++const struct fscache_cookie_def cifs_fscache_server_index_def = {
++      .name = "CIFS.server",
++      .type = FSCACHE_COOKIE_TYPE_INDEX,
++      .get_key = cifs_server_get_key,
++};
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -193,6 +193,9 @@ struct TCP_Server_Info {
+       bool    sec_mskerberos;         /* supports legacy MS Kerberos */
+       bool    sec_kerberosu2u;        /* supports U2U Kerberos */
+       bool    sec_ntlmssp;            /* supports NTLMSSP */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie   *fscache; /* client index cache cookie */
++#endif
+ };
+ /*
+Index: cifs-2.6/fs/cifs/connect.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/connect.c
++++ cifs-2.6/fs/cifs/connect.c
+@@ -48,6 +48,7 @@
+ #include "nterr.h"
+ #include "rfc1002pdu.h"
+ #include "cn_cifs.h"
++#include "fscache.h"
+ #define CIFS_PORT 445
+ #define RFC1001_PORT 139
+@@ -1453,6 +1454,8 @@ cifs_put_tcp_session(struct TCP_Server_I
+               return;
+       }
++      cifs_fscache_release_client_cookie(server);
++
+       list_del_init(&server->tcp_ses_list);
+       write_unlock(&cifs_tcp_ses_lock);
+@@ -1572,6 +1575,7 @@ cifs_get_tcp_session(struct smb_vol *vol
+               goto out_err;
+       }
++      cifs_fscache_get_client_cookie(tcp_ses);
+       /* thread spawned, put it on the list */
+       write_lock(&cifs_tcp_ses_lock);
+       list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- /dev/null
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -0,0 +1,47 @@
++/*
++ *   fs/cifs/fscache.c - CIFS filesystem cache interface
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/mm.h>
++#include <linux/in6.h>
++
++#include "fscache.h"
++#include "cifsglob.h"
++#include "cifs_debug.h"
++
++void cifs_fscache_get_client_cookie(struct TCP_Server_Info *server)
++{
++      server->fscache =
++              fscache_acquire_cookie(cifs_fscache_netfs.primary_index,
++                              &cifs_fscache_server_index_def, server);
++      cFYI(1, "CIFS: get client cookie (0x%p/0x%p)\n",
++                              server, server->fscache);
++}
++
++void cifs_fscache_release_client_cookie(struct TCP_Server_Info *server)
++{
++      cFYI(1, "CIFS: release client cookie (0x%p/0x%p)\n",
++                              server, server->fscache);
++      fscache_relinquish_cookie(server->fscache, 0);
++      server->fscache = NULL;
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -27,14 +27,26 @@
+ #ifdef CONFIG_CIFS_FSCACHE
+ extern struct fscache_netfs cifs_fscache_netfs;
++extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
++/*
++ * fscache.c
++ */
++extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
++extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
++static inline void
++cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
++static inline void
++cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
++
+ #endif /* CONFIG_CIFS_FSCACHE */
+ #endif /* _CIFS_FSCACHE_H */
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001735:2, b/test/corpora/lkml/cur/1382298587.001735:2,
new file mode 100644 (file)
index 0000000..d76da35
--- /dev/null
@@ -0,0 +1,212 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 07/10] cifs: FS-Cache page management
+Date: Tue, 22 Jun 2010 20:53:48 +0530
+Lines: 175
+Message-ID: <1277220228-3635-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:44:27 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5eF-0008G7-BK
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:44:27 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754757Ab0FVPoS (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:18 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:54214 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752542Ab0FVPoB (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:01 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:50 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001761>
+
+Takes care of invalidation and release of FS-Cache marked pages and also
+invalidation of the FsCache page flag when the inode is removed.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cache.c   |   31 +++++++++++++++++++++++++++++++
+ fs/cifs/file.c    |   20 ++++++++++++++++++++
+ fs/cifs/fscache.c |   26 ++++++++++++++++++++++++++
+ fs/cifs/fscache.h |   16 ++++++++++++++++
+ 4 files changed, 93 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c
+index b205424..3a733c1 100644
+--- a/fs/cifs/cache.c
++++ b/fs/cifs/cache.c
+@@ -210,6 +210,36 @@ fscache_checkaux cifs_fscache_inode_check_aux(void *cookie_netfs_data,
+       return FSCACHE_CHECKAUX_OKAY;
+ }
++static void cifs_fscache_inode_now_uncached(void *cookie_netfs_data)
++{
++      struct cifsInodeInfo *cifsi = cookie_netfs_data;
++      struct pagevec pvec;
++      pgoff_t first;
++      int loop, nr_pages;
++
++      pagevec_init(&pvec, 0);
++      first = 0;
++
++      cFYI(1, "cifs inode 0x%p now uncached\n", cifsi);
++
++      for (;;) {
++              nr_pages = pagevec_lookup(&pvec,
++                                        cifsi->vfs_inode.i_mapping, first,
++                                        PAGEVEC_SIZE - pagevec_count(&pvec));
++              if (!nr_pages)
++                      break;
++
++              for (loop = 0; loop < nr_pages; loop++)
++                      ClearPageFsCache(pvec.pages[loop]);
++
++              first = pvec.pages[nr_pages - 1]->index + 1;
++
++              pvec.nr = nr_pages;
++              pagevec_release(&pvec);
++              cond_resched();
++      }
++}
++
+ const struct fscache_cookie_def cifs_fscache_inode_object_def = {
+       .name           = "CIFS.uniqueid",
+       .type           = FSCACHE_COOKIE_TYPE_DATAFILE,
+@@ -217,4 +247,5 @@ const struct fscache_cookie_def cifs_fscache_inode_object_def = {
+       .get_attr       = cifs_fscache_inode_get_attr,
+       .get_aux        = cifs_fscache_inode_get_aux,
+       .check_aux      = cifs_fscache_inode_check_aux,
++      .now_uncached   = cifs_fscache_inode_now_uncached,
+ };
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 55ecb55..786ec04 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -2271,6 +2271,22 @@ out:
+       return rc;
+ }
++static int cifs_release_page(struct page *page, gfp_t gfp)
++{
++      if (PagePrivate(page))
++              return 0;
++
++      return cifs_fscache_release_page(page, gfp);
++}
++
++static void cifs_invalidate_page(struct page *page, unsigned long offset)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(page->mapping->host);
++
++      if (offset == 0)
++              cifs_fscache_invalidate_page(page, &cifsi->vfs_inode);
++}
++
+ static void
+ cifs_oplock_break(struct slow_work *work)
+ {
+@@ -2344,6 +2360,8 @@ const struct address_space_operations cifs_addr_ops = {
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
+       .set_page_dirty = __set_page_dirty_nobuffers,
++      .releasepage = cifs_release_page,
++      .invalidatepage = cifs_invalidate_page,
+       /* .sync_page = cifs_sync_page, */
+       /* .direct_IO = */
+ };
+@@ -2360,6 +2378,8 @@ const struct address_space_operations cifs_addr_ops_smallbuf = {
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
+       .set_page_dirty = __set_page_dirty_nobuffers,
++      .releasepage = cifs_release_page,
++      .invalidatepage = cifs_invalidate_page,
+       /* .sync_page = cifs_sync_page, */
+       /* .direct_IO = */
+ };
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index ddfd355..c09d3b8 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -130,3 +130,29 @@ void cifs_fscache_reset_inode_cookie(struct inode *inode)
+       }
+ }
++int cifs_fscache_release_page(struct page *page, gfp_t gfp)
++{
++      if (PageFsCache(page)) {
++              struct inode *inode = page->mapping->host;
++              struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++              cFYI(1, "CIFS: fscache release page (0x%p/0x%p)\n",
++                              cifsi->fscache, page);
++              if (!fscache_maybe_release_page(cifsi->fscache, page, gfp))
++                      return 0;
++      }
++
++      return 1;
++}
++
++void __cifs_fscache_invalidate_page(struct page *page, struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct fscache_cookie *cookie = cifsi->fscache;
++
++      cFYI(1, "CIFS: fscache invalidatepage (0x%p/0x%p/0x%p)\n",
++                      cookie, page, cifsi);
++      fscache_wait_on_page_write(cookie, page);
++      fscache_uncache_page(cookie, page);
++}
++
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index 836bb02..127cb0a 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -47,6 +47,16 @@ extern void cifs_fscache_release_inode_cookie(struct inode *);
+ extern void cifs_fscache_set_inode_cookie(struct inode *, struct file *);
+ extern void cifs_fscache_reset_inode_cookie(struct inode *);
++extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
++extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++
++static inline void cifs_fscache_invalidate_page(struct page *page,
++                                             struct inode *inode)
++{
++      if (PageFsCache(page))
++              __cifs_fscache_invalidate_page(page, inode);
++}
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -63,7 +73,13 @@ static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {}
+ static inline void cifs_fscache_set_inode_cookie(struct inode *inode,
+                       struct file *filp) {}
+ static inline void cifs_fscache_reset_inode_cookie(struct inode *inode) {}
++static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
++{
++      return 1; /* May release page */
++}
++static inline int cifs_fscache_invalidate_page(struct page *page,
++                      struct inode *) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001736:2, b/test/corpora/lkml/cur/1382298587.001736:2,
new file mode 100644 (file)
index 0000000..f972891
--- /dev/null
@@ -0,0 +1,256 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 09/10] cifs: read pages from FS-Cache
+Date: Tue, 22 Jun 2010 20:54:21 +0530
+Lines: 219
+Message-ID: <1277220261-3717-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:44:46 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5eX-0008O2-Q4
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:44:46 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752563Ab0FVPom (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:42 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:42741 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752542Ab0FVPok (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:40 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:24:22 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001762>
+
+Read pages from a FS-Cache data storage object into a CIFS inode.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/file.c    |   19 ++++++++++++++
+ fs/cifs/fscache.c |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h |   40 ++++++++++++++++++++++++++++-
+ 3 files changed, 131 insertions(+), 1 deletions(-)
+
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 39c1ce0..42d2f25 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -1978,6 +1978,16 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+       cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
+       pTcon = cifs_sb->tcon;
++      /*
++       * Reads as many pages as possible from fscache. Returns -ENOBUFS
++       * immediately if the cookie is negative
++       */
++      rc = cifs_readpages_from_fscache(mapping->host, mapping, page_list,
++                                       &num_pages);
++      cFYI(1, "CIFS: readpages_from_fscache returned %d\n", rc);
++      if (rc == 0)
++              goto read_complete;
++
+       cFYI(DBG2, "rpages: num pages %d", num_pages);
+       for (i = 0; i < num_pages; ) {
+               unsigned contig_pages;
+@@ -2090,6 +2100,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+               smb_read_data = NULL;
+       }
++read_complete:
+       FreeXid(xid);
+       return rc;
+ }
+@@ -2100,6 +2111,12 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+       char *read_data;
+       int rc;
++      /* Is the page cached? */
++      rc = cifs_readpage_from_fscache(file->f_path.dentry->d_inode, page);
++      cFYI(1, "CIFS: cifs_readpage_from_fscache returned %d\n", rc);
++      if (rc == 0)
++              goto read_complete;
++
+       page_cache_get(page);
+       read_data = kmap(page);
+       /* for reads over a certain size could initiate async read ahead */
+@@ -2128,6 +2145,8 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+ io_error:
+       kunmap(page);
+       page_cache_release(page);
++
++read_complete:
+       return rc;
+ }
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index 13e47d5..6813737 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -145,6 +145,79 @@ int cifs_fscache_release_page(struct page *page, gfp_t gfp)
+       return 1;
+ }
++static void cifs_readpage_from_fscache_complete(struct page *page, void *ctx,
++                                              int error)
++{
++      cFYI(1, "CFS: readpage_from_fscache_complete (0x%p/%d)\n",
++                      page, error);
++      if (!error)
++              SetPageUptodate(page);
++      unlock_page(page);
++}
++
++/*
++ * Retrieve a page from FS-Cache
++ */
++int __cifs_readpage_from_fscache(struct inode *inode, struct page *page)
++{
++      int ret;
++
++      cFYI(1, "CIFS: readpage_from_fscache(fsc:%p, p:%p, i:0x%p\n",
++                      CIFS_I(inode)->fscache, page, inode);
++      ret = fscache_read_or_alloc_page(CIFS_I(inode)->fscache, page,
++                                       cifs_readpage_from_fscache_complete,
++                                       NULL,
++                                       GFP_KERNEL);
++      switch (ret) {
++
++      case 0: /* page found in fscache, read submitted */
++              cFYI(1, "CIFS: readpage_from_fscache: submitted\n");
++              return ret;
++      case -ENOBUFS:  /* page won't be cached */
++      case -ENODATA:  /* page not in cache */
++              cFYI(1, "CIFS: readpage_from_fscache %d\n", ret);
++              return 1;
++
++      default:
++              cFYI(1, "unknown error ret = %d", ret);
++      }
++      return ret;
++}
++
++/*
++ * Retrieve a set of pages from FS-Cache
++ */
++int __cifs_readpages_from_fscache(struct inode *inode,
++                              struct address_space *mapping,
++                              struct list_head *pages,
++                              unsigned *nr_pages)
++{
++      int ret;
++
++      cFYI(1, "CIFS: __cifs_readpages_from_fscache (0x%p/%u/0x%p)\n",
++                      CIFS_I(inode)->fscache, *nr_pages, inode);
++      ret = fscache_read_or_alloc_pages(CIFS_I(inode)->fscache, mapping,
++                                        pages, nr_pages,
++                                        cifs_readpage_from_fscache_complete,
++                                        NULL,
++                                        mapping_gfp_mask(mapping));
++      switch (ret) {
++      case 0: /* read submitted to the cache for all pages */
++              cFYI(1, "CIFS: readpages_from_fscache\n");
++              return ret;
++
++      case -ENOBUFS:  /* some pages are not cached and can't be */
++      case -ENODATA:  /* some pages are not cached */
++              cFYI(1, "CIFS: readpages_from_fscache: no page\n");
++              return 1;
++
++      default:
++              cFYI(1, "unknown error ret = %d", ret);
++      }
++
++      return ret;
++}
++
+ void __cifs_readpage_to_fscache(struct inode *inode, struct page *page)
+ {
+       int ret;
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index e34d8ab..03bd3fe 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -31,7 +31,6 @@ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_super_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_inode_object_def;
+-
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -49,6 +48,11 @@ extern void cifs_fscache_reset_inode_cookie(struct inode *);
+ extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
+ extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++extern int __cifs_readpage_from_fscache(struct inode *, struct page *);
++extern int __cifs_readpages_from_fscache(struct inode *,
++                                       struct address_space *,
++                                       struct list_head *,
++                                       unsigned *);
+ extern void __cifs_readpage_to_fscache(struct inode *, struct page *);
+@@ -59,6 +63,26 @@ static inline void cifs_fscache_invalidate_page(struct page *page,
+               __cifs_fscache_invalidate_page(page, inode);
+ }
++static inline int cifs_readpage_from_fscache(struct inode *inode,
++                                           struct page *page)
++{
++      if (CIFS_I(inode)->fscache)
++              return __cifs_readpage_from_fscache(inode, page);
++
++      return -ENOBUFS;
++}
++
++static inline int cifs_readpages_from_fscache(struct inode *inode,
++                                            struct address_space *mapping,
++                                            struct list_head *pages,
++                                            unsigned *nr_pages)
++{
++      if (CIFS_I(inode)->fscache)
++              return __cifs_readpages_from_fscache(inode, mapping, pages,
++                                                   nr_pages);
++      return -ENOBUFS;
++}
++
+ static inline void cifs_readpage_to_fscache(struct inode *inode,
+                                           struct page *page)
+ {
+@@ -89,6 +113,20 @@ static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
+ static inline int cifs_fscache_invalidate_page(struct page *page,
+                       struct inode *) {}
++static inline int
++cifs_readpage_from_fscache(struct inode *inode, struct page *page)
++{
++      return -ENOBUFS;
++}
++
++static inline int cifs_readpages_from_fscache(struct inode *inode,
++                                            struct address_space *mapping,
++                                            struct list_head *pages,
++                                            unsigned *nr_pages)
++{
++      return -ENOBUFS;
++}
++
+ static inline void cifs_readpage_to_fscache(struct inode *inode,
+                       struct page *page) {}
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001738:2, b/test/corpora/lkml/cur/1382298587.001738:2,
new file mode 100644 (file)
index 0000000..b1e0edf
--- /dev/null
@@ -0,0 +1,139 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 08/10] cifs: store pages into local cache
+Date: Tue, 22 Jun 2010 20:54:00 +0530
+Lines: 102
+Message-ID: <1277220240-3674-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-fsdevel-owner@vger.kernel.org Tue Jun 22 17:45:09 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OR5ev-00007O-6e
+       for lnx-linux-fsdevel@lo.gmane.org; Tue, 22 Jun 2010 17:45:09 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755015Ab0FVPon (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:43 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:58250 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751265Ab0FVPok (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:40 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:24:02 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001764>
+
+Store pages from an CIFS inode into the data storage object associated with
+that inode.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/file.c    |    6 ++++++
+ fs/cifs/fscache.c |   13 +++++++++++++
+ fs/cifs/fscache.h |   11 +++++++++++
+ 3 files changed, 30 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 786ec04..39c1ce0 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -2060,6 +2060,8 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+                                  we will hit it on next read */
+                               /* break; */
++                              /* send this page to FS-Cache */
++                              cifs_readpage_to_fscache(mapping->host, page);
+                       }
+               } else {
+                       cFYI(1, "No bytes read (%d) at offset %lld . "
+@@ -2117,6 +2119,10 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+       flush_dcache_page(page);
+       SetPageUptodate(page);
++
++      /* send this page to the cache */
++      cifs_readpage_to_fscache(file->f_path.dentry->d_inode, page);
++
+       rc = 0;
+ io_error:
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index c09d3b8..13e47d5 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -145,6 +145,19 @@ int cifs_fscache_release_page(struct page *page, gfp_t gfp)
+       return 1;
+ }
++void __cifs_readpage_to_fscache(struct inode *inode, struct page *page)
++{
++      int ret;
++
++      cFYI(1, "CIFS: readpage_to_fscache(fsc: %p, p: %p, i: %p\n",
++                      CIFS_I(inode)->fscache, page, inode);
++      ret = fscache_write_page(CIFS_I(inode)->fscache, page, GFP_KERNEL);
++      cFYI(1, "CIFS: fscache_write_page returned %d\n", ret);
++
++      if (ret != 0)
++              fscache_uncache_page(CIFS_I(inode)->fscache, page);
++}
++
+ void __cifs_fscache_invalidate_page(struct page *page, struct inode *inode)
+ {
+       struct cifsInodeInfo *cifsi = CIFS_I(inode);
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index 127cb0a..e34d8ab 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -50,6 +50,8 @@ extern void cifs_fscache_reset_inode_cookie(struct inode *);
+ extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
+ extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++extern void __cifs_readpage_to_fscache(struct inode *, struct page *);
++
+ static inline void cifs_fscache_invalidate_page(struct page *page,
+                                              struct inode *inode)
+ {
+@@ -57,6 +59,13 @@ static inline void cifs_fscache_invalidate_page(struct page *page,
+               __cifs_fscache_invalidate_page(page, inode);
+ }
++static inline void cifs_readpage_to_fscache(struct inode *inode,
++                                          struct page *page)
++{
++      if (PageFsCache(page))
++              __cifs_readpage_to_fscache(inode, page);
++}
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -80,6 +89,8 @@ static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
+ static inline int cifs_fscache_invalidate_page(struct page *page,
+                       struct inode *) {}
++static inline void cifs_readpage_to_fscache(struct inode *inode,
++                      struct page *page) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+-- 
+1.6.4.2
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001739:2, b/test/corpora/lkml/cur/1382298587.001739:2,
new file mode 100644 (file)
index 0000000..d0abda0
--- /dev/null
@@ -0,0 +1,355 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Tue, 22 Jun 2010 20:53:33 +0530
+Lines: 318
+Message-ID: <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:45:30 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5fF-0000Ka-Na
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:45:30 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755952Ab0FVPpP (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:45:15 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:59441 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751397Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:35 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001765>
+
+Define inode-level data storage objects (managed by cifsInodeInfo structs).
+Each inode-level object is created in a super-block level object and is itself
+a data storage object in to which pages from the inode are stored.
+
+The inode object is keyed by UniqueId. The coherency data being used is
+LastWriteTime and the file size.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cache.c    |   80 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsfs.c   |    7 ++++
+ fs/cifs/cifsglob.h |    3 +
+ fs/cifs/file.c     |    6 +++
+ fs/cifs/fscache.c  |   68 +++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h  |   12 +++++++
+ fs/cifs/inode.c    |    4 ++
+ 7 files changed, 180 insertions(+)
+
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -138,3 +138,83 @@ const struct fscache_cookie_def cifs_fsc
+       .get_key = cifs_super_get_key,
+ };
++/*
++ * Auxiliary data attached to CIFS inode within the cache
++ */
++struct cifs_fscache_inode_auxdata {
++      struct timespec last_write_time;
++      loff_t          size;
++};
++
++static uint16_t cifs_fscache_inode_get_key(const void *cookie_netfs_data,
++                                         void *buffer, uint16_t maxbuf)
++{
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++      uint16_t keylen;
++
++      /* use the UniqueId as the key */
++      keylen = sizeof(cifsi->uniqueid);
++      if (keylen > maxbuf)
++              keylen = 0;
++      else
++              memcpy(buffer, &cifsi->uniqueid, keylen);
++
++      return keylen;
++}
++
++static void
++cifs_fscache_inode_get_attr(const void *cookie_netfs_data, uint64_t *size)
++{
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      *size = cifsi->vfs_inode.i_size;
++}
++
++static uint16_t
++cifs_fscache_inode_get_aux(const void *cookie_netfs_data, void *buffer,
++                         uint16_t maxbuf)
++{
++      struct cifs_fscache_inode_auxdata auxdata;
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      memset(&auxdata, 0, sizeof(auxdata));
++      auxdata.size = cifsi->vfs_inode.i_size;
++      auxdata.last_write_time = cifsi->vfs_inode.i_ctime;
++
++      if (maxbuf > sizeof(auxdata))
++              maxbuf = sizeof(auxdata);
++
++      memcpy(buffer, &auxdata, maxbuf);
++
++      return maxbuf;
++}
++
++static enum
++fscache_checkaux cifs_fscache_inode_check_aux(void *cookie_netfs_data,
++                                            const void *data,
++                                            uint16_t datalen)
++{
++      struct cifs_fscache_inode_auxdata auxdata;
++      struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      if (datalen != sizeof(auxdata))
++              return FSCACHE_CHECKAUX_OBSOLETE;
++
++      memset(&auxdata, 0, sizeof(auxdata));
++      auxdata.size = cifsi->vfs_inode.i_size;
++      auxdata.last_write_time = cifsi->vfs_inode.i_ctime;
++
++      if (memcmp(data, &auxdata, datalen) != 0)
++              return FSCACHE_CHECKAUX_OBSOLETE;
++
++      return FSCACHE_CHECKAUX_OKAY;
++}
++
++const struct fscache_cookie_def cifs_fscache_inode_object_def = {
++      .name           = "CIFS.uniqueid",
++      .type           = FSCACHE_COOKIE_TYPE_DATAFILE,
++      .get_key        = cifs_fscache_inode_get_key,
++      .get_attr       = cifs_fscache_inode_get_attr,
++      .get_aux        = cifs_fscache_inode_get_aux,
++      .check_aux      = cifs_fscache_inode_check_aux,
++};
+Index: cifs-2.6/fs/cifs/cifsfs.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsfs.c
++++ cifs-2.6/fs/cifs/cifsfs.c
+@@ -330,6 +330,12 @@ cifs_destroy_inode(struct inode *inode)
+ }
+ static void
++cifs_clear_inode(struct inode *inode)
++{
++      cifs_fscache_release_inode_cookie(inode);
++}
++
++static void
+ cifs_show_address(struct seq_file *s, struct TCP_Server_Info *server)
+ {
+       seq_printf(s, ",addr=");
+@@ -490,6 +496,7 @@ static const struct super_operations cif
+       .alloc_inode = cifs_alloc_inode,
+       .destroy_inode = cifs_destroy_inode,
+       .drop_inode     = cifs_drop_inode,
++      .clear_inode    = cifs_clear_inode,
+ /*    .delete_inode   = cifs_delete_inode,  */  /* Do not need above
+       function unless later we add lazy close of inodes or unless the
+       kernel forgets to call us with the same number of releases (closes)
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -407,6 +407,9 @@ struct cifsInodeInfo {
+       bool invalid_mapping:1;         /* pagecache is invalid */
+       u64  server_eof;                /* current file size on server */
+       u64  uniqueid;                  /* server inode number */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie *fscache;
++#endif
+       struct inode vfs_inode;
+ };
+Index: cifs-2.6/fs/cifs/file.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/file.c
++++ cifs-2.6/fs/cifs/file.c
+@@ -40,6 +40,7 @@
+ #include "cifs_unicode.h"
+ #include "cifs_debug.h"
+ #include "cifs_fs_sb.h"
++#include "fscache.h"
+ static inline int cifs_convert_flags(unsigned int flags)
+ {
+@@ -282,6 +283,9 @@ int cifs_open(struct inode *inode, struc
+                               CIFSSMBClose(xid, tcon, netfid);
+                               rc = -ENOMEM;
+                       }
++
++                      cifs_fscache_set_inode_cookie(inode, file);
++
+                       goto out;
+               } else if ((rc == -EINVAL) || (rc == -EOPNOTSUPP)) {
+                       if (tcon->ses->serverNOS)
+@@ -373,6 +377,8 @@ int cifs_open(struct inode *inode, struc
+               goto out;
+       }
++      cifs_fscache_set_inode_cookie(inode, file);
++
+       if (oplock & CIFS_CREATE_ACTION) {
+               /* time to set mode which we can not set earlier due to
+                  problems creating new read-only files */
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.c
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -62,3 +62,71 @@ void cifs_fscache_release_super_cookie(s
+       tcon->fscache = NULL;
+ }
++static void cifs_fscache_enable_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
++
++      if (cifsi->fscache)
++              return;
++
++      cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache,
++                              &cifs_fscache_inode_object_def,
++                              cifsi);
++      cFYI(1, "CIFS: got FH cookie (0x%p/0x%p/0x%p)\n",
++                      cifs_sb->tcon, cifsi, cifsi->fscache);
++}
++
++void cifs_fscache_release_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++      if (cifsi->fscache) {
++              cFYI(1, "CIFS releasing inode cookie (0x%p/0x%p)\n",
++                              cifsi, cifsi->fscache);
++              fscache_relinquish_cookie(cifsi->fscache, 0);
++              cifsi->fscache = NULL;
++      }
++}
++
++static void cifs_fscache_disable_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++      if (cifsi->fscache) {
++              cFYI(1, "CIFS disabling inode cookie (0x%p/0x%p)\n",
++                              cifsi, cifsi->fscache);
++              fscache_relinquish_cookie(cifsi->fscache, 1);
++              cifsi->fscache = NULL;
++      }
++}
++
++void cifs_fscache_set_inode_cookie(struct inode *inode, struct file *filp)
++{
++      /* BB: parallel opens - need locking? */
++      if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
++              cifs_fscache_disable_inode_cookie(inode);
++      else {
++              cifs_fscache_enable_inode_cookie(inode);
++              cFYI(1, "CIFS: fscache inode cookie set\n");
++      }
++}
++
++void cifs_fscache_reset_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
++      struct fscache_cookie *old = cifsi->fscache;
++
++      if (cifsi->fscache) {
++              /* retire the current fscache cache and get a new one */
++              fscache_relinquish_cookie(cifsi->fscache, 1);
++
++              cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache,
++                                      &cifs_fscache_inode_object_def,
++                                      cifsi);
++              cFYI(1, "CIFS: new cookie (0x%p/0x%p) oldcookie 0x%p\n",
++                              cifsi, cifsi->fscache, old);
++      }
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -29,6 +29,8 @@
+ extern struct fscache_netfs cifs_fscache_netfs;
+ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_super_index_def;
++extern const struct fscache_cookie_def cifs_fscache_inode_object_def;
++
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -41,6 +43,10 @@ extern void cifs_fscache_release_client_
+ extern void cifs_fscache_get_super_cookie(struct cifsTconInfo *);
+ extern void cifs_fscache_release_super_cookie(struct cifsTconInfo *);
++extern void cifs_fscache_release_inode_cookie(struct inode *);
++extern void cifs_fscache_set_inode_cookie(struct inode *, struct file *);
++extern void cifs_fscache_reset_inode_cookie(struct inode *);
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -53,6 +59,12 @@ static inline void cifs_fscache_get_supe
+ static inline void
+ cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) {}
++static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {}
++static inline void cifs_fscache_set_inode_cookie(struct inode *inode,
++                      struct file *filp) {}
++static inline void cifs_fscache_reset_inode_cookie(struct inode *inode) {}
++
++
+ #endif /* CONFIG_CIFS_FSCACHE */
+ #endif /* _CIFS_FSCACHE_H */
+Index: cifs-2.6/fs/cifs/inode.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/inode.c
++++ cifs-2.6/fs/cifs/inode.c
+@@ -29,6 +29,7 @@
+ #include "cifsproto.h"
+ #include "cifs_debug.h"
+ #include "cifs_fs_sb.h"
++#include "fscache.h"
+ static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral)
+@@ -776,6 +777,8 @@ retry_iget5_locked:
+                       inode->i_flags |= S_NOATIME | S_NOCMTIME;
+               if (inode->i_state & I_NEW) {
+                       inode->i_ino = hash;
++                      /* initialize per-inode cache cookie pointer */
++                      CIFS_I(inode)->fscache = NULL;
+                       unlock_new_inode(inode);
+               }
+       }
+@@ -1568,6 +1571,7 @@ cifs_invalidate_mapping(struct inode *in
+                       cifs_i->write_behind_rc = rc;
+       }
+       invalidate_remote_inode(inode);
++      cifs_fscache_reset_inode_cookie(inode);
+ }
+ int cifs_revalidate_file(struct file *filp)
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001740:2, b/test/corpora/lkml/cur/1382298587.001740:2,
new file mode 100644 (file)
index 0000000..ef0f657
--- /dev/null
@@ -0,0 +1,214 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Tue, 22 Jun 2010 20:53:26 +0530
+Lines: 177
+Message-ID: <1277220206-3559-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:45:50 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5fZ-0000Vj-Mj
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:45:50 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752511Ab0FVPpJ (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:45:09 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:56189 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752441Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:29 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001766>
+
+Define superblock-level cache index objects (managed by cifsTconInfo structs).
+Each superblock object is created in a server-level index object and in itself
+an index into which inode-level objects are inserted.
+
+Currently, the superblock objects are keyed by sharename.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/cache.c    |   62 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsglob.h |    3 ++
+ fs/cifs/connect.c  |    4 +++
+ fs/cifs/fscache.c  |   17 ++++++++++++++
+ fs/cifs/fscache.h  |    6 +++++
+ 5 files changed, 92 insertions(+)
+
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -76,3 +76,65 @@ const struct fscache_cookie_def cifs_fsc
+       .type = FSCACHE_COOKIE_TYPE_INDEX,
+       .get_key = cifs_server_get_key,
+ };
++
++static char *extract_sharename(const char *treename)
++{
++      const char *src;
++      char *delim, *dst;
++      int len;
++
++      /* skip double chars at the beginning */
++      src = treename + 2;
++
++      /* share name is always preceded by '\\' now */
++      delim = strchr(src, '\\');
++      if (!delim)
++              return ERR_PTR(-EINVAL);
++      delim++;
++      len = strlen(delim);
++
++      /* caller has to free the memory */
++      dst = kstrndup(delim, len, GFP_KERNEL);
++      if (!dst)
++              return ERR_PTR(-ENOMEM);
++
++      return dst;
++}
++
++/*
++ * Superblock object currently keyed by share name
++ */
++static uint16_t cifs_super_get_key(const void *cookie_netfs_data, void *buffer,
++                                 uint16_t maxbuf)
++{
++      const struct cifsTconInfo *tcon = cookie_netfs_data;
++      char *sharename;
++      uint16_t len;
++
++      sharename = extract_sharename(tcon->treeName);
++      if (IS_ERR(sharename)) {
++              cFYI(1, "CIFS: couldn't extract sharename\n");
++              sharename = NULL;
++              return 0;
++      }
++
++      len = strlen(sharename);
++      if (len > maxbuf)
++              return 0;
++
++      memcpy(buffer, sharename, len);
++
++      kfree(sharename);
++
++      return len;
++}
++
++/*
++ * Superblock object for FS-Cache
++ */
++const struct fscache_cookie_def cifs_fscache_super_index_def = {
++      .name = "CIFS.super",
++      .type = FSCACHE_COOKIE_TYPE_INDEX,
++      .get_key = cifs_super_get_key,
++};
++
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -317,6 +317,9 @@ struct cifsTconInfo {
+       bool local_lease:1; /* check leases (only) on local system not remote */
+       bool broken_posix_open; /* e.g. Samba server versions < 3.3.2, 3.2.9 */
+       bool need_reconnect:1; /* connection reset, tid now invalid */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie *fscache; /* cookie for share */
++#endif
+       /* BB add field for back pointer to sb struct(s)? */
+ };
+Index: cifs-2.6/fs/cifs/connect.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/connect.c
++++ cifs-2.6/fs/cifs/connect.c
+@@ -1773,6 +1773,8 @@ cifs_put_tcon(struct cifsTconInfo *tcon)
+       list_del_init(&tcon->tcon_list);
+       write_unlock(&cifs_tcp_ses_lock);
++      cifs_fscache_release_super_cookie(tcon);
++
+       xid = GetXid();
+       CIFSSMBTDis(xid, tcon);
+       _FreeXid(xid);
+@@ -1843,6 +1845,8 @@ cifs_get_tcon(struct cifsSesInfo *ses, s
+       tcon->nocase = volume_info->nocase;
+       tcon->local_lease = volume_info->local_lease;
++      cifs_fscache_get_super_cookie(tcon);
++
+       write_lock(&cifs_tcp_ses_lock);
+       list_add(&tcon->tcon_list, &ses->tcon_list);
+       write_unlock(&cifs_tcp_ses_lock);
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.c
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -45,3 +45,20 @@ void cifs_fscache_release_client_cookie(
+       server->fscache = NULL;
+ }
++void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon)
++{
++      tcon->fscache =
++              fscache_acquire_cookie(tcon->ses->server->fscache,
++                              &cifs_fscache_super_index_def, tcon);
++      cFYI(1, "CIFS: get superblock cookie (0x%p/0x%p)\n",
++                              tcon, tcon->fscache);
++}
++
++void cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon)
++{
++      cFYI(1, "CIFS: releasing superblock cookie (0x%p/0x%p)\n",
++                      tcon, tcon->fscache);
++      fscache_relinquish_cookie(tcon->fscache, 0);
++      tcon->fscache = NULL;
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -28,6 +28,7 @@
+ extern struct fscache_netfs cifs_fscache_netfs;
+ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
++extern const struct fscache_cookie_def cifs_fscache_super_index_def;
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -37,6 +38,8 @@ extern void cifs_fscache_unregister(void
+  */
+ extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
+ extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
++extern void cifs_fscache_get_super_cookie(struct cifsTconInfo *);
++extern void cifs_fscache_release_super_cookie(struct cifsTconInfo *);
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+@@ -46,6 +49,9 @@ static inline void
+ cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
+ static inline void
+ cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
++static inline void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon) {}
++static inline void
++cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001887:2, b/test/corpora/lkml/cur/1382298587.001887:2,
new file mode 100644 (file)
index 0000000..8129048
--- /dev/null
@@ -0,0 +1,85 @@
+From: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+Subject: Re: [RFC][PATCH 02/10] cifs: guard cifsglob.h against multiple
+ inclusion
+Date: Tue, 22 Jun 2010 17:37:42 -0400
+Lines: 35
+Message-ID: <20100622173742.448e1e94@corrin.poochiereds.net>
+References: <yes>
+       <1277220170-3442-1-git-send-email-sjayaraman@suse.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 23:36:08 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORB8Z-00027v-R8
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 23:36:08 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751663Ab0FVVfq (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 17:35:46 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.121]:46190 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751933Ab0FVVfo (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Tue, 22 Jun 2010 17:35:44 -0400
+X-Authority-Analysis: v=1.0 c=1 a=Y4kVDsoNLLAA:10 a=yQWWgrYGNuUA:10 a=kj9zAlcOel0A:10 a=hGzw-44bAAAA:8 a=6UT2YofcClCzWf3PPoQA:9 a=Ipo6nwFRv7ENfF13HvmH_iG48b8A:4 a=CjuIK1q_8ugA:10 a=0kPLrQdw3YYA:10 a=dowx1zmaLagA:10
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:49036] helo=mail.poochiereds.net)
+       by cdptpa-oedge01.mail.rr.com (envelope-from <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id 29/22-24471-DAC212C4; Tue, 22 Jun 2010 21:35:42 +0000
+Received: from corrin.poochiereds.net (unknown [65.88.2.5])
+       by mail.poochiereds.net (Postfix) with ESMTPSA id 1C5A1580F4;
+       Tue, 22 Jun 2010 17:35:41 -0400 (EDT)
+In-Reply-To: <1277220170-3442-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001913>
+
+On Tue, 22 Jun 2010 20:52:50 +0530
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Add conditional compile macros to guard the header file against multiple
+> inclusion.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> ---
+>  fs/cifs/cifsglob.h |    5 +++++
+>  1 files changed, 5 insertions(+), 0 deletions(-)
+> 
+> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
+> index a88479c..6b2c39d 100644
+> --- a/fs/cifs/cifsglob.h
+> +++ b/fs/cifs/cifsglob.h
+> @@ -16,6 +16,9 @@
+>   *   the GNU Lesser General Public License for more details.
+>   *
+>   */
+> +#ifndef _CIFS_GLOB_H
+> +#define _CIFS_GLOB_H
+> +
+>  #include <linux/in.h>
+>  #include <linux/in6.h>
+>  #include <linux/slab.h>
+> @@ -733,3 +736,5 @@ GLOBAL_EXTERN unsigned int cifs_min_small;  /* min size of small buf pool */
+>  GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/
+>  
+>  extern const struct slow_work_ops cifs_oplock_break_ops;
+> +
+> +#endif      /* _CIFS_GLOB_H */
+
+Strong ACK
+
+Acked-by: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001892:2, b/test/corpora/lkml/cur/1382298587.001892:2,
new file mode 100644 (file)
index 0000000..82603bf
--- /dev/null
@@ -0,0 +1,254 @@
+From: Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>
+Subject: Re: [RFC][PATCH 04/10] cifs: define server-level cache index
+ objects and register them with FS-Cache
+Date: Tue, 22 Jun 2010 17:52:14 -0400
+Lines: 204
+Message-ID: <20100622175214.4c56234f@corrin.poochiereds.net>
+References: <yes>
+       <1277220198-3522-1-git-send-email-sjayaraman@suse.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 23:50:23 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORBMJ-0005WJ-Lj
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 23:50:20 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1750777Ab0FVVuS (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 17:50:18 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.120]:55670 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1750749Ab0FVVuR (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Tue, 22 Jun 2010 17:50:17 -0400
+X-Authority-Analysis: v=1.1 cv=8MuG1bpxLlSbaYWWtODGdBCK7StbFcRsMXhWm1NVx/I= c=1 sm=0 a=wpY4Lvx3kJcA:10 a=UBIxAjGgU1YA:10 a=kj9zAlcOel0A:10 a=ld/erqUjW76FpBUqCqkKeA==:17 a=VwQbUJbxAAAA:8 a=qYub2k57AAAA:8 a=uYIlwBZcjrF9BUCsR4kA:9 a=OO1ZLbZb6q4TPdC5pcAA:7 a=jFshslHAf8hJVDYUYRlYN4n-w5YA:4 a=CjuIK1q_8ugA:10 a=x8gzFH9gYPwA:10 a=0kPLrQdw3YYA:10 a=jBoGP612-tUA:10 a=t5DF_bUGhurCx8LQ:21 a=W6P_Gh1y2IibdbqZ:21 a=ld/erqUjW76FpBUqCqkKeA==:117
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:59154] helo=mail.poochiereds.net)
+       by cdptpa-oedge03.mail.rr.com (envelope-from <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id AC/10-00502-710312C4; Tue, 22 Jun 2010 21:50:16 +0000
+Received: from corrin.poochiereds.net (unknown [65.88.2.5])
+       by mail.poochiereds.net (Postfix) with ESMTPSA id 03B11580F4;
+       Tue, 22 Jun 2010 17:50:14 -0400 (EDT)
+In-Reply-To: <1277220198-3522-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001918>
+
+On Tue, 22 Jun 2010 20:53:18 +0530
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define server-level cache index objects (as managed by TCP_ServerInfo structs).
+> Each server object is created in the CIFS top-level index object and is itself
+> an index into which superblock-level objects are inserted.
+> 
+> Currently, the server objects are keyed by hostname.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> ---
+>  fs/cifs/Makefile   |    2 +-
+>  fs/cifs/cache.c    |   25 +++++++++++++++++++++++++
+>  fs/cifs/cifsglob.h |    3 +++
+>  fs/cifs/connect.c  |    4 ++++
+>  fs/cifs/fscache.c  |   47 +++++++++++++++++++++++++++++++++++++++++++++++
+>  fs/cifs/fscache.h  |   12 ++++++++++++
+>  6 files changed, 92 insertions(+), 1 deletion(-)
+>  create mode 100644 fs/cifs/fscache.c
+> 
+> Index: cifs-2.6/fs/cifs/Makefile
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/Makefile
+> +++ cifs-2.6/fs/cifs/Makefile
+> @@ -12,4 +12,4 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spneg
+>  
+>  cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
+>  
+> -cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
+> +cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
+> Index: cifs-2.6/fs/cifs/cache.c
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/cache.c
+> +++ cifs-2.6/fs/cifs/cache.c
+> @@ -51,3 +51,28 @@ void cifs_fscache_unregister(void)
+>      fscache_unregister_netfs(&cifs_fscache_netfs);
+>  }
+>  
+> +/*
+> + * Server object currently keyed by hostname
+> + */
+> +static uint16_t cifs_server_get_key(const void *cookie_netfs_data,
+> +                               void *buffer, uint16_t maxbuf)
+> +{
+> +    const struct TCP_Server_Info *server = cookie_netfs_data;
+> +    uint16_t len = strnlen(server->hostname, sizeof(server->hostname));
+> +
+
+Would a tuple of address/family/port be a better choice here? Imagine I
+mount "foo" and then later mount "foor.bar.baz". If they are the same
+address and only the UNC differs, then you won't get the benefit of
+the cache, right?
+
+> +    if (len > maxbuf)
+> +            return 0;
+> +
+> +    memcpy(buffer, server->hostname, len);
+> +
+> +    return len;
+> +}
+> +
+> +/*
+> + * Server object for FS-Cache
+> + */
+> +const struct fscache_cookie_def cifs_fscache_server_index_def = {
+> +    .name = "CIFS.server",
+> +    .type = FSCACHE_COOKIE_TYPE_INDEX,
+> +    .get_key = cifs_server_get_key,
+> +};
+> Index: cifs-2.6/fs/cifs/cifsglob.h
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/cifsglob.h
+> +++ cifs-2.6/fs/cifs/cifsglob.h
+> @@ -193,6 +193,9 @@ struct TCP_Server_Info {
+>      bool    sec_mskerberos;         /* supports legacy MS Kerberos */
+>      bool    sec_kerberosu2u;        /* supports U2U Kerberos */
+>      bool    sec_ntlmssp;            /* supports NTLMSSP */
+> +#ifdef CONFIG_CIFS_FSCACHE
+> +    struct fscache_cookie   *fscache; /* client index cache cookie */
+> +#endif
+>  };
+>  
+>  /*
+> Index: cifs-2.6/fs/cifs/connect.c
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/connect.c
+> +++ cifs-2.6/fs/cifs/connect.c
+> @@ -48,6 +48,7 @@
+>  #include "nterr.h"
+>  #include "rfc1002pdu.h"
+>  #include "cn_cifs.h"
+> +#include "fscache.h"
+>  
+>  #define CIFS_PORT 445
+>  #define RFC1001_PORT 139
+> @@ -1453,6 +1454,8 @@ cifs_put_tcp_session(struct TCP_Server_I
+>              return;
+>      }
+>  
+> +    cifs_fscache_release_client_cookie(server);
+> +
+>      list_del_init(&server->tcp_ses_list);
+>      write_unlock(&cifs_tcp_ses_lock);
+>  
+> @@ -1572,6 +1575,7 @@ cifs_get_tcp_session(struct smb_vol *vol
+>              goto out_err;
+>      }
+>  
+> +    cifs_fscache_get_client_cookie(tcp_ses);
+>      /* thread spawned, put it on the list */
+>      write_lock(&cifs_tcp_ses_lock);
+>      list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
+> Index: cifs-2.6/fs/cifs/fscache.c
+> ===================================================================
+> --- /dev/null
+> +++ cifs-2.6/fs/cifs/fscache.c
+> @@ -0,0 +1,47 @@
+> +/*
+> + *   fs/cifs/fscache.c - CIFS filesystem cache interface
+> + *
+> + *   Copyright (c) 2010 Novell, Inc.
+> + *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> + *
+> + *   This library is free software; you can redistribute it and/or modify
+> + *   it under the terms of the GNU Lesser General Public License as published
+> + *   by the Free Software Foundation; either version 2.1 of the License, or
+> + *   (at your option) any later version.
+> + *
+> + *   This library 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 Lesser General Public License for more details.
+> + *
+> + *   You should have received a copy of the GNU Lesser General Public License
+> + *   along with this library; if not, write to the Free Software
+> + *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+> + */
+> +#include <linux/init.h>
+> +#include <linux/kernel.h>
+> +#include <linux/sched.h>
+> +#include <linux/mm.h>
+> +#include <linux/in6.h>
+> +
+> +#include "fscache.h"
+> +#include "cifsglob.h"
+> +#include "cifs_debug.h"
+> +
+> +void cifs_fscache_get_client_cookie(struct TCP_Server_Info *server)
+> +{
+> +    server->fscache =
+> +            fscache_acquire_cookie(cifs_fscache_netfs.primary_index,
+> +                            &cifs_fscache_server_index_def, server);
+> +    cFYI(1, "CIFS: get client cookie (0x%p/0x%p)\n",
+> +                            server, server->fscache);
+> +}
+> +
+> +void cifs_fscache_release_client_cookie(struct TCP_Server_Info *server)
+> +{
+> +    cFYI(1, "CIFS: release client cookie (0x%p/0x%p)\n",
+> +                            server, server->fscache);
+> +    fscache_relinquish_cookie(server->fscache, 0);
+> +    server->fscache = NULL;
+> +}
+> +
+> Index: cifs-2.6/fs/cifs/fscache.h
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/fscache.h
+> +++ cifs-2.6/fs/cifs/fscache.h
+> @@ -27,14 +27,26 @@
+>  #ifdef CONFIG_CIFS_FSCACHE
+>  
+>  extern struct fscache_netfs cifs_fscache_netfs;
+> +extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+>  
+>  extern int cifs_fscache_register(void);
+>  extern void cifs_fscache_unregister(void);
+>  
+> +/*
+> + * fscache.c
+> + */
+> +extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
+> +extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
+> +
+>  #else /* CONFIG_CIFS_FSCACHE */
+>  static inline int cifs_fscache_register(void) { return 0; }
+>  static inline void cifs_fscache_unregister(void) {}
+>  
+> +static inline void
+> +cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
+> +static inline void
+> +cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
+> +
+>  #endif /* CONFIG_CIFS_FSCACHE */
+>  
+>  #endif /* _CIFS_FSCACHE_H */
+> --
+> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
+> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+> More majordomo info at  http://vger.kernel.org/majordomo-info.html
+> 
+
+
+-- 
+Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298587.001970:2, b/test/corpora/lkml/cur/1382298587.001970:2,
new file mode 100644 (file)
index 0000000..707d0ad
--- /dev/null
@@ -0,0 +1,103 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 04/10] cifs: define server-level cache index objects
+ and register them with FS-Cache
+Date: Wed, 23 Jun 2010 11:04:39 +0530
+Lines: 61
+Message-ID: <4C219CEF.5000003@suse.de>
+References: <yes>      <1277220198-3522-1-git-send-email-sjayaraman@suse.de> <20100622175214.4c56234f@corrin.poochiereds.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 07:34:50 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORIbp-0002v4-3W
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 07:34:49 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1750954Ab0FWFes (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 01:34:48 -0400
+Received: from cantor2.suse.de ([195.135.220.15]:58263 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1750809Ab0FWFes (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 01:34:48 -0400
+Received: from relay1.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id 8C18386A2E;
+       Wed, 23 Jun 2010 07:34:46 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <20100622175214.4c56234f-4QP7MXygkU+dMjc06nkz3ljfA9RmPOcC@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001998>
+
+On 06/23/2010 03:22 AM, Jeff Layton wrote:
+> On Tue, 22 Jun 2010 20:53:18 +0530
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+>> Define server-level cache index objects (as managed by TCP_ServerInfo structs).
+>> Each server object is created in the CIFS top-level index object and is itself
+>> an index into which superblock-level objects are inserted.
+>>
+>> Currently, the server objects are keyed by hostname.
+>>
+>> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+>> ---
+>>  fs/cifs/Makefile   |    2 +-
+>>  fs/cifs/cache.c    |   25 +++++++++++++++++++++++++
+>>  fs/cifs/cifsglob.h |    3 +++
+>>  fs/cifs/connect.c  |    4 ++++
+>>  fs/cifs/fscache.c  |   47 +++++++++++++++++++++++++++++++++++++++++++++++
+>>  fs/cifs/fscache.h  |   12 ++++++++++++
+>>  6 files changed, 92 insertions(+), 1 deletion(-)
+>>  create mode 100644 fs/cifs/fscache.c
+>>
+>> Index: cifs-2.6/fs/cifs/Makefile
+>> ===================================================================
+>> --- cifs-2.6.orig/fs/cifs/Makefile
+>> +++ cifs-2.6/fs/cifs/Makefile
+>> @@ -12,4 +12,4 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spneg
+>>  
+>>  cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
+>>  
+>> -cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
+>> +cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
+>> Index: cifs-2.6/fs/cifs/cache.c
+>> ===================================================================
+>> --- cifs-2.6.orig/fs/cifs/cache.c
+>> +++ cifs-2.6/fs/cifs/cache.c
+>> @@ -51,3 +51,28 @@ void cifs_fscache_unregister(void)
+>>     fscache_unregister_netfs(&cifs_fscache_netfs);
+>>  }
+>>  
+>> +/*
+>> + * Server object currently keyed by hostname
+>> + */
+>> +static uint16_t cifs_server_get_key(const void *cookie_netfs_data,
+>> +                              void *buffer, uint16_t maxbuf)
+>> +{
+>> +   const struct TCP_Server_Info *server = cookie_netfs_data;
+>> +   uint16_t len = strnlen(server->hostname, sizeof(server->hostname));
+>> +
+> 
+> Would a tuple of address/family/port be a better choice here? Imagine I
+> mount "foo" and then later mount "foor.bar.baz". If they are the same
+> address and only the UNC differs, then you won't get the benefit of
+> the cache, right?
+> 
+
+Good point. I'll fix it up when I do a respin.
+
+Thanks,
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002189:2, b/test/corpora/lkml/cur/1382298587.002189:2,
new file mode 100644 (file)
index 0000000..3cfc62e
--- /dev/null
@@ -0,0 +1,66 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 03/10] cifs: register CIFS for caching
+Date: Wed, 23 Jun 2010 17:51:17 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 20
+Message-ID: <9603.1277311877@redhat.com>
+References: <1277220189-3485-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 18:51:32 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTAg-0008Bt-CT
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 18:51:30 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751915Ab0FWQv3 (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 12:51:29 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50923 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751520Ab0FWQv3 (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 12:51:29 -0400
+Received: from int-mx05.intmail.prod.int.phx2.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.18])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGpLFc028550
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 12:51:21 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx05.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGpHIG010890;
+       Wed, 23 Jun 2010 12:51:18 -0400
+In-Reply-To: <1277220189-3485-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.18
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002219>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> +    rc = cifs_fscache_register();
+> +    if (rc)
+> +            goto out;
+> +
+>      rc = cifs_init_inodecache();
+>      if (rc)
+>              goto out_clean_proc;
+> @@ -949,8 +954,10 @@ init_cifs(void)
+>      cifs_destroy_mids();
+>   out_destroy_inodecache:
+>      cifs_destroy_inodecache();
+> +    cifs_fscache_unregister();
+>   out_clean_proc:
+
+This is incorrect.  You need to call cifs_fscache_unregister() if
+cifs_init_inodecache() fails.
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002193:2, b/test/corpora/lkml/cur/1382298587.002193:2,
new file mode 100644 (file)
index 0000000..e2ea626
--- /dev/null
@@ -0,0 +1,59 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Wed, 23 Jun 2010 17:58:10 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <9720.1277312290@redhat.com>
+References: <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 18:58:19 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTHG-0003Az-Ge
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 18:58:18 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751520Ab0FWQ6R (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 12:58:17 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:62343 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751372Ab0FWQ6R (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 12:58:17 -0400
+Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGwDC2031683
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 12:58:13 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGwAfq021298;
+       Wed, 23 Jun 2010 12:58:11 -0400
+In-Reply-To: <1277220206-3559-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002223>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define superblock-level cache index objects (managed by cifsTconInfo
+> structs).  Each superblock object is created in a server-level index object
+> and in itself an index into which inode-level objects are inserted.
+> 
+> Currently, the superblock objects are keyed by sharename.
+
+Seems reasonable.  Is there any way you can check that the share you are
+looking at on a server is the same as the last time you looked?  Can you
+validate the root directory of the share in some way?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002194:2, b/test/corpora/lkml/cur/1382298587.002194:2,
new file mode 100644 (file)
index 0000000..d2d1efd
--- /dev/null
@@ -0,0 +1,61 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Wed, 23 Jun 2010 18:02:53 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 15
+Message-ID: <9822.1277312573@redhat.com>
+References: <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:03:04 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTLr-0007Bh-Cs
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:03:03 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752063Ab0FWRDB (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:03:01 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:30823 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRDA (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:03:00 -0400
+Received: from int-mx03.intmail.prod.int.phx2.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.16])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH2v0J030982
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:02:57 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx03.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH2r9N014323;
+       Wed, 23 Jun 2010 13:02:54 -0400
+In-Reply-To: <1277220214-3597-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.16
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002224>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define inode-level data storage objects (managed by cifsInodeInfo structs).
+> Each inode-level object is created in a super-block level object and is
+> itself a data storage object in to which pages from the inode are stored.
+> 
+> The inode object is keyed by UniqueId. The coherency data being used is
+> LastWriteTime and the file size.
+
+Isn't there a file creation time too?
+
+I take it you don't support caching on files that are open for writing at this
+time?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002195:2, b/test/corpora/lkml/cur/1382298587.002195:2,
new file mode 100644 (file)
index 0000000..ec54a81
--- /dev/null
@@ -0,0 +1,59 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 07/10] cifs: FS-Cache page management
+Date: Wed, 23 Jun 2010 18:05:01 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <9866.1277312701@redhat.com>
+References: <1277220228-3635-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Wed Jun 23 19:05:19 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1ORTNz-0008Oj-Ho
+       for lnx-linux-fsdevel@lo.gmane.org; Wed, 23 Jun 2010 19:05:15 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752145Ab0FWRFO (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Wed, 23 Jun 2010 13:05:14 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:1689 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRFN (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Wed, 23 Jun 2010 13:05:13 -0400
+Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH59sl011966
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:05:09 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH52Jl022163;
+       Wed, 23 Jun 2010 13:05:03 -0400
+In-Reply-To: <1277220228-3635-1-git-send-email-sjayaraman@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002225>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Takes care of invalidation and release of FS-Cache marked pages and also
+> invalidation of the FsCache page flag when the inode is removed.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+
+Acked-by: David Howells <dhowells@redhat.com>
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002196:2, b/test/corpora/lkml/cur/1382298587.002196:2,
new file mode 100644 (file)
index 0000000..63838dc
--- /dev/null
@@ -0,0 +1,54 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 08/10] cifs: store pages into local cache
+Date: Wed, 23 Jun 2010 18:06:12 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 8
+Message-ID: <9890.1277312772@redhat.com>
+References: <1277220240-3674-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:06:21 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTP3-0000fp-01
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:06:21 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752403Ab0FWRGU (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:06:20 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:63621 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRGT (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:06:19 -0400
+Received: from int-mx08.intmail.prod.int.phx2.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.21])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH6FCB012081
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:06:15 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx08.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH6CKG013414;
+       Wed, 23 Jun 2010 13:06:13 -0400
+In-Reply-To: <1277220240-3674-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.21
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002226>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Store pages from an CIFS inode into the data storage object associated with
+> that inode.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+
+Acked-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002197:2, b/test/corpora/lkml/cur/1382298587.002197:2,
new file mode 100644 (file)
index 0000000..765c399
--- /dev/null
@@ -0,0 +1,53 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 09/10] cifs: read pages from FS-Cache
+Date: Wed, 23 Jun 2010 18:07:40 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 7
+Message-ID: <9918.1277312860@redhat.com>
+References: <1277220261-3717-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:07:51 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTQR-0000nv-JF
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:07:47 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751708Ab0FWRHr (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:07:47 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:34413 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1750954Ab0FWRHq (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:07:46 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH7h3Y005904
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:07:43 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH7efR020683;
+       Wed, 23 Jun 2010 13:07:41 -0400
+In-Reply-To: <1277220261-3717-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002227>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Read pages from a FS-Cache data storage object into a CIFS inode.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+
+Acked-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002201:2, b/test/corpora/lkml/cur/1382298587.002201:2,
new file mode 100644 (file)
index 0000000..bae1eef
--- /dev/null
@@ -0,0 +1,58 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Wed, 23 Jun 2010 18:08:34 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 12
+Message-ID: <9942.1277312914@redhat.com>
+References: <1277220309-3757-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 23 19:09:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1ORTRv-0002J8-2s
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 23 Jun 2010 19:09:19 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753275Ab0FWRIt (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 23 Jun 2010 13:08:49 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:6156 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753203Ab0FWRIr (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 23 Jun 2010 13:08:47 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH8dax006028
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:08:39 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH8YmA020846;
+       Wed, 23 Jun 2010 13:08:36 -0400
+In-Reply-To: <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002231>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Add a mount option 'fsc' to enable local caching on CIFS.
+> 
+> As the cifs-utils (userspace) changes are not done yet, this patch enables
+> 'fsc' by default to assist testing.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+
+Acked-by: David Howells <dhowells@redhat.com>
+
+(Give or take the debugging bit)
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002228:2, b/test/corpora/lkml/cur/1382298587.002228:2,
new file mode 100644 (file)
index 0000000..b401ae3
--- /dev/null
@@ -0,0 +1,100 @@
+From: Scott Lovenberg <scott.lovenberg@gmail.com>
+Subject: Re: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Wed, 23 Jun 2010 14:32:24 -0400
+Lines: 37
+Message-ID: <4C225338.9010807@gmail.com>
+References: <yes> <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench@gmail.com>, linux-cifs@vger.kernel.org,
+       linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
+       David Howells <dhowells@redhat.com>
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 23 20:32:44 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1ORUke-00020X-7B
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 23 Jun 2010 20:32:44 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753205Ab0FWScd (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 23 Jun 2010 14:32:33 -0400
+Received: from mail-gx0-f174.google.com ([209.85.161.174]:50118 "EHLO
+       mail-gx0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752380Ab0FWScb (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 23 Jun 2010 14:32:31 -0400
+Received: by gxk28 with SMTP id 28so317656gxk.19
+        for <multiple recipients>; Wed, 23 Jun 2010 11:32:31 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:received:message-id:date:from
+         :user-agent:mime-version:to:cc:subject:references:in-reply-to
+         :content-type:content-transfer-encoding;
+        bh=iTBSrajJefJVTimpUcvJQmptYefXJDrz9ZyZgxnMvzA=;
+        b=DOZLux9YGwNIWknqofz5rMltvopOT+kRgPsHIYw8Z7Uhh9gR5YAD4V6kKmv1SIaWoo
+         uXjNwY+IPIiD4f4OwwlpwJTd4B7PkBCDIlOkwVcvvS3F6qr6WbXBd0nRuRiFGMwONU3E
+         MqTAWDDwIXLVURr1t+n3MFrKwKj5b7pZT5fHw=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=message-id:date:from:user-agent:mime-version:to:cc:subject
+         :references:in-reply-to:content-type:content-transfer-encoding;
+        b=pTsfPMlTDpE3Oi2w9V3eE2ohOeBEloXhgElmCwGEenBegF7ZhIyoga6tyRJqQ922ws
+         oyxLXSORpOuPJRoIBRXfzae3KXkgKT0eLDjxQNTdS7Jbe+vcJ604sANFcnxBsJ51fThT
+         R/wXt7LiG/T6H4DUpcN7aUjtzlq9JgC2JQ/ws=
+Received: by 10.224.43.197 with SMTP id x5mr5243425qae.127.1277317950764;
+        Wed, 23 Jun 2010 11:32:30 -0700 (PDT)
+Received: from [192.168.0.2] ([64.9.41.61])
+        by mx.google.com with ESMTPS id 15sm3010007qcg.2.2010.06.23.11.32.25
+        (version=TLSv1/SSLv3 cipher=RC4-MD5);
+        Wed, 23 Jun 2010 11:32:26 -0700 (PDT)
+User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.10) Gecko/20100512 Lightning/1.0b1 Thunderbird/3.0.5 ThunderBrowse/3.2.8.1
+In-Reply-To: <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002258>
+
+On 6/22/2010 11:25 AM, Suresh Jayaraman wrote:
+> Add a mount option 'fsc' to enable local caching on CIFS.
+>
+> As the cifs-utils (userspace) changes are not done yet, this patch enables
+> 'fsc' by default to assist testing.
+>    
+[...]
+> @@ -1332,6 +1336,8 @@ cifs_parse_mount_options(char *options, const char *devname,
+>                      printk(KERN_WARNING "CIFS: Mount option noac not "
+>                              "supported. Instead set "
+>                              "/proc/fs/cifs/LookupCacheEnabled to 0\n");
+> +            } else if (strnicmp(data, "fsc", 3) == 0) {
+> +                    vol->fsc = true;
+>              } else
+>                      printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
+>                                              data);
+> @@ -2405,6 +2411,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
+>              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+>      if (pvolume_info->dynperm)
+>              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
+> +    if (pvolume_info->fsc)
+> +            cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE;
+>      if (pvolume_info->direct_io) {
+>              cFYI(1, "mounting share using direct i/o");
+>              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+>    
+I reworked the CIFS mount option parsing a while back; I'm not sure 
+whether that patch was going to be in the 2.6.35 tree or not (the window 
+just opened, didn't it?).
+
+Jeff, Steve, can you confirm if that patch is going to be in 2.6.35?
+
+Patch refs: http://patchwork.ozlabs.org/patch/53059/  and 
+http://patchwork.ozlabs.org/patch/53674/
+
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002878:2, b/test/corpora/lkml/cur/1382298587.002878:2,
new file mode 100644 (file)
index 0000000..66a3e22
--- /dev/null
@@ -0,0 +1,90 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Fri, 25 Jun 2010 16:18:12 +0530
+Lines: 47
+Message-ID: <4C24896C.4000903@suse.de>
+References: <yes> <1277220309-3757-1-git-send-email-sjayaraman@suse.de> <4C225338.9010807@gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Scott Lovenberg <scott.lovenberg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 12:48:27 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS6SO-0003QF-NW
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 12:48:25 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753965Ab0FYKsX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 06:48:23 -0400
+Received: from cantor.suse.de ([195.135.220.2]:46395 "EHLO mx1.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752612Ab0FYKsW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 06:48:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by mx1.suse.de (Postfix) with ESMTP id 60CED6CB00;
+       Fri, 25 Jun 2010 12:48:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <4C225338.9010807-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002912>
+
+On 06/24/2010 12:02 AM, Scott Lovenberg wrote:
+> On 6/22/2010 11:25 AM, Suresh Jayaraman wrote:
+>> Add a mount option 'fsc' to enable local caching on CIFS.
+>>
+>> As the cifs-utils (userspace) changes are not done yet, this patch
+>> enables
+>> 'fsc' by default to assist testing.
+>>    
+> [...]
+>> @@ -1332,6 +1336,8 @@ cifs_parse_mount_options(char *options, const
+>> char *devname,
+>>               printk(KERN_WARNING "CIFS: Mount option noac not "
+>>                   "supported. Instead set "
+>>                   "/proc/fs/cifs/LookupCacheEnabled to 0\n");
+>> +        } else if (strnicmp(data, "fsc", 3) == 0) {
+>> +            vol->fsc = true;
+>>           } else
+>>               printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
+>>                           data);
+>> @@ -2405,6 +2411,8 @@ static void setup_cifs_sb(struct smb_vol
+>> *pvolume_info,
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+>>       if (pvolume_info->dynperm)
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
+>> +    if (pvolume_info->fsc)
+>> +        cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE;
+>>       if (pvolume_info->direct_io) {
+>>           cFYI(1, "mounting share using direct i/o");
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+>>    
+> I reworked the CIFS mount option parsing a while back; I'm not sure
+> whether that patch was going to be in the 2.6.35 tree or not (the window
+> just opened, didn't it?).
+
+Not a problem, I could redo this patch alone when the reworked option
+parsing patches get in.
+
+> Jeff, Steve, can you confirm if that patch is going to be in 2.6.35?
+> 
+> Patch refs: http://patchwork.ozlabs.org/patch/53059/  and
+> http://patchwork.ozlabs.org/patch/53674/
+> 
+
+Thanks,
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002912:2, b/test/corpora/lkml/cur/1382298587.002912:2,
new file mode 100644 (file)
index 0000000..d9c761d
--- /dev/null
@@ -0,0 +1,65 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Fri, 25 Jun 2010 18:20:14 +0530
+Lines: 24
+Message-ID: <4C24A606.5040001@suse.de>
+References: <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 14:50:26 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8MR-0007EU-OS
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 14:50:24 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754607Ab0FYMuX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 08:50:23 -0400
+Received: from cantor2.suse.de ([195.135.220.15]:38716 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753675Ab0FYMuW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 08:50:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id B05E686A2E;
+       Fri, 25 Jun 2010 14:50:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <9822.1277312573-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002946>
+
+On 06/23/2010 10:32 PM, David Howells wrote:
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+>> Define inode-level data storage objects (managed by cifsInodeInfo structs).
+>> Each inode-level object is created in a super-block level object and is
+>> itself a data storage object in to which pages from the inode are stored.
+>>
+>> The inode object is keyed by UniqueId. The coherency data being used is
+>> LastWriteTime and the file size.
+> 
+> Isn't there a file creation time too?
+
+I think the creation time is currently being ignored as we won't be able
+to accomodate in POSIX stat struct.
+
+> I take it you don't support caching on files that are open for writing at this
+> time?
+> 
+
+Yes.
+
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002915:2, b/test/corpora/lkml/cur/1382298587.002915:2,
new file mode 100644 (file)
index 0000000..e43c909
--- /dev/null
@@ -0,0 +1,58 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Fri, 25 Jun 2010 13:55:49 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 12
+Message-ID: <22697.1277470549@redhat.com>
+References: <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 14:56:04 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8Rw-0002tq-3k
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 14:56:04 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753622Ab0FYM4B (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 08:56:01 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50162 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752535Ab0FYM4B (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 08:56:01 -0400
+Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCtqOd018091
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 08:55:52 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCtn4G016466;
+       Fri, 25 Jun 2010 08:55:51 -0400
+In-Reply-To: <4C24A606.5040001-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002949>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> I think the creation time is currently being ignored as we won't be able
+> to accomodate in POSIX stat struct.
+
+The FS-Cache interface doesn't use the POSIX stat struct, but it could be
+really useful to save it and use it for cache coherency inside the kernel.
+
+Out of interest, what does Samba do when it comes to generating a creation time
+for UNIX where one does not exist?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002917:2, b/test/corpora/lkml/cur/1382298587.002917:2,
new file mode 100644 (file)
index 0000000..f7047f8
--- /dev/null
@@ -0,0 +1,67 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Fri, 25 Jun 2010 13:58:33 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 21
+Message-ID: <22746.1277470713@redhat.com>
+References: <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Fri Jun 25 15:02:20 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with smtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OS8Xz-000628-FG
+       for lnx-linux-fsdevel@lo.gmane.org; Fri, 25 Jun 2010 15:02:19 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755357Ab0FYM6k (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Fri, 25 Jun 2010 08:58:40 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50417 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754086Ab0FYM6j (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Fri, 25 Jun 2010 08:58:39 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCwa7Z005113
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 08:58:36 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCwXVB011094;
+       Fri, 25 Jun 2010 08:58:34 -0400
+In-Reply-To: <4C24A4A0.90408@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002951>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Also, considering the UNC name of the resource (//server/share) may not
+> be a good idea too as the cache will not be used when for e.g. IPaddress
+> is used to mount.
+
+You could convert the UNC name to an IP address, and just use that as your
+key.
+
+> > validate the root directory of the share in some way?
+>
+> I don't know if there is a way to do this.
+
+Is there an inode number or something?  Even the creation time might do.
+
+David
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.002997:2, b/test/corpora/lkml/cur/1382298587.002997:2,
new file mode 100644 (file)
index 0000000..b78073c
--- /dev/null
@@ -0,0 +1,90 @@
+From: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Fri, 25 Jun 2010 12:53:06 -0400
+Lines: 36
+Message-ID: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+References: <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+       <yes>
+       <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>,
+       Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 18:53:12 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSC9P-0005Eb-SU
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 18:53:12 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S932199Ab0FYQxK (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 12:53:10 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.122]:53512 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S932187Ab0FYQxJ (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Fri, 25 Jun 2010 12:53:09 -0400
+X-Authority-Analysis: v=1.0 c=1 a=iVNVO0OCT3kA:10 a=yQWWgrYGNuUA:10 a=kj9zAlcOel0A:10 a=20KFwNOVAAAA:8 a=hGzw-44bAAAA:8 a=f0L6POiToRdS6aViIA4A:9 a=tdNtT7bw1iHNm6ggrCkIte35EhAA:4 a=CjuIK1q_8ugA:10 a=jEp0ucaQiEUA:10 a=0kPLrQdw3YYA:10 a=dowx1zmaLagA:10 a=00U40p1LBqVLw4jT:21 a=gh7LVOPznGai4vo_:21
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:42266] helo=mail.poochiereds.net)
+       by cdptpa-oedge01.mail.rr.com (envelope-from <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id 2D/E0-24471-3FED42C4; Fri, 25 Jun 2010 16:53:08 +0000
+Received: from tlielax.poochiereds.net (tlielax.poochiereds.net [192.168.1.3])
+       by mail.poochiereds.net (Postfix) with ESMTPS id E9B19580FA;
+       Fri, 25 Jun 2010 12:53:06 -0400 (EDT)
+In-Reply-To: <22697.1277470549-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003033>
+
+On Fri, 25 Jun 2010 13:55:49 +0100
+David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
+
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+> > I think the creation time is currently being ignored as we won't be able
+> > to accomodate in POSIX stat struct.
+> 
+> The FS-Cache interface doesn't use the POSIX stat struct, but it could be
+> really useful to save it and use it for cache coherency inside the kernel.
+> 
+> Out of interest, what does Samba do when it comes to generating a creation time
+> for UNIX where one does not exist?
+> 
+
+(cc'ing samba-technical since we're talking about the create time)
+
+Looks like it mostly uses the ctime. IMO, the mtime would be a better
+choice since it changes less frequently, but I don't guess that it
+matters very much.
+
+I have a few patches that make the cifs_iget code do more stringent
+checks. One of those makes it use the create time like an i_generation
+field to guard against matching inodes that have the same number but
+that have undergone a delete/create cycle. They need a bit more testing
+but I'm planning to post them in time for 2.6.36.
+
+Because of how samba generates this number, it could be somewhat
+problematic to do this. What may save us though is that Linux<->Samba
+mostly uses unix extensions unless someone has specifically disabled
+them on either end. The unix extension calls don't generally send any
+sort of create time field, so we can't rely on it in those codepaths
+anyway.
+
+-- 
+Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003106:2, b/test/corpora/lkml/cur/1382298587.003106:2,
new file mode 100644 (file)
index 0000000..19ea381
--- /dev/null
@@ -0,0 +1,60 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Fri, 25 Jun 2010 22:46:38 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <18628.1277502398@redhat.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com>
+Cc: dhowells@redhat.com, Suresh Jayaraman <sjayaraman@suse.de>,
+       Steve French <smfrench@gmail.com>, linux-cifs@vger.kernel.org,
+       linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
+       samba-technical@lists.samba.org
+To: Jeff Layton <jlayton@samba.org>
+X-From: linux-kernel-owner@vger.kernel.org Fri Jun 25 23:47:07 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSGjo-0006q8-ME
+       for glk-linux-kernel-3@lo.gmane.org; Fri, 25 Jun 2010 23:47:05 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932250Ab0FYVqv (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Fri, 25 Jun 2010 17:46:51 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:55406 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932088Ab0FYVqs (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Fri, 25 Jun 2010 17:46:48 -0400
+Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PLkhIG005974
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 17:46:43 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PLkd77017768;
+       Fri, 25 Jun 2010 17:46:40 -0400
+In-Reply-To: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003142>
+
+Jeff Layton <jlayton@samba.org> wrote:
+
+> Looks like it mostly uses the ctime. IMO, the mtime would be a better
+> choice since it changes less frequently, but I don't guess that it
+> matters very much.
+
+I'd've thought mtime changes more frequently since that's altered when data is
+written.  ctime is changed when attributes are changed.
+
+Note that Ext4 appears to have a file creation time field in its inode
+(struct ext4_inode::i_crtime[_extra]).  Can Samba be made to use that?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003112:2, b/test/corpora/lkml/cur/1382298587.003112:2,
new file mode 100644 (file)
index 0000000..e74a864
--- /dev/null
@@ -0,0 +1,105 @@
+From: Jeff Layton <jlayton@samba.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+       register them
+Date: Fri, 25 Jun 2010 18:26:51 -0400
+Lines: 30
+Message-ID: <20100625182651.36800d06@tlielax.poochiereds.net>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+       <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes>
+       <9822.1277312573@redhat.com> <22697.1277470549@redhat.com>
+       <18628.1277502398@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: linux-cifs@vger.kernel.org, samba-technical@lists.samba.org,
+       linux-kernel@vger.kernel.org, Steve French <smfrench@gmail.com>,
+       linux-fsdevel@vger.kernel.org
+To: David Howells <dhowells@redhat.com>
+X-From: samba-technical-bounces@lists.samba.org Sat Jun 26 00:27:01 2010
+Return-path: <samba-technical-bounces@lists.samba.org>
+Envelope-to: gnsi-samba-technical@m.gmane.org
+Received: from fn.samba.org ([216.83.154.106] helo=lists.samba.org)
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <samba-technical-bounces@lists.samba.org>)
+       id 1OSHMS-0003Yl-G8
+       for gnsi-samba-technical@m.gmane.org; Sat, 26 Jun 2010 00:27:01 +0200
+Received: from fn.samba.org (localhost [127.0.0.1])
+       by lists.samba.org (Postfix) with ESMTP id 8919DAD2B8;
+       Fri, 25 Jun 2010 16:26:57 -0600 (MDT)
+X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on fn.samba.org
+X-Spam-Level: 
+X-Spam-Status: No, score=-1.7 required=3.8 tests=AWL,BAYES_00,SPF_NEUTRAL
+       autolearn=no version=3.2.5
+X-Original-To: samba-technical@lists.samba.org
+Delivered-To: samba-technical@lists.samba.org
+Received: from cdptpa-omtalb.mail.rr.com (cdptpa-omtalb.mail.rr.com
+       [75.180.132.122])
+       by lists.samba.org (Postfix) with ESMTP id ECB66AD220
+       for <samba-technical@lists.samba.org>;
+       Fri, 25 Jun 2010 16:26:51 -0600 (MDT)
+X-Authority-Analysis: v=1.0 c=1 a=iVNVO0OCT3kA:10 a=yQWWgrYGNuUA:10
+       a=kj9zAlcOel0A:10 a=20KFwNOVAAAA:8 a=hGzw-44bAAAA:8
+       a=AraS79FXNJ3kHilSTm4A:9 a=3STw0N-n4mJG0pydffwA:7
+       a=0uwppTlTaQ5HiYOalIavAxwTlvEA:4 a=CjuIK1q_8ugA:10
+       a=jEp0ucaQiEUA:10 a=dowx1zmaLagA:10
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:55553] helo=mail.poochiereds.net)
+       by cdptpa-oedge01.mail.rr.com (envelope-from <jlayton@samba.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id 78/FA-24471-C2D252C4; Fri, 25 Jun 2010 22:26:53 +0000
+Received: from tlielax.poochiereds.net (tlielax.poochiereds.net [192.168.1.3])
+       by mail.poochiereds.net (Postfix) with ESMTPS id 68F07580FA;
+       Fri, 25 Jun 2010 18:26:52 -0400 (EDT)
+In-Reply-To: <18628.1277502398@redhat.com>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+X-BeenThere: samba-technical@lists.samba.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Discussions on Samba internals. For general questions please
+       subscribe to the list samba@samba.org"
+       <samba-technical.lists.samba.org>
+List-Unsubscribe: <https://lists.samba.org/mailman/options/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=unsubscribe>
+List-Archive: <http://lists.samba.org/pipermail/samba-technical>
+List-Post: <mailto:samba-technical@lists.samba.org>
+List-Help: <mailto:samba-technical-request@lists.samba.org?subject=help>
+List-Subscribe: <https://lists.samba.org/mailman/listinfo/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=subscribe>
+Sender: samba-technical-bounces@lists.samba.org
+Errors-To: samba-technical-bounces@lists.samba.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003148>
+
+On Fri, 25 Jun 2010 22:46:38 +0100
+David Howells <dhowells@redhat.com> wrote:
+
+> Jeff Layton <jlayton@samba.org> wrote:
+> 
+> > Looks like it mostly uses the ctime. IMO, the mtime would be a better
+> > choice since it changes less frequently, but I don't guess that it
+> > matters very much.
+> 
+> I'd've thought mtime changes more frequently since that's altered when data is
+> written.  ctime is changed when attributes are changed.
+> 
+
+IIUC, updating mtime for a write is also an attribute change, and that
+affects ctime. According to the stat(2) manpage:
+
+       The field st_ctime is changed by writing or by setting  inode  informa-
+       tion (i.e., owner, group, link count, mode, etc.).
+
+> Note that Ext4 appears to have a file creation time field in its inode
+> (struct ext4_inode::i_crtime[_extra]).  Can Samba be made to use that?
+> 
+
+Is it exposed to userspace in any (standard) way? It would be handy to
+have that. While we're wishing...it might also be nice to have a
+standard way to get at the i_generation from userspace too.
+
+-- 
+Jeff Layton <jlayton@samba.org>
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003117:2, b/test/corpora/lkml/cur/1382298587.003117:2,
new file mode 100644 (file)
index 0000000..7f53e34
--- /dev/null
@@ -0,0 +1,65 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Sat, 26 Jun 2010 00:04:28 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 18
+Message-ID: <20123.1277507068@redhat.com>
+References: <20100625182651.36800d06@tlielax.poochiereds.net> <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com> <18628.1277502398@redhat.com>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>,
+       Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org
+To: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Sat Jun 26 01:04:45 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSHww-0006Jk-NV
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Sat, 26 Jun 2010 01:04:43 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751807Ab0FYXEl (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 19:04:41 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:62977 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752149Ab0FYXEl (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 19:04:41 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PN4X40004498
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 19:04:34 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PN4Sld008220;
+       Fri, 25 Jun 2010 19:04:30 -0400
+In-Reply-To: <20100625182651.36800d06-9yPaYZwiELC+kQycOl6kW4xkIHaj4LzF@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003153>
+
+Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org> wrote:
+
+> IIUC, updating mtime for a write is also an attribute change, and that
+> affects ctime. According to the stat(2) manpage:
+
+You're right.  Okay, ctime is the more frequently changed.
+
+> > Note that Ext4 appears to have a file creation time field in its inode
+> > (struct ext4_inode::i_crtime[_extra]).  Can Samba be made to use that?
+> 
+> Is it exposed to userspace in any (standard) way? It would be handy to
+> have that. While we're wishing...it might also be nice to have a
+> standard way to get at the i_generation from userspace too.
+
+Not at present, but it's something that could be exported by ioctl() or
+getxattr().
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003118:2, b/test/corpora/lkml/cur/1382298587.003118:2,
new file mode 100644 (file)
index 0000000..a1ec438
--- /dev/null
@@ -0,0 +1,122 @@
+From: Steve French <smfrench@gmail.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and 
+       register them
+Date: Fri, 25 Jun 2010 18:05:30 -0500
+Lines: 51
+Message-ID: <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+       <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+       <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>
+       <18628.1277502398@redhat.com>
+       <20100625182651.36800d06@tlielax.poochiereds.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: David Howells <dhowells@redhat.com>,
+       Suresh Jayaraman <sjayaraman@suse.de>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, samba-technical@lists.samba.org,
+       Jeff Layton <jlayton@redhat.com>
+To: Jeff Layton <jlayton@samba.org>,
+       "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>,
+       Mingming Cao <mcao@us.ibm.com>
+X-From: linux-kernel-owner@vger.kernel.org Sat Jun 26 01:05:41 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSHxs-0006a8-BA
+       for glk-linux-kernel-3@lo.gmane.org; Sat, 26 Jun 2010 01:05:40 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756188Ab0FYXFd convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Fri, 25 Jun 2010 19:05:33 -0400
+Received: from mail-qw0-f46.google.com ([209.85.216.46]:51369 "EHLO
+       mail-qw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751575Ab0FYXFb convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Fri, 25 Jun 2010 19:05:31 -0400
+Received: by qwi4 with SMTP id 4so742644qwi.19
+        for <multiple recipients>; Fri, 25 Jun 2010 16:05:30 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:mime-version:received:received:in-reply-to
+         :references:date:message-id:subject:from:to:cc:content-type
+         :content-transfer-encoding;
+        bh=6wKQkGOEeUGN4oPR3Nm4SRxtJr/EBwN8ENmpLnfdCDU=;
+        b=X7L6W0MtpQeW/4iBuj+oDlcP2yCJ3qwUs9lHBq1fRW6WdYblHXjmaN8o++3GDPLAg5
+         0MD07zxbYTGXRSrgCjCrGVm0tT88/6hY2a/rB8g68h/Qso2sIHa7B1iIN8JRR4pPWle0
+         sVjp9Xy/bQn2e0uE481Ii1TLHuWYA/QDXZreU=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:date:message-id:subject:from:to
+         :cc:content-type:content-transfer-encoding;
+        b=B+7qQvdOpN5a/KCRrDbssKZX8D3SnP73VMHd9RpkqP9nCHCmSLAgbeH03+/m6CLVAo
+         G+NKWqWtknwPBkYqT/bdP2XEak1yr+0rjOqjUaNvaT7AhzsyHEJBkaNnsbS3qaRy39OP
+         S7OkAyHfmgdeNAHkKnKRF73hfpvgAqR9X4bn8=
+Received: by 10.224.59.223 with SMTP id m31mr1130670qah.63.1277507130411; Fri, 
+       25 Jun 2010 16:05:30 -0700 (PDT)
+Received: by 10.229.46.136 with HTTP; Fri, 25 Jun 2010 16:05:30 -0700 (PDT)
+In-Reply-To: <20100625182651.36800d06@tlielax.poochiereds.net>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003154>
+
+On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wrote:
+>
+> On Fri, 25 Jun 2010 22:46:38 +0100
+> David Howells <dhowells@redhat.com> wrote:
+>
+> > Jeff Layton <jlayton@samba.org> wrote:
+> >
+> > > Looks like it mostly uses the ctime. IMO, the mtime would be a be=
+tter
+> > > choice since it changes less frequently, but I don't guess that i=
+t
+> > > matters very much.
+> >
+> > I'd've thought mtime changes more frequently since that's altered w=
+hen data is
+> > written. =A0ctime is changed when attributes are changed.
+> >
+>
+> IIUC, updating mtime for a write is also an attribute change, and tha=
+t
+> affects ctime. According to the stat(2) manpage:
+>
+> =A0 =A0 =A0 The field st_ctime is changed by writing or by setting =A0=
+inode =A0informa-
+> =A0 =A0 =A0 tion (i.e., owner, group, link count, mode, etc.).
+>
+> > Note that Ext4 appears to have a file creation time field in its in=
+ode
+> > (struct ext4_inode::i_crtime[_extra]). =A0Can Samba be made to use =
+that?
+> >
+>
+> Is it exposed to userspace in any (standard) way? It would be handy t=
+o
+> have that. While we're wishing...it might also be nice to have a
+> standard way to get at the i_generation from userspace too.
+>
+
+Yes - I have talked with MingMing and Aneesh about those (NFS may
+someday be able to use those too).=A0 An obstacle in the past had been
+that samba server stores its own fake creation time in an ndr encoded
+xattr which complicates things.
+
+MingMing/Annesh -
+Xattr or other way to get at birth time?
+
+
+--
+Thanks,
+
+Steve
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003171:2, b/test/corpora/lkml/cur/1382298587.003171:2,
new file mode 100644 (file)
index 0000000..66e425e
--- /dev/null
@@ -0,0 +1,174 @@
+From: Mingming Cao <mcao@us.ibm.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+       register them
+Date: Fri, 25 Jun 2010 17:52:24 -0700
+Lines: 92
+Message-ID: <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>  <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>   <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>   <18628.1277502398@redhat.com>   <20100625182651.36800d06@tlielax.poochiereds.net>
+       <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+Cc: linux-cifs@vger.kernel.org, Jeff Layton <jlayton@redhat.com>,
+       samba-technical@lists.samba.org, linux-kernel@vger.kernel.org,
+       David Howells <dhowells@redhat.com>, linux-fsdevel@vger.kernel.org,
+       "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>
+To: Steve French <smfrench@gmail.com>
+X-From: samba-technical-bounces@lists.samba.org Sat Jun 26 13:36:56 2010
+Return-path: <samba-technical-bounces@lists.samba.org>
+Envelope-to: gnsi-samba-technical@m.gmane.org
+Received: from fn.samba.org ([216.83.154.106] helo=lists.samba.org)
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <samba-technical-bounces@lists.samba.org>)
+       id 1OSTgu-00025d-6P
+       for gnsi-samba-technical@m.gmane.org; Sat, 26 Jun 2010 13:36:56 +0200
+Received: from fn.samba.org (localhost [127.0.0.1])
+       by lists.samba.org (Postfix) with ESMTP id 1ED11AD2C4;
+       Sat, 26 Jun 2010 05:36:45 -0600 (MDT)
+X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on fn.samba.org
+X-Spam-Level: 
+X-Spam-Status: No, score=-6.6 required=3.8 tests=BAYES_00,HTML_MESSAGE,
+       RCVD_IN_DNSWL_MED,SPF_PASS autolearn=ham version=3.2.5
+X-Original-To: samba-technical@lists.samba.org
+Delivered-To: samba-technical@lists.samba.org
+Received: from e34.co.us.ibm.com (e34.co.us.ibm.com [32.97.110.152])
+       by lists.samba.org (Postfix) with ESMTP id 30F90AD282
+       for <samba-technical@lists.samba.org>;
+       Fri, 25 Jun 2010 18:52:24 -0600 (MDT)
+Received: from d03relay01.boulder.ibm.com (d03relay01.boulder.ibm.com
+       [9.17.195.226])
+       by e34.co.us.ibm.com (8.14.4/8.13.1) with ESMTP id o5Q0iN1h017083
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:44:23 -0600
+Received: from d03av01.boulder.ibm.com (d03av01.boulder.ibm.com [9.17.195.167])
+       by d03relay01.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id
+       o5Q0qQTN175324
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:52:26 -0600
+Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1])
+       by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP
+       id o5Q0qPCF006767
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:52:26 -0600
+Received: from d03nm128.boulder.ibm.com (d03nm128.boulder.ibm.com
+       [9.17.195.32])
+       by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id
+       o5Q0qPrh006760; Fri, 25 Jun 2010 18:52:25 -0600
+In-Reply-To: <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+X-KeepSent: B55E8EC7:E8DD23D5-8725774E:0004921E;
+ type=4; name=$KeepSent
+X-Mailer: Lotus Notes Build V852_M2_03302010 March 30, 2010
+X-MIMETrack: Serialize by Router on D03NM128/03/M/IBM(Release 8.0.1|February
+       07, 2008) at 06/25/2010 18:52:25
+X-Mailman-Approved-At: Sat, 26 Jun 2010 05:36:42 -0600
+X-Content-Filtered-By: Mailman/MimeDel 2.1.12
+X-BeenThere: samba-technical@lists.samba.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Discussions on Samba internals. For general questions please
+       subscribe to the list samba@samba.org"
+       <samba-technical.lists.samba.org>
+List-Unsubscribe: <https://lists.samba.org/mailman/options/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=unsubscribe>
+List-Archive: <http://lists.samba.org/pipermail/samba-technical>
+List-Post: <mailto:samba-technical@lists.samba.org>
+List-Help: <mailto:samba-technical-request@lists.samba.org?subject=help>
+List-Subscribe: <https://lists.samba.org/mailman/listinfo/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=subscribe>
+Sender: samba-technical-bounces@lists.samba.org
+Errors-To: samba-technical-bounces@lists.samba.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003208>
+
+
+
+Steve French <smfrench@gmail.com> wrote on 06/25/2010 04:05:30 PM:
+
+> Steve French <smfrench@gmail.com>
+> 06/25/2010 04:05 PM
+>
+> To
+>
+> Jeff Layton <jlayton@samba.org>, "Aneesh Kumar K.V"
+> <aneesh.kumar@linux.vnet.ibm.com>, Mingming Cao/Beaverton/IBM@IBMUS
+>
+> cc
+>
+> David Howells <dhowells@redhat.com>, Suresh Jayaraman
+> <sjayaraman@suse.de>, linux-cifs@vger.kernel.org, linux-
+> fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, samba-
+> technical@lists.samba.org, Jeff Layton <jlayton@redhat.com>
+>
+> Subject
+>
+> Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+> register them
+>
+> On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wrot=
+e:
+> >
+> > On Fri, 25 Jun 2010 22:46:38 +0100
+> > David Howells <dhowells@redhat.com> wrote:
+> >
+> > > Jeff Layton <jlayton@samba.org> wrote:
+> > >
+> > > > Looks like it mostly uses the ctime. IMO, the mtime would be a
+better
+> > > > choice since it changes less frequently, but I don't guess that=
+ it
+> > > > matters very much.
+> > >
+> > > I'd've thought mtime changes more frequently since that's
+> altered when data is
+> > > written. =A0ctime is changed when attributes are changed.
+> > >
+> >
+> > IIUC, updating mtime for a write is also an attribute change, and t=
+hat
+> > affects ctime. According to the stat(2) manpage:
+> >
+> > =A0 =A0 =A0 The field st_ctime is changed by writing or by setting
+> =A0inode =A0informa-
+> > =A0 =A0 =A0 tion (i.e., owner, group, link count, mode, etc.).
+> >
+> > > Note that Ext4 appears to have a file creation time field in its
+inode
+> > > (struct ext4_inode::i_crtime[_extra]). =A0Can Samba be made to us=
+e
+that?
+> > >
+> >
+> > Is it exposed to userspace in any (standard) way? It would be handy=
+ to
+> > have that. While we're wishing...it might also be nice to have a
+> > standard way to get at the i_generation from userspace too.
+> >
+>
+> Yes - I have talked with MingMing and Aneesh about those (NFS may
+> someday be able to use those too).=A0 An obstacle in the past had bee=
+n
+> that samba server stores its own fake creation time in an ndr encoded=
+
+> xattr which complicates things.
+>
+> MingMing/Annesh -
+> Xattr or other way to get at birth time?
+>
+>
+
+Not yet,
+ The ext4 file creation time only accesable from the kernel at the mome=
+nt.
+There were discussion
+to make this information avaliable via xattr before, but was rejected,
+since most people
+agree that making this info avalibele via stat() is more standard. Howe=
+ver
+modifying stat() would imply
+big interface change. thus no action has been taken yet.
+
+> --
+> Thanks,
+>
+> Steve=
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003317:2, b/test/corpora/lkml/cur/1382298587.003317:2,
new file mode 100644 (file)
index 0000000..6fce518
--- /dev/null
@@ -0,0 +1,156 @@
+From: "Aneesh Kumar K. V" <aneesh.kumar@linux.vnet.ibm.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Sun, 27 Jun 2010 23:47:21 +0530
+Lines: 100
+Message-ID: <871vbscpce.fsf@linux.vnet.ibm.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com> <18628.1277502398@redhat.com> <20100625182651.36800d06@tlielax.poochiereds.net> <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com> <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: David Howells <dhowells@redhat.com>,
+       Jeff Layton <jlayton@redhat.com>,
+       Jeff Layton <jlayton@samba.org>, linux-cifs@vger.kernel.org,
+       linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
+       samba-technical@lists.samba.org,
+       Suresh Jayaraman <sjayaraman@suse.de>
+To: Mingming Cao <mcao@us.ibm.com>, Steve French <smfrench@gmail.com>,
+       "DENIEL Philippe" <philippe.deniel@cea.fr>
+X-From: linux-kernel-owner@vger.kernel.org Sun Jun 27 20:18:00 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSwQZ-0003Kh-Vu
+       for glk-linux-kernel-3@lo.gmane.org; Sun, 27 Jun 2010 20:18:00 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754631Ab0F0SRq convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 27 Jun 2010 14:17:46 -0400
+Received: from e23smtp07.au.ibm.com ([202.81.31.140]:52430 "EHLO
+       e23smtp07.au.ibm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753837Ab0F0SRl convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 27 Jun 2010 14:17:41 -0400
+Received: from d23relay05.au.ibm.com (d23relay05.au.ibm.com [202.81.31.247])
+       by e23smtp07.au.ibm.com (8.14.4/8.13.1) with ESMTP id o5RIHbfJ012483;
+       Mon, 28 Jun 2010 04:17:37 +1000
+Received: from d23av03.au.ibm.com (d23av03.au.ibm.com [9.190.234.97])
+       by d23relay05.au.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id o5RIHW9f1130634;
+       Mon, 28 Jun 2010 04:17:32 +1000
+Received: from d23av03.au.ibm.com (loopback [127.0.0.1])
+       by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id o5RIHVcR027534;
+       Mon, 28 Jun 2010 04:17:32 +1000
+Received: from skywalker.linux.vnet.ibm.com ([9.77.196.78])
+       by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id o5RIHMFl027485;
+       Mon, 28 Jun 2010 04:17:24 +1000
+In-Reply-To: <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+User-Agent: Notmuch/ (http://notmuchmail.org) Emacs/24.0.50.1 (i686-pc-linux-gnu)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003357>
+
+On Fri, 25 Jun 2010 17:52:24 -0700, Mingming Cao <mcao@us.ibm.com> wrot=
+e:
+>=20
+>=20
+> Steve French <smfrench@gmail.com> wrote on 06/25/2010 04:05:30 PM:
+>=20
+> > Steve French <smfrench@gmail.com>
+> > 06/25/2010 04:05 PM
+> >
+> > To
+> >
+> > Jeff Layton <jlayton@samba.org>, "Aneesh Kumar K.V"
+> > <aneesh.kumar@linux.vnet.ibm.com>, Mingming Cao/Beaverton/IBM@IBMUS
+> >
+> > cc
+> >
+> > David Howells <dhowells@redhat.com>, Suresh Jayaraman
+> > <sjayaraman@suse.de>, linux-cifs@vger.kernel.org, linux-
+> > fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, samba-
+> > technical@lists.samba.org, Jeff Layton <jlayton@redhat.com>
+> >
+> > Subject
+> >
+> > Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+> > register them
+> >
+> > On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wr=
+ote:
+> > >
+> > > On Fri, 25 Jun 2010 22:46:38 +0100
+> > > David Howells <dhowells@redhat.com> wrote:
+> > >
+> > > > Jeff Layton <jlayton@samba.org> wrote:
+> > > >
+> > > > > Looks like it mostly uses the ctime. IMO, the mtime would be =
+a
+> better
+> > > > > choice since it changes less frequently, but I don't guess th=
+at it
+> > > > > matters very much.
+> > > >
+> > > > I'd've thought mtime changes more frequently since that's
+> > altered when data is
+> > > > written. =C2=A0ctime is changed when attributes are changed.
+> > > >
+> > >
+> > > IIUC, updating mtime for a write is also an attribute change, and=
+ that
+> > > affects ctime. According to the stat(2) manpage:
+> > >
+> > > =C2=A0 =C2=A0 =C2=A0 The field st_ctime is changed by writing or =
+by setting
+> > =C2=A0inode =C2=A0informa-
+> > > =C2=A0 =C2=A0 =C2=A0 tion (i.e., owner, group, link count, mode, =
+etc.).
+> > >
+> > > > Note that Ext4 appears to have a file creation time field in it=
+s
+> inode
+> > > > (struct ext4_inode::i_crtime[_extra]). =C2=A0Can Samba be made =
+to use
+> that?
+> > > >
+> > >
+> > > Is it exposed to userspace in any (standard) way? It would be han=
+dy to
+> > > have that. While we're wishing...it might also be nice to have a
+> > > standard way to get at the i_generation from userspace too.
+> > >
+> >
+> > Yes - I have talked with MingMing and Aneesh about those (NFS may
+> > someday be able to use those too).=C2=A0 An obstacle in the past ha=
+d been
+> > that samba server stores its own fake creation time in an ndr encod=
+ed
+> > xattr which complicates things.
+> >
+> > MingMing/Annesh -
+> > Xattr or other way to get at birth time?
+> >
+> >
+>=20
+> Not yet,
+>  The ext4 file creation time only accesable from the kernel at the mo=
+ment.
+> There were discussion
+> to make this information avaliable via xattr before, but was rejected=
+,
+> since most people
+> agree that making this info avalibele via stat() is more standard. Ho=
+wever
+> modifying stat() would imply
+> big interface change. thus no action has been taken yet.
+
+NFS ganesha pNFS also had a requirement for getting i_generation and
+inode number in userspace. So may be we should now look at updating
+stat or add a variant syscall that include i_generation and create time
+in the return value
+
+-aneesh
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003318:2, b/test/corpora/lkml/cur/1382298587.003318:2,
new file mode 100644 (file)
index 0000000..058d147
--- /dev/null
@@ -0,0 +1,66 @@
+From: Christoph Hellwig <hch-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Sun, 27 Jun 2010 14:22:29 -0400
+Lines: 9
+Message-ID: <20100627182229.GA492@infradead.org>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+ <4C24A606.5040001@suse.de>
+ <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+ <9822.1277312573@redhat.com>
+ <22697.1277470549@redhat.com>
+ <18628.1277502398@redhat.com>
+ <20100625182651.36800d06@tlielax.poochiereds.net>
+ <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+ <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+ <871vbscpce.fsf@linux.vnet.ibm.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Mingming Cao <mcao-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       DENIEL Philippe <philippe.deniel-KCE40YydGKI@public.gmane.org>,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>,
+       Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>,
+       Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org,
+       Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+To: "Aneesh Kumar K. V" <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Sun Jun 27 20:22:46 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSwVB-0005TI-SG
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Sun, 27 Jun 2010 20:22:46 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752811Ab0F0SWo (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Sun, 27 Jun 2010 14:22:44 -0400
+Received: from bombadil.infradead.org ([18.85.46.34]:55433 "EHLO
+       bombadil.infradead.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752728Ab0F0SWn (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Sun, 27 Jun 2010 14:22:43 -0400
+Received: from hch by bombadil.infradead.org with local (Exim 4.72 #1 (Red Hat Linux))
+       id 1OSwUv-00009z-9N; Sun, 27 Jun 2010 18:22:29 +0000
+Content-Disposition: inline
+In-Reply-To: <871vbscpce.fsf-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+X-SRS-Rewrite: SMTP reverse-path rewritten from <hch-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org> by bombadil.infradead.org
+       See http://www.infradead.org/rpr.html
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003358>
+
+On Sun, Jun 27, 2010 at 11:47:21PM +0530, Aneesh Kumar K. V wrote:
+> NFS ganesha pNFS also had a requirement for getting i_generation and
+> inode number in userspace. So may be we should now look at updating
+> stat or add a variant syscall that include i_generation and create time
+> in the return value
+
+What's missing in knfsd that you feel the sudden urge to move backwards
+to a userspace nfsd (one with a horribly crappy codebase, too).
+
+
+
diff --git a/test/corpora/lkml/cur/1382298587.003486:2, b/test/corpora/lkml/cur/1382298587.003486:2,
new file mode 100644 (file)
index 0000000..8831b45
--- /dev/null
@@ -0,0 +1,89 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index
+ objects and register them
+Date: Mon, 28 Jun 2010 18:23:13 +0530
+Lines: 48
+Message-ID: <4C289B39.4060901@suse.de>
+References: <22746.1277470713@redhat.com> <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com> <23204.1277472412@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mon Jun 28 14:53:24 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OTDq0-00054Q-At
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Mon, 28 Jun 2010 14:53:24 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754503Ab0F1MxX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Mon, 28 Jun 2010 08:53:23 -0400
+Received: from cantor2.suse.de ([195.135.220.15]:48374 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754456Ab0F1MxW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Mon, 28 Jun 2010 08:53:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id 7BDC18672B;
+       Mon, 28 Jun 2010 14:53:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <23204.1277472412-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003530>
+
+On 06/25/2010 06:56 PM, David Howells wrote:
+> David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
+> 
+>>>> validate the root directory of the share in some way?
+>>>
+>>> I don't know if there is a way to do this.
+>>
+>> Is there an inode number or something?  Even the creation time might do.
+> 
+> Looking in cifspdu.h, there are a number of things that it might be possible
+> to use.
+> 
+>  (1) FILE_ALL_INFO: CreationTime, IndexNumber, IndexNumber1, FileName
+>      (assuming this isn't flattened to '\' or something for the root of a
+>      share.
+> 
+>  (2) FILE_UNIX_BASIC_INFO: DevMajor, DevMinor, UniqueId.
+> 
+>  (3) FILE_INFO_STANDARD: CreationDate, CreationTime.
+> 
+>  (4) FILE_INFO_BASIC: CreationTime.
+> 
+>  (5) FILE_DIRECTORY_INFO: FileIndex, CreationTime, FileName.
+> 
+>  (6) SEARCH_ID_FULL_DIR_INFO: FileIndex, CreationTime, UniqueId, FileName.
+> 
+>  (7) FILE_BOTH_DIRECTORY_INFO: FileIndex, CreationTime, ShortName, FileName.
+> 
+>  (8) OPEN_RSP_EXT: Fid, CreationTime, VolumeGUID, FileId.
+> 
+> You may have to choose different sets of things, depending on what the server
+> has on offer.  Also, don't forget, if you can't work out whether a share is
+
+Did you mean we need to validate differently for different servers?
+
+I just did some testing and it looks like we could rely on CreationTime,
+IndexNumber for validating with Windows servers (FileName is relative to
+the mapped drive) and UniqueId for validating with Samba servers. I did
+not test all possibilities (there could be more).
+
+> coherent or not from the above, you can always use LastWriteTime, ChangeTime
+> and EndOfFile and just discard the whole subtree if they differ.
+> 
+
+Thanks,
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298587.004581:2, b/test/corpora/lkml/cur/1382298587.004581:2,
new file mode 100644 (file)
index 0000000..732bfa0
--- /dev/null
@@ -0,0 +1,92 @@
+From: Timur Tabi <timur.tabi@gmail.com>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Wed, 30 Jun 2010 15:55:58 -0500
+Lines: 33
+Message-ID: <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+References: <20100308191005.GE4324@amak.tundra.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: mporter@kernel.crashing.org, linux-kernel@vger.kernel.org,
+       linuxppc-dev@lists.ozlabs.org, thomas.moll@sysgo.com
+To: Alexandre Bounine <abounine@tundra.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 30 22:56:40 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OU4Kl-0005Kf-V4
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 30 Jun 2010 22:56:40 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756668Ab0F3U4b convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 30 Jun 2010 16:56:31 -0400
+Received: from mail-vw0-f46.google.com ([209.85.212.46]:41333 "EHLO
+       mail-vw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753416Ab0F3U43 convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 30 Jun 2010 16:56:29 -0400
+Received: by vws5 with SMTP id 5so1449398vws.19
+        for <linux-kernel@vger.kernel.org>; Wed, 30 Jun 2010 13:56:28 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:mime-version:received:in-reply-to
+         :references:from:date:message-id:subject:to:cc:content-type
+         :content-transfer-encoding;
+        bh=FTlit9cHTz/9rLGcvA5/pEZlzxAQ5x20v8HE5XYFwYM=;
+        b=NFbjnxZ4KwcjTy4tFh+BnhWPEGeYTw6z918yIouRaMmbEDph56xq26K9aTBokuYHqe
+         UgFjBn7XWcxvqJPyCetfsDRG+F3M2XwCq/DSCswSPtXSLsy8WKm7cMXVS3hjiO8sMZ97
+         mRMGZkYBJHjWP+ulkBXiq6q7/OQuE8Dkl+rWM=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:from:date:message-id:subject:to
+         :cc:content-type:content-transfer-encoding;
+        b=r0N6AOAg+TSvY2kPQPahldj4iRU9oUoSLtHA7JXG2QU4CR9O5GBhxAtr2aY99qUPZd
+         tFS0ZWRAb9cmOgiZhTpNxsBjCJ/e/DQ1ccP5rZ/U40q1SJ1KwN92hqpOoppZ0tkqSB7/
+         UlQtsvPSK7a0bYqufEmscfAi98w1+mfZIbK6U=
+Received: by 10.220.161.203 with SMTP id s11mr5093041vcx.195.1277931388141; 
+       Wed, 30 Jun 2010 13:56:28 -0700 (PDT)
+Received: by 10.220.161.137 with HTTP; Wed, 30 Jun 2010 13:55:58 -0700 (PDT)
+In-Reply-To: <20100308191005.GE4324@amak.tundra.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1004632>
+
+On Mon, Mar 8, 2010 at 2:10 PM, Alexandre Bounine <abounine@tundra.com>=
+ wrote:
+>
+> From: Alexandre Bounine <alexandre.bounine@idt.com>
+>
+> Add Machine Check exception handling into RapidIO port driver
+> for Freescale SoCs (MPC85xx).
+>
+> Signed-off-by: Alexandre Bounine <alexandre.bounine@idt.com>
+> Tested-by: Thomas Moll <thomas.moll@sysgo.com>
+=2E..
+
+> +static int fsl_rio_mcheck_exception(struct pt_regs *regs)
+> +{
+> + =A0 =A0 =A0 const struct exception_table_entry *entry =3D NULL;
+> + =A0 =A0 =A0 unsigned long reason =3D (mfspr(SPRN_MCSR) & MCSR_MASK)=
+;
+
+MCSR_MASK is not defined anywhere, so when I compile this code, I get t=
+his:
+
+  CC      arch/powerpc/sysdev/fsl_rio.o
+arch/powerpc/sysdev/fsl_rio.c: In function 'fsl_rio_mcheck_exception':
+arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+(first use in this function)
+arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+is reported only once
+arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears =
+in.)
+
+--=20
+Timur Tabi
+Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298587.004582:2, b/test/corpora/lkml/cur/1382298587.004582:2,
new file mode 100644 (file)
index 0000000..d149b72
--- /dev/null
@@ -0,0 +1,68 @@
+From: Timur Tabi <timur.tabi@gmail.com>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Wed, 30 Jun 2010 16:00:56 -0500
+Lines: 12
+Message-ID: <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Cc: mporter@kernel.crashing.org, linux-kernel@vger.kernel.org,
+       linuxppc-dev@lists.ozlabs.org, thomas.moll@sysgo.com
+To: Alexandre Bounine <abounine@tundra.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 30 23:01:37 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OU4PZ-0000HS-0T
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 30 Jun 2010 23:01:37 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755703Ab0F3VB2 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 30 Jun 2010 17:01:28 -0400
+Received: from mail-vw0-f46.google.com ([209.85.212.46]:53141 "EHLO
+       mail-vw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751784Ab0F3VB1 (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 30 Jun 2010 17:01:27 -0400
+Received: by vws5 with SMTP id 5so1454517vws.19
+        for <linux-kernel@vger.kernel.org>; Wed, 30 Jun 2010 14:01:26 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:mime-version:received:in-reply-to
+         :references:from:date:message-id:subject:to:cc:content-type;
+        bh=+BUKti+Oa03CrnVvRyT591FhcoxqR7S2rzZHtD6WSuY=;
+        b=O/b04HLJrmTE0aIq2mNCRznQrXxAAGHSMarHR5mrgYptmr68froM6UgmDqTZFLhNiH
+         BcT8g+AziiqSV1k/ckXjRyVR0s9Jdv4g2phMNtp8NStbPfOPpLDkUKTQadphOTonCfeK
+         e+ZrLBwh+FCoYNAOjvFioBKj6CxN2Oi5xIhPc=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:from:date:message-id:subject:to
+         :cc:content-type;
+        b=UcKGhJIXCTTcSvBWwGwLUefPONGygVPsUnTt4nDSl4udB8JKMyi0EghzzgNXUyq4Dz
+         UCxzZAyxzjvjgsgPS3kzPhSsWG2PRG66pC1OA68RJ5YVOjt55/yOz/yfTqXBVvRSq2fV
+         QNcKACYHSjkIZ7Uq7ZEW9bEGI5tTKdz++N2UA=
+Received: by 10.220.124.73 with SMTP id t9mr5099129vcr.37.1277931686462; Wed, 
+       30 Jun 2010 14:01:26 -0700 (PDT)
+Received: by 10.220.161.137 with HTTP; Wed, 30 Jun 2010 14:00:56 -0700 (PDT)
+In-Reply-To: <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1004633>
+
+On Wed, Jun 30, 2010 at 3:55 PM, Timur Tabi <timur.tabi@gmail.com> wrote:
+
+> MCSR_MASK is not defined anywhere, so when I compile this code, I get this:
+
+Never mind.  I see that it's been fixed already, and that the patch
+that removed MCSR_MASK was posted around the same time that this patch
+was posted.
+
+
+-- 
+Timur Tabi
+Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001724:2, b/test/corpora/lkml/cur/1382298770.001724:2,
new file mode 100644 (file)
index 0000000..69c794c
--- /dev/null
@@ -0,0 +1,104 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Tue, 22 Jun 2010 20:55:09 +0530
+Lines: 66
+Message-ID: <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:25:29 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5Ls-0004PS-BM
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:25:28 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1755015Ab0FVPZ1 (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:25:27 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:48639 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1754070Ab0FVPZ1 (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:25:27 -0400
+X-Greylist: delayed 316 seconds by postgrey-1.27 at vger.kernel.org; Tue, 22 Jun 2010 11:25:26 EDT
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:25:11 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001750>
+
+Add a mount option 'fsc' to enable local caching on CIFS.
+
+As the cifs-utils (userspace) changes are not done yet, this patch enables
+'fsc' by default to assist testing.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/cifs_fs_sb.h |    1 +
+ fs/cifs/connect.c    |    8 ++++++++
+ 2 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h
+index 246a167..9e77145 100644
+--- a/fs/cifs/cifs_fs_sb.h
++++ b/fs/cifs/cifs_fs_sb.h
+@@ -35,6 +35,7 @@
+ #define CIFS_MOUNT_DYNPERM      0x1000 /* allow in-memory only mode setting   */
+ #define CIFS_MOUNT_NOPOSIXBRL   0x2000 /* mandatory not posix byte range lock */
+ #define CIFS_MOUNT_NOSSYNC      0x4000 /* don't do slow SMBflush on every sync*/
++#define CIFS_MOUNT_FSCACHE    0x8000 /* local caching enabled */
+ struct cifs_sb_info {
+       struct cifsTconInfo *tcon;      /* primary mount */
+diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
+index 4844dbd..6c6ff3c 100644
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -98,6 +98,7 @@ struct smb_vol {
+       bool noblocksnd:1;
+       bool noautotune:1;
+       bool nostrictsync:1; /* do not force expensive SMBflush on every sync */
++      bool fsc:1;     /* enable fscache */
+       unsigned int rsize;
+       unsigned int wsize;
+       bool sockopt_tcp_nodelay:1;
+@@ -843,6 +844,9 @@ cifs_parse_mount_options(char *options, const char *devname,
+       /* default to using server inode numbers where available */
+       vol->server_ino = 1;
++      /* XXX: default to fsc for testing until mount.cifs pieces are done */
++      vol->fsc = 1;
++
+       if (!options)
+               return 1;
+@@ -1332,6 +1336,8 @@ cifs_parse_mount_options(char *options, const char *devname,
+                       printk(KERN_WARNING "CIFS: Mount option noac not "
+                               "supported. Instead set "
+                               "/proc/fs/cifs/LookupCacheEnabled to 0\n");
++              } else if (strnicmp(data, "fsc", 3) == 0) {
++                      vol->fsc = true;
+               } else
+                       printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
+                                               data);
+@@ -2405,6 +2411,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+       if (pvolume_info->dynperm)
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
++      if (pvolume_info->fsc)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE;
+       if (pvolume_info->direct_io) {
+               cFYI(1, "mounting share using direct i/o");
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001730:2, b/test/corpora/lkml/cur/1382298770.001730:2,
new file mode 100644 (file)
index 0000000..840be2e
--- /dev/null
@@ -0,0 +1,103 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 00/10] cifs: local caching support using FS-Cache
+Date: Tue, 22 Jun 2010 20:50:05 +0530
+Lines: 66
+Message-ID: <1277220005-3322-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:40:38 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5aY-00055O-BD
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:40:38 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751889Ab0FVPkf (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:40:35 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:50040 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751554Ab0FVPkf (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:40:35 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:20:07 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001756>
+
+This patchset is a first stab at adding persistent, local caching facility for
+CIFS using the FS-Cache interface.
+
+The index hierarchy which is mainly used to locate a file object or discard
+a certain subset of the files cached, currently has three levels:
+       - Server
+       - Share 
+       - File
+
+The server index object is keyed by hostname of the server. The superblock
+index object is keyed by the sharename and the inode object is keyed by the
+UniqueId. The cache coherency is ensured by checking the 'LastWriteTime' and
+size of file.
+
+To use this, apply this patchset in order, mount the share with rsize=4096 and
+try copying a huge file (say few hundred MBs) from mount point to local
+filesystem. During the first time, the cache will be initialized. When you copy
+the second time, it should read from the local cache.
+
+To reduce the impact of page cache and see the local caching in action
+readily, try doing a sync and drop the caches by doing:
+       sync; echo 3 > /proc/sys/vm/drop_caches
+
+Known issues
+-------------
+       - the cache coherency check may not be reliable always as some
+         CIFS servers are known not to update mtime until the filehandle is
+         closed.
+       - not all the Servers under all circumstances provide a unique
+         'UniqueId'.
+
+Todo's
+-------
+       - improvements to avoid potential key collisions
+       - address the above known issues
+
+This set is lightly tested and all the bugs seen during my testing have been
+fixed. However, this can be considered as an RFC for now.
+
+Any Comments or Suggestions are welcome.
+
+Suresh Jayaraman (10)
+  cifs: add kernel config option for CIFS Client caching support
+  cifs: guard cifsglob.h against multiple inclusion
+  cifs: register CIFS for caching
+  cifs: define server-level cache index objects and register them with FS-Cache
+  cifs: define superblock-level cache index objects and register them
+  cifs: define inode-level cache object and register them
+  cifs: FS-Cache page management
+  cifs: store pages into local cache
+  cifs: read pages from FS-Cache
+  cifs: add mount option to enable local caching
+
+ Kconfig      |    9 ++
+ Makefile     |    2 
+ cache.c      |  251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ cifs_fs_sb.h |    1 
+ cifsfs.c     |   15 +++
+ cifsglob.h   |   14 +++
+ connect.c    |   16 +++
+ file.c       |   51 +++++++++++
+ fscache.c    |  244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fscache.h    |  135 +++++++++++++++++++++++++++++++
+ inode.c      |    4 
+ 11 files changed, 742 insertions(+)
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001731:2, b/test/corpora/lkml/cur/1382298770.001731:2,
new file mode 100644 (file)
index 0000000..d8b3168
--- /dev/null
@@ -0,0 +1,67 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 01/10] cifs: add kernel config option for CIFS Client caching support
+Date: Tue, 22 Jun 2010 20:52:38 +0530
+Lines: 30
+Message-ID: <1277220158-3405-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:43:27 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5dG-0007m9-Ij
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:43:26 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1751536Ab0FVPnS (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:18 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:51303 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1750800Ab0FVPnR (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:43:17 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:22:40 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001757>
+
+Add a kernel config option to enable local caching for CIFS.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/Kconfig |    9 +++++++++
+ 1 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/Kconfig b/fs/cifs/Kconfig
+index 80f3525..5739fd7 100644
+--- a/fs/cifs/Kconfig
++++ b/fs/cifs/Kconfig
+@@ -131,6 +131,15 @@ config CIFS_DFS_UPCALL
+           IP addresses) which is needed for implicit mounts of DFS junction
+           points. If unsure, say N.
++config CIFS_FSCACHE
++        bool "Provide CIFS client caching support (EXPERIMENTAL)"
++        depends on EXPERIMENTAL
++        depends on CIFS=m && FSCACHE || CIFS=y && FSCACHE=y
++        help
++          Makes CIFS FS-Cache capable. Say Y here if you want your CIFS data
++          to be cached locally on disk through the general filesystem cache
++          manager. If unsure, say N.
++
+ config CIFS_EXPERIMENTAL
+         bool "CIFS Experimental Features (EXPERIMENTAL)"
+         depends on CIFS && EXPERIMENTAL
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001732:2, b/test/corpora/lkml/cur/1382298770.001732:2,
new file mode 100644 (file)
index 0000000..8850953
--- /dev/null
@@ -0,0 +1,73 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 02/10] cifs: guard cifsglob.h against multiple inclusion
+Date: Tue, 22 Jun 2010 20:52:50 +0530
+Lines: 36
+Message-ID: <1277220170-3442-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-fsdevel-owner@vger.kernel.org Tue Jun 22 17:43:39 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OR5dT-0007sB-18
+       for lnx-linux-fsdevel@lo.gmane.org; Tue, 22 Jun 2010 17:43:39 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752441Ab0FVPn3 (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:29 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:41538 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751889Ab0FVPn2 (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:43:28 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:22:52 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001758>
+
+Add conditional compile macros to guard the header file against multiple
+inclusion.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cifsglob.h |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
+index a88479c..6b2c39d 100644
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -16,6 +16,9 @@
+  *   the GNU Lesser General Public License for more details.
+  *
+  */
++#ifndef _CIFS_GLOB_H
++#define _CIFS_GLOB_H
++
+ #include <linux/in.h>
+ #include <linux/in6.h>
+ #include <linux/slab.h>
+@@ -733,3 +736,5 @@ GLOBAL_EXTERN unsigned int cifs_min_small;  /* min size of small buf pool */
+ GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/
+ extern const struct slow_work_ops cifs_oplock_break_ops;
++
++#endif        /* _CIFS_GLOB_H */
+-- 
+1.6.4.2
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001733:2, b/test/corpora/lkml/cur/1382298770.001733:2,
new file mode 100644 (file)
index 0000000..d782f90
--- /dev/null
@@ -0,0 +1,211 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 03/10] cifs: register CIFS for caching
+Date: Tue, 22 Jun 2010 20:53:09 +0530
+Lines: 174
+Message-ID: <1277220189-3485-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:43:52 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5de-0007xC-Ov
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:43:51 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753125Ab0FVPnt (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:43:49 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:55866 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751261Ab0FVPnt (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:43:49 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:11 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001759>
+
+Define CIFS for FS-Cache and register for caching. Upon registration the
+top-level index object cookie will be stuck to the netfs definition by
+FS-Cache.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/Makefile  |    2 ++
+ fs/cifs/cache.c   |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsfs.c  |    8 ++++++++
+ fs/cifs/fscache.h |   40 ++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 103 insertions(+), 0 deletions(-)
+ create mode 100644 fs/cifs/cache.c
+ create mode 100644 fs/cifs/fscache.h
+
+diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile
+index 9948c00..e2de709 100644
+--- a/fs/cifs/Makefile
++++ b/fs/cifs/Makefile
+@@ -11,3 +11,5 @@ cifs-y := cifsfs.o cifssmb.o cifs_debug.o connect.o dir.o file.o inode.o \
+ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o
+ cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
++
++cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
+diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c
+new file mode 100644
+index 0000000..1080b96
+--- /dev/null
++++ b/fs/cifs/cache.c
+@@ -0,0 +1,53 @@
++/*
++ *   fs/cifs/cache.c - CIFS filesystem cache index structure definitions
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/mm.h>
++
++#include "fscache.h"
++#include "cifsglob.h"
++#include "cifs_debug.h"
++
++/*
++ * CIFS filesystem definition for FS-Cache
++ */
++struct fscache_netfs cifs_fscache_netfs = {
++      .name = "cifs",
++      .version = 0,
++};
++
++/*
++ * Register CIFS for caching with FS-Cache
++ */
++int cifs_fscache_register(void)
++{
++      return fscache_register_netfs(&cifs_fscache_netfs);
++}
++
++/*
++ * Unregister CIFS for caching
++ */
++void cifs_fscache_unregister(void)
++{
++      fscache_unregister_netfs(&cifs_fscache_netfs);
++}
++
+diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
+index 484e52b..c2a7aa9 100644
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -47,6 +47,7 @@
+ #include <linux/key-type.h>
+ #include "dns_resolve.h"
+ #include "cifs_spnego.h"
++#include "fscache.h"
+ #define CIFS_MAGIC_NUMBER 0xFF534D42  /* the first four bytes of SMB PDUs */
+ int cifsFYI = 0;
+@@ -902,6 +903,10 @@ init_cifs(void)
+               cFYI(1, "cifs_max_pending set to max of 256");
+       }
++      rc = cifs_fscache_register();
++      if (rc)
++              goto out;
++
+       rc = cifs_init_inodecache();
+       if (rc)
+               goto out_clean_proc;
+@@ -949,8 +954,10 @@ init_cifs(void)
+       cifs_destroy_mids();
+  out_destroy_inodecache:
+       cifs_destroy_inodecache();
++      cifs_fscache_unregister();
+  out_clean_proc:
+       cifs_proc_clean();
++ out:
+       return rc;
+ }
+@@ -959,6 +966,7 @@ exit_cifs(void)
+ {
+       cFYI(DBG2, "exit_cifs");
+       cifs_proc_clean();
++      cifs_fscache_unregister();
+ #ifdef CONFIG_CIFS_DFS_UPCALL
+       cifs_dfs_release_automount_timer();
+       unregister_key_type(&key_type_dns_resolver);
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+new file mode 100644
+index 0000000..cec9e2b
+--- /dev/null
++++ b/fs/cifs/fscache.h
+@@ -0,0 +1,40 @@
++/*
++ *   fs/cifs/fscache.h - CIFS filesystem cache interface definitions
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#ifndef _CIFS_FSCACHE_H
++#define _CIFS_FSCACHE_H
++
++#include <linux/fscache.h>
++#include "cifsglob.h"
++
++#ifdef CONFIG_CIFS_FSCACHE
++
++extern struct fscache_netfs cifs_fscache_netfs;
++
++extern int cifs_fscache_register(void);
++extern void cifs_fscache_unregister(void);
++
++#else /* CONFIG_CIFS_FSCACHE */
++static inline int cifs_fscache_register(void) { return 0; }
++static inline void cifs_fscache_unregister(void) {}
++
++#endif /* CONFIG_CIFS_FSCACHE */
++
++#endif /* _CIFS_FSCACHE_H */
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001734:2, b/test/corpora/lkml/cur/1382298770.001734:2,
new file mode 100644 (file)
index 0000000..4b64bc3
--- /dev/null
@@ -0,0 +1,223 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 04/10] cifs: define server-level cache index objects and register them with FS-Cache
+Date: Tue, 22 Jun 2010 20:53:18 +0530
+Lines: 186
+Message-ID: <1277220198-3522-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:44:26 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5eD-0008G7-KP
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:44:26 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753942Ab0FVPoC (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:02 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:58783 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751265Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:20 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001760>
+
+Define server-level cache index objects (as managed by TCP_ServerInfo structs).
+Each server object is created in the CIFS top-level index object and is itself
+an index into which superblock-level objects are inserted.
+
+Currently, the server objects are keyed by hostname.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/Makefile   |    2 +-
+ fs/cifs/cache.c    |   25 +++++++++++++++++++++++++
+ fs/cifs/cifsglob.h |    3 +++
+ fs/cifs/connect.c  |    4 ++++
+ fs/cifs/fscache.c  |   47 +++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h  |   12 ++++++++++++
+ 6 files changed, 92 insertions(+), 1 deletion(-)
+ create mode 100644 fs/cifs/fscache.c
+
+Index: cifs-2.6/fs/cifs/Makefile
+===================================================================
+--- cifs-2.6.orig/fs/cifs/Makefile
++++ cifs-2.6/fs/cifs/Makefile
+@@ -12,4 +12,4 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spneg
+ cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
+-cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
++cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -51,3 +51,28 @@ void cifs_fscache_unregister(void)
+       fscache_unregister_netfs(&cifs_fscache_netfs);
+ }
++/*
++ * Server object currently keyed by hostname
++ */
++static uint16_t cifs_server_get_key(const void *cookie_netfs_data,
++                                 void *buffer, uint16_t maxbuf)
++{
++      const struct TCP_Server_Info *server = cookie_netfs_data;
++      uint16_t len = strnlen(server->hostname, sizeof(server->hostname));
++
++      if (len > maxbuf)
++              return 0;
++
++      memcpy(buffer, server->hostname, len);
++
++      return len;
++}
++
++/*
++ * Server object for FS-Cache
++ */
++const struct fscache_cookie_def cifs_fscache_server_index_def = {
++      .name = "CIFS.server",
++      .type = FSCACHE_COOKIE_TYPE_INDEX,
++      .get_key = cifs_server_get_key,
++};
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -193,6 +193,9 @@ struct TCP_Server_Info {
+       bool    sec_mskerberos;         /* supports legacy MS Kerberos */
+       bool    sec_kerberosu2u;        /* supports U2U Kerberos */
+       bool    sec_ntlmssp;            /* supports NTLMSSP */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie   *fscache; /* client index cache cookie */
++#endif
+ };
+ /*
+Index: cifs-2.6/fs/cifs/connect.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/connect.c
++++ cifs-2.6/fs/cifs/connect.c
+@@ -48,6 +48,7 @@
+ #include "nterr.h"
+ #include "rfc1002pdu.h"
+ #include "cn_cifs.h"
++#include "fscache.h"
+ #define CIFS_PORT 445
+ #define RFC1001_PORT 139
+@@ -1453,6 +1454,8 @@ cifs_put_tcp_session(struct TCP_Server_I
+               return;
+       }
++      cifs_fscache_release_client_cookie(server);
++
+       list_del_init(&server->tcp_ses_list);
+       write_unlock(&cifs_tcp_ses_lock);
+@@ -1572,6 +1575,7 @@ cifs_get_tcp_session(struct smb_vol *vol
+               goto out_err;
+       }
++      cifs_fscache_get_client_cookie(tcp_ses);
+       /* thread spawned, put it on the list */
+       write_lock(&cifs_tcp_ses_lock);
+       list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- /dev/null
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -0,0 +1,47 @@
++/*
++ *   fs/cifs/fscache.c - CIFS filesystem cache interface
++ *
++ *   Copyright (c) 2010 Novell, Inc.
++ *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
++ *
++ *   This library is free software; you can redistribute it and/or modify
++ *   it under the terms of the GNU Lesser General Public License as published
++ *   by the Free Software Foundation; either version 2.1 of the License, or
++ *   (at your option) any later version.
++ *
++ *   This library 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 Lesser General Public License for more details.
++ *
++ *   You should have received a copy of the GNU Lesser General Public License
++ *   along with this library; if not, write to the Free Software
++ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/sched.h>
++#include <linux/mm.h>
++#include <linux/in6.h>
++
++#include "fscache.h"
++#include "cifsglob.h"
++#include "cifs_debug.h"
++
++void cifs_fscache_get_client_cookie(struct TCP_Server_Info *server)
++{
++      server->fscache =
++              fscache_acquire_cookie(cifs_fscache_netfs.primary_index,
++                              &cifs_fscache_server_index_def, server);
++      cFYI(1, "CIFS: get client cookie (0x%p/0x%p)\n",
++                              server, server->fscache);
++}
++
++void cifs_fscache_release_client_cookie(struct TCP_Server_Info *server)
++{
++      cFYI(1, "CIFS: release client cookie (0x%p/0x%p)\n",
++                              server, server->fscache);
++      fscache_relinquish_cookie(server->fscache, 0);
++      server->fscache = NULL;
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -27,14 +27,26 @@
+ #ifdef CONFIG_CIFS_FSCACHE
+ extern struct fscache_netfs cifs_fscache_netfs;
++extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
++/*
++ * fscache.c
++ */
++extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
++extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
++static inline void
++cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
++static inline void
++cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
++
+ #endif /* CONFIG_CIFS_FSCACHE */
+ #endif /* _CIFS_FSCACHE_H */
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001735:2, b/test/corpora/lkml/cur/1382298770.001735:2,
new file mode 100644 (file)
index 0000000..d76da35
--- /dev/null
@@ -0,0 +1,212 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 07/10] cifs: FS-Cache page management
+Date: Tue, 22 Jun 2010 20:53:48 +0530
+Lines: 175
+Message-ID: <1277220228-3635-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:44:27 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5eF-0008G7-BK
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:44:27 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754757Ab0FVPoS (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:18 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:54214 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752542Ab0FVPoB (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:01 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:50 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001761>
+
+Takes care of invalidation and release of FS-Cache marked pages and also
+invalidation of the FsCache page flag when the inode is removed.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cache.c   |   31 +++++++++++++++++++++++++++++++
+ fs/cifs/file.c    |   20 ++++++++++++++++++++
+ fs/cifs/fscache.c |   26 ++++++++++++++++++++++++++
+ fs/cifs/fscache.h |   16 ++++++++++++++++
+ 4 files changed, 93 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/cache.c b/fs/cifs/cache.c
+index b205424..3a733c1 100644
+--- a/fs/cifs/cache.c
++++ b/fs/cifs/cache.c
+@@ -210,6 +210,36 @@ fscache_checkaux cifs_fscache_inode_check_aux(void *cookie_netfs_data,
+       return FSCACHE_CHECKAUX_OKAY;
+ }
++static void cifs_fscache_inode_now_uncached(void *cookie_netfs_data)
++{
++      struct cifsInodeInfo *cifsi = cookie_netfs_data;
++      struct pagevec pvec;
++      pgoff_t first;
++      int loop, nr_pages;
++
++      pagevec_init(&pvec, 0);
++      first = 0;
++
++      cFYI(1, "cifs inode 0x%p now uncached\n", cifsi);
++
++      for (;;) {
++              nr_pages = pagevec_lookup(&pvec,
++                                        cifsi->vfs_inode.i_mapping, first,
++                                        PAGEVEC_SIZE - pagevec_count(&pvec));
++              if (!nr_pages)
++                      break;
++
++              for (loop = 0; loop < nr_pages; loop++)
++                      ClearPageFsCache(pvec.pages[loop]);
++
++              first = pvec.pages[nr_pages - 1]->index + 1;
++
++              pvec.nr = nr_pages;
++              pagevec_release(&pvec);
++              cond_resched();
++      }
++}
++
+ const struct fscache_cookie_def cifs_fscache_inode_object_def = {
+       .name           = "CIFS.uniqueid",
+       .type           = FSCACHE_COOKIE_TYPE_DATAFILE,
+@@ -217,4 +247,5 @@ const struct fscache_cookie_def cifs_fscache_inode_object_def = {
+       .get_attr       = cifs_fscache_inode_get_attr,
+       .get_aux        = cifs_fscache_inode_get_aux,
+       .check_aux      = cifs_fscache_inode_check_aux,
++      .now_uncached   = cifs_fscache_inode_now_uncached,
+ };
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 55ecb55..786ec04 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -2271,6 +2271,22 @@ out:
+       return rc;
+ }
++static int cifs_release_page(struct page *page, gfp_t gfp)
++{
++      if (PagePrivate(page))
++              return 0;
++
++      return cifs_fscache_release_page(page, gfp);
++}
++
++static void cifs_invalidate_page(struct page *page, unsigned long offset)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(page->mapping->host);
++
++      if (offset == 0)
++              cifs_fscache_invalidate_page(page, &cifsi->vfs_inode);
++}
++
+ static void
+ cifs_oplock_break(struct slow_work *work)
+ {
+@@ -2344,6 +2360,8 @@ const struct address_space_operations cifs_addr_ops = {
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
+       .set_page_dirty = __set_page_dirty_nobuffers,
++      .releasepage = cifs_release_page,
++      .invalidatepage = cifs_invalidate_page,
+       /* .sync_page = cifs_sync_page, */
+       /* .direct_IO = */
+ };
+@@ -2360,6 +2378,8 @@ const struct address_space_operations cifs_addr_ops_smallbuf = {
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
+       .set_page_dirty = __set_page_dirty_nobuffers,
++      .releasepage = cifs_release_page,
++      .invalidatepage = cifs_invalidate_page,
+       /* .sync_page = cifs_sync_page, */
+       /* .direct_IO = */
+ };
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index ddfd355..c09d3b8 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -130,3 +130,29 @@ void cifs_fscache_reset_inode_cookie(struct inode *inode)
+       }
+ }
++int cifs_fscache_release_page(struct page *page, gfp_t gfp)
++{
++      if (PageFsCache(page)) {
++              struct inode *inode = page->mapping->host;
++              struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++              cFYI(1, "CIFS: fscache release page (0x%p/0x%p)\n",
++                              cifsi->fscache, page);
++              if (!fscache_maybe_release_page(cifsi->fscache, page, gfp))
++                      return 0;
++      }
++
++      return 1;
++}
++
++void __cifs_fscache_invalidate_page(struct page *page, struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct fscache_cookie *cookie = cifsi->fscache;
++
++      cFYI(1, "CIFS: fscache invalidatepage (0x%p/0x%p/0x%p)\n",
++                      cookie, page, cifsi);
++      fscache_wait_on_page_write(cookie, page);
++      fscache_uncache_page(cookie, page);
++}
++
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index 836bb02..127cb0a 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -47,6 +47,16 @@ extern void cifs_fscache_release_inode_cookie(struct inode *);
+ extern void cifs_fscache_set_inode_cookie(struct inode *, struct file *);
+ extern void cifs_fscache_reset_inode_cookie(struct inode *);
++extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
++extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++
++static inline void cifs_fscache_invalidate_page(struct page *page,
++                                             struct inode *inode)
++{
++      if (PageFsCache(page))
++              __cifs_fscache_invalidate_page(page, inode);
++}
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -63,7 +73,13 @@ static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {}
+ static inline void cifs_fscache_set_inode_cookie(struct inode *inode,
+                       struct file *filp) {}
+ static inline void cifs_fscache_reset_inode_cookie(struct inode *inode) {}
++static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
++{
++      return 1; /* May release page */
++}
++static inline int cifs_fscache_invalidate_page(struct page *page,
++                      struct inode *) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001736:2, b/test/corpora/lkml/cur/1382298770.001736:2,
new file mode 100644 (file)
index 0000000..f972891
--- /dev/null
@@ -0,0 +1,256 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 09/10] cifs: read pages from FS-Cache
+Date: Tue, 22 Jun 2010 20:54:21 +0530
+Lines: 219
+Message-ID: <1277220261-3717-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:44:46 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5eX-0008O2-Q4
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:44:46 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752563Ab0FVPom (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:42 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:42741 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752542Ab0FVPok (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:40 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:24:22 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001762>
+
+Read pages from a FS-Cache data storage object into a CIFS inode.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/file.c    |   19 ++++++++++++++
+ fs/cifs/fscache.c |   73 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h |   40 ++++++++++++++++++++++++++++-
+ 3 files changed, 131 insertions(+), 1 deletions(-)
+
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 39c1ce0..42d2f25 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -1978,6 +1978,16 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+       cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
+       pTcon = cifs_sb->tcon;
++      /*
++       * Reads as many pages as possible from fscache. Returns -ENOBUFS
++       * immediately if the cookie is negative
++       */
++      rc = cifs_readpages_from_fscache(mapping->host, mapping, page_list,
++                                       &num_pages);
++      cFYI(1, "CIFS: readpages_from_fscache returned %d\n", rc);
++      if (rc == 0)
++              goto read_complete;
++
+       cFYI(DBG2, "rpages: num pages %d", num_pages);
+       for (i = 0; i < num_pages; ) {
+               unsigned contig_pages;
+@@ -2090,6 +2100,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+               smb_read_data = NULL;
+       }
++read_complete:
+       FreeXid(xid);
+       return rc;
+ }
+@@ -2100,6 +2111,12 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+       char *read_data;
+       int rc;
++      /* Is the page cached? */
++      rc = cifs_readpage_from_fscache(file->f_path.dentry->d_inode, page);
++      cFYI(1, "CIFS: cifs_readpage_from_fscache returned %d\n", rc);
++      if (rc == 0)
++              goto read_complete;
++
+       page_cache_get(page);
+       read_data = kmap(page);
+       /* for reads over a certain size could initiate async read ahead */
+@@ -2128,6 +2145,8 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+ io_error:
+       kunmap(page);
+       page_cache_release(page);
++
++read_complete:
+       return rc;
+ }
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index 13e47d5..6813737 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -145,6 +145,79 @@ int cifs_fscache_release_page(struct page *page, gfp_t gfp)
+       return 1;
+ }
++static void cifs_readpage_from_fscache_complete(struct page *page, void *ctx,
++                                              int error)
++{
++      cFYI(1, "CFS: readpage_from_fscache_complete (0x%p/%d)\n",
++                      page, error);
++      if (!error)
++              SetPageUptodate(page);
++      unlock_page(page);
++}
++
++/*
++ * Retrieve a page from FS-Cache
++ */
++int __cifs_readpage_from_fscache(struct inode *inode, struct page *page)
++{
++      int ret;
++
++      cFYI(1, "CIFS: readpage_from_fscache(fsc:%p, p:%p, i:0x%p\n",
++                      CIFS_I(inode)->fscache, page, inode);
++      ret = fscache_read_or_alloc_page(CIFS_I(inode)->fscache, page,
++                                       cifs_readpage_from_fscache_complete,
++                                       NULL,
++                                       GFP_KERNEL);
++      switch (ret) {
++
++      case 0: /* page found in fscache, read submitted */
++              cFYI(1, "CIFS: readpage_from_fscache: submitted\n");
++              return ret;
++      case -ENOBUFS:  /* page won't be cached */
++      case -ENODATA:  /* page not in cache */
++              cFYI(1, "CIFS: readpage_from_fscache %d\n", ret);
++              return 1;
++
++      default:
++              cFYI(1, "unknown error ret = %d", ret);
++      }
++      return ret;
++}
++
++/*
++ * Retrieve a set of pages from FS-Cache
++ */
++int __cifs_readpages_from_fscache(struct inode *inode,
++                              struct address_space *mapping,
++                              struct list_head *pages,
++                              unsigned *nr_pages)
++{
++      int ret;
++
++      cFYI(1, "CIFS: __cifs_readpages_from_fscache (0x%p/%u/0x%p)\n",
++                      CIFS_I(inode)->fscache, *nr_pages, inode);
++      ret = fscache_read_or_alloc_pages(CIFS_I(inode)->fscache, mapping,
++                                        pages, nr_pages,
++                                        cifs_readpage_from_fscache_complete,
++                                        NULL,
++                                        mapping_gfp_mask(mapping));
++      switch (ret) {
++      case 0: /* read submitted to the cache for all pages */
++              cFYI(1, "CIFS: readpages_from_fscache\n");
++              return ret;
++
++      case -ENOBUFS:  /* some pages are not cached and can't be */
++      case -ENODATA:  /* some pages are not cached */
++              cFYI(1, "CIFS: readpages_from_fscache: no page\n");
++              return 1;
++
++      default:
++              cFYI(1, "unknown error ret = %d", ret);
++      }
++
++      return ret;
++}
++
+ void __cifs_readpage_to_fscache(struct inode *inode, struct page *page)
+ {
+       int ret;
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index e34d8ab..03bd3fe 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -31,7 +31,6 @@ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_super_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_inode_object_def;
+-
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -49,6 +48,11 @@ extern void cifs_fscache_reset_inode_cookie(struct inode *);
+ extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
+ extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++extern int __cifs_readpage_from_fscache(struct inode *, struct page *);
++extern int __cifs_readpages_from_fscache(struct inode *,
++                                       struct address_space *,
++                                       struct list_head *,
++                                       unsigned *);
+ extern void __cifs_readpage_to_fscache(struct inode *, struct page *);
+@@ -59,6 +63,26 @@ static inline void cifs_fscache_invalidate_page(struct page *page,
+               __cifs_fscache_invalidate_page(page, inode);
+ }
++static inline int cifs_readpage_from_fscache(struct inode *inode,
++                                           struct page *page)
++{
++      if (CIFS_I(inode)->fscache)
++              return __cifs_readpage_from_fscache(inode, page);
++
++      return -ENOBUFS;
++}
++
++static inline int cifs_readpages_from_fscache(struct inode *inode,
++                                            struct address_space *mapping,
++                                            struct list_head *pages,
++                                            unsigned *nr_pages)
++{
++      if (CIFS_I(inode)->fscache)
++              return __cifs_readpages_from_fscache(inode, mapping, pages,
++                                                   nr_pages);
++      return -ENOBUFS;
++}
++
+ static inline void cifs_readpage_to_fscache(struct inode *inode,
+                                           struct page *page)
+ {
+@@ -89,6 +113,20 @@ static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
+ static inline int cifs_fscache_invalidate_page(struct page *page,
+                       struct inode *) {}
++static inline int
++cifs_readpage_from_fscache(struct inode *inode, struct page *page)
++{
++      return -ENOBUFS;
++}
++
++static inline int cifs_readpages_from_fscache(struct inode *inode,
++                                            struct address_space *mapping,
++                                            struct list_head *pages,
++                                            unsigned *nr_pages)
++{
++      return -ENOBUFS;
++}
++
+ static inline void cifs_readpage_to_fscache(struct inode *inode,
+                       struct page *page) {}
+-- 
+1.6.4.2
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001738:2, b/test/corpora/lkml/cur/1382298770.001738:2,
new file mode 100644 (file)
index 0000000..b1e0edf
--- /dev/null
@@ -0,0 +1,139 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 08/10] cifs: store pages into local cache
+Date: Tue, 22 Jun 2010 20:54:00 +0530
+Lines: 102
+Message-ID: <1277220240-3674-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-fsdevel-owner@vger.kernel.org Tue Jun 22 17:45:09 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OR5ev-00007O-6e
+       for lnx-linux-fsdevel@lo.gmane.org; Tue, 22 Jun 2010 17:45:09 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755015Ab0FVPon (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Tue, 22 Jun 2010 11:44:43 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:58250 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751265Ab0FVPok (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:40 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:24:02 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001764>
+
+Store pages from an CIFS inode into the data storage object associated with
+that inode.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/file.c    |    6 ++++++
+ fs/cifs/fscache.c |   13 +++++++++++++
+ fs/cifs/fscache.h |   11 +++++++++++
+ 3 files changed, 30 insertions(+), 0 deletions(-)
+
+diff --git a/fs/cifs/file.c b/fs/cifs/file.c
+index 786ec04..39c1ce0 100644
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -2060,6 +2060,8 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
+                                  we will hit it on next read */
+                               /* break; */
++                              /* send this page to FS-Cache */
++                              cifs_readpage_to_fscache(mapping->host, page);
+                       }
+               } else {
+                       cFYI(1, "No bytes read (%d) at offset %lld . "
+@@ -2117,6 +2119,10 @@ static int cifs_readpage_worker(struct file *file, struct page *page,
+       flush_dcache_page(page);
+       SetPageUptodate(page);
++
++      /* send this page to the cache */
++      cifs_readpage_to_fscache(file->f_path.dentry->d_inode, page);
++
+       rc = 0;
+ io_error:
+diff --git a/fs/cifs/fscache.c b/fs/cifs/fscache.c
+index c09d3b8..13e47d5 100644
+--- a/fs/cifs/fscache.c
++++ b/fs/cifs/fscache.c
+@@ -145,6 +145,19 @@ int cifs_fscache_release_page(struct page *page, gfp_t gfp)
+       return 1;
+ }
++void __cifs_readpage_to_fscache(struct inode *inode, struct page *page)
++{
++      int ret;
++
++      cFYI(1, "CIFS: readpage_to_fscache(fsc: %p, p: %p, i: %p\n",
++                      CIFS_I(inode)->fscache, page, inode);
++      ret = fscache_write_page(CIFS_I(inode)->fscache, page, GFP_KERNEL);
++      cFYI(1, "CIFS: fscache_write_page returned %d\n", ret);
++
++      if (ret != 0)
++              fscache_uncache_page(CIFS_I(inode)->fscache, page);
++}
++
+ void __cifs_fscache_invalidate_page(struct page *page, struct inode *inode)
+ {
+       struct cifsInodeInfo *cifsi = CIFS_I(inode);
+diff --git a/fs/cifs/fscache.h b/fs/cifs/fscache.h
+index 127cb0a..e34d8ab 100644
+--- a/fs/cifs/fscache.h
++++ b/fs/cifs/fscache.h
+@@ -50,6 +50,8 @@ extern void cifs_fscache_reset_inode_cookie(struct inode *);
+ extern void __cifs_fscache_invalidate_page(struct page *, struct inode *);
+ extern int cifs_fscache_release_page(struct page *page, gfp_t gfp);
++extern void __cifs_readpage_to_fscache(struct inode *, struct page *);
++
+ static inline void cifs_fscache_invalidate_page(struct page *page,
+                                              struct inode *inode)
+ {
+@@ -57,6 +59,13 @@ static inline void cifs_fscache_invalidate_page(struct page *page,
+               __cifs_fscache_invalidate_page(page, inode);
+ }
++static inline void cifs_readpage_to_fscache(struct inode *inode,
++                                          struct page *page)
++{
++      if (PageFsCache(page))
++              __cifs_readpage_to_fscache(inode, page);
++}
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -80,6 +89,8 @@ static inline void cifs_fscache_release_page(struct page *page, gfp_t gfp)
+ static inline int cifs_fscache_invalidate_page(struct page *page,
+                       struct inode *) {}
++static inline void cifs_readpage_to_fscache(struct inode *inode,
++                      struct page *page) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+-- 
+1.6.4.2
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001739:2, b/test/corpora/lkml/cur/1382298770.001739:2,
new file mode 100644 (file)
index 0000000..d0abda0
--- /dev/null
@@ -0,0 +1,355 @@
+From: Suresh Jayaraman <sjayaraman@suse.de>
+Subject: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Tue, 22 Jun 2010 20:53:33 +0530
+Lines: 318
+Message-ID: <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, David Howells <dhowells@redhat.com>
+To: Steve French <smfrench@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Jun 22 17:45:30 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OR5fF-0000Ka-Na
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 22 Jun 2010 17:45:30 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755952Ab0FVPpP (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 22 Jun 2010 11:45:15 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:59441 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751397Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman@novell.com:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:35 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001765>
+
+Define inode-level data storage objects (managed by cifsInodeInfo structs).
+Each inode-level object is created in a super-block level object and is itself
+a data storage object in to which pages from the inode are stored.
+
+The inode object is keyed by UniqueId. The coherency data being used is
+LastWriteTime and the file size.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+---
+ fs/cifs/cache.c    |   80 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsfs.c   |    7 ++++
+ fs/cifs/cifsglob.h |    3 +
+ fs/cifs/file.c     |    6 +++
+ fs/cifs/fscache.c  |   68 +++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/fscache.h  |   12 +++++++
+ fs/cifs/inode.c    |    4 ++
+ 7 files changed, 180 insertions(+)
+
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -138,3 +138,83 @@ const struct fscache_cookie_def cifs_fsc
+       .get_key = cifs_super_get_key,
+ };
++/*
++ * Auxiliary data attached to CIFS inode within the cache
++ */
++struct cifs_fscache_inode_auxdata {
++      struct timespec last_write_time;
++      loff_t          size;
++};
++
++static uint16_t cifs_fscache_inode_get_key(const void *cookie_netfs_data,
++                                         void *buffer, uint16_t maxbuf)
++{
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++      uint16_t keylen;
++
++      /* use the UniqueId as the key */
++      keylen = sizeof(cifsi->uniqueid);
++      if (keylen > maxbuf)
++              keylen = 0;
++      else
++              memcpy(buffer, &cifsi->uniqueid, keylen);
++
++      return keylen;
++}
++
++static void
++cifs_fscache_inode_get_attr(const void *cookie_netfs_data, uint64_t *size)
++{
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      *size = cifsi->vfs_inode.i_size;
++}
++
++static uint16_t
++cifs_fscache_inode_get_aux(const void *cookie_netfs_data, void *buffer,
++                         uint16_t maxbuf)
++{
++      struct cifs_fscache_inode_auxdata auxdata;
++      const struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      memset(&auxdata, 0, sizeof(auxdata));
++      auxdata.size = cifsi->vfs_inode.i_size;
++      auxdata.last_write_time = cifsi->vfs_inode.i_ctime;
++
++      if (maxbuf > sizeof(auxdata))
++              maxbuf = sizeof(auxdata);
++
++      memcpy(buffer, &auxdata, maxbuf);
++
++      return maxbuf;
++}
++
++static enum
++fscache_checkaux cifs_fscache_inode_check_aux(void *cookie_netfs_data,
++                                            const void *data,
++                                            uint16_t datalen)
++{
++      struct cifs_fscache_inode_auxdata auxdata;
++      struct cifsInodeInfo *cifsi = cookie_netfs_data;
++
++      if (datalen != sizeof(auxdata))
++              return FSCACHE_CHECKAUX_OBSOLETE;
++
++      memset(&auxdata, 0, sizeof(auxdata));
++      auxdata.size = cifsi->vfs_inode.i_size;
++      auxdata.last_write_time = cifsi->vfs_inode.i_ctime;
++
++      if (memcmp(data, &auxdata, datalen) != 0)
++              return FSCACHE_CHECKAUX_OBSOLETE;
++
++      return FSCACHE_CHECKAUX_OKAY;
++}
++
++const struct fscache_cookie_def cifs_fscache_inode_object_def = {
++      .name           = "CIFS.uniqueid",
++      .type           = FSCACHE_COOKIE_TYPE_DATAFILE,
++      .get_key        = cifs_fscache_inode_get_key,
++      .get_attr       = cifs_fscache_inode_get_attr,
++      .get_aux        = cifs_fscache_inode_get_aux,
++      .check_aux      = cifs_fscache_inode_check_aux,
++};
+Index: cifs-2.6/fs/cifs/cifsfs.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsfs.c
++++ cifs-2.6/fs/cifs/cifsfs.c
+@@ -330,6 +330,12 @@ cifs_destroy_inode(struct inode *inode)
+ }
+ static void
++cifs_clear_inode(struct inode *inode)
++{
++      cifs_fscache_release_inode_cookie(inode);
++}
++
++static void
+ cifs_show_address(struct seq_file *s, struct TCP_Server_Info *server)
+ {
+       seq_printf(s, ",addr=");
+@@ -490,6 +496,7 @@ static const struct super_operations cif
+       .alloc_inode = cifs_alloc_inode,
+       .destroy_inode = cifs_destroy_inode,
+       .drop_inode     = cifs_drop_inode,
++      .clear_inode    = cifs_clear_inode,
+ /*    .delete_inode   = cifs_delete_inode,  */  /* Do not need above
+       function unless later we add lazy close of inodes or unless the
+       kernel forgets to call us with the same number of releases (closes)
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -407,6 +407,9 @@ struct cifsInodeInfo {
+       bool invalid_mapping:1;         /* pagecache is invalid */
+       u64  server_eof;                /* current file size on server */
+       u64  uniqueid;                  /* server inode number */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie *fscache;
++#endif
+       struct inode vfs_inode;
+ };
+Index: cifs-2.6/fs/cifs/file.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/file.c
++++ cifs-2.6/fs/cifs/file.c
+@@ -40,6 +40,7 @@
+ #include "cifs_unicode.h"
+ #include "cifs_debug.h"
+ #include "cifs_fs_sb.h"
++#include "fscache.h"
+ static inline int cifs_convert_flags(unsigned int flags)
+ {
+@@ -282,6 +283,9 @@ int cifs_open(struct inode *inode, struc
+                               CIFSSMBClose(xid, tcon, netfid);
+                               rc = -ENOMEM;
+                       }
++
++                      cifs_fscache_set_inode_cookie(inode, file);
++
+                       goto out;
+               } else if ((rc == -EINVAL) || (rc == -EOPNOTSUPP)) {
+                       if (tcon->ses->serverNOS)
+@@ -373,6 +377,8 @@ int cifs_open(struct inode *inode, struc
+               goto out;
+       }
++      cifs_fscache_set_inode_cookie(inode, file);
++
+       if (oplock & CIFS_CREATE_ACTION) {
+               /* time to set mode which we can not set earlier due to
+                  problems creating new read-only files */
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.c
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -62,3 +62,71 @@ void cifs_fscache_release_super_cookie(s
+       tcon->fscache = NULL;
+ }
++static void cifs_fscache_enable_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
++
++      if (cifsi->fscache)
++              return;
++
++      cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache,
++                              &cifs_fscache_inode_object_def,
++                              cifsi);
++      cFYI(1, "CIFS: got FH cookie (0x%p/0x%p/0x%p)\n",
++                      cifs_sb->tcon, cifsi, cifsi->fscache);
++}
++
++void cifs_fscache_release_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++      if (cifsi->fscache) {
++              cFYI(1, "CIFS releasing inode cookie (0x%p/0x%p)\n",
++                              cifsi, cifsi->fscache);
++              fscache_relinquish_cookie(cifsi->fscache, 0);
++              cifsi->fscache = NULL;
++      }
++}
++
++static void cifs_fscache_disable_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++
++      if (cifsi->fscache) {
++              cFYI(1, "CIFS disabling inode cookie (0x%p/0x%p)\n",
++                              cifsi, cifsi->fscache);
++              fscache_relinquish_cookie(cifsi->fscache, 1);
++              cifsi->fscache = NULL;
++      }
++}
++
++void cifs_fscache_set_inode_cookie(struct inode *inode, struct file *filp)
++{
++      /* BB: parallel opens - need locking? */
++      if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
++              cifs_fscache_disable_inode_cookie(inode);
++      else {
++              cifs_fscache_enable_inode_cookie(inode);
++              cFYI(1, "CIFS: fscache inode cookie set\n");
++      }
++}
++
++void cifs_fscache_reset_inode_cookie(struct inode *inode)
++{
++      struct cifsInodeInfo *cifsi = CIFS_I(inode);
++      struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
++      struct fscache_cookie *old = cifsi->fscache;
++
++      if (cifsi->fscache) {
++              /* retire the current fscache cache and get a new one */
++              fscache_relinquish_cookie(cifsi->fscache, 1);
++
++              cifsi->fscache = fscache_acquire_cookie(cifs_sb->tcon->fscache,
++                                      &cifs_fscache_inode_object_def,
++                                      cifsi);
++              cFYI(1, "CIFS: new cookie (0x%p/0x%p) oldcookie 0x%p\n",
++                              cifsi, cifsi->fscache, old);
++      }
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -29,6 +29,8 @@
+ extern struct fscache_netfs cifs_fscache_netfs;
+ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+ extern const struct fscache_cookie_def cifs_fscache_super_index_def;
++extern const struct fscache_cookie_def cifs_fscache_inode_object_def;
++
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -41,6 +43,10 @@ extern void cifs_fscache_release_client_
+ extern void cifs_fscache_get_super_cookie(struct cifsTconInfo *);
+ extern void cifs_fscache_release_super_cookie(struct cifsTconInfo *);
++extern void cifs_fscache_release_inode_cookie(struct inode *);
++extern void cifs_fscache_set_inode_cookie(struct inode *, struct file *);
++extern void cifs_fscache_reset_inode_cookie(struct inode *);
++
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+ static inline void cifs_fscache_unregister(void) {}
+@@ -53,6 +59,12 @@ static inline void cifs_fscache_get_supe
+ static inline void
+ cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) {}
++static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {}
++static inline void cifs_fscache_set_inode_cookie(struct inode *inode,
++                      struct file *filp) {}
++static inline void cifs_fscache_reset_inode_cookie(struct inode *inode) {}
++
++
+ #endif /* CONFIG_CIFS_FSCACHE */
+ #endif /* _CIFS_FSCACHE_H */
+Index: cifs-2.6/fs/cifs/inode.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/inode.c
++++ cifs-2.6/fs/cifs/inode.c
+@@ -29,6 +29,7 @@
+ #include "cifsproto.h"
+ #include "cifs_debug.h"
+ #include "cifs_fs_sb.h"
++#include "fscache.h"
+ static void cifs_set_ops(struct inode *inode, const bool is_dfs_referral)
+@@ -776,6 +777,8 @@ retry_iget5_locked:
+                       inode->i_flags |= S_NOATIME | S_NOCMTIME;
+               if (inode->i_state & I_NEW) {
+                       inode->i_ino = hash;
++                      /* initialize per-inode cache cookie pointer */
++                      CIFS_I(inode)->fscache = NULL;
+                       unlock_new_inode(inode);
+               }
+       }
+@@ -1568,6 +1571,7 @@ cifs_invalidate_mapping(struct inode *in
+                       cifs_i->write_behind_rc = rc;
+       }
+       invalidate_remote_inode(inode);
++      cifs_fscache_reset_inode_cookie(inode);
+ }
+ int cifs_revalidate_file(struct file *filp)
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001740:2, b/test/corpora/lkml/cur/1382298770.001740:2,
new file mode 100644 (file)
index 0000000..ef0f657
--- /dev/null
@@ -0,0 +1,214 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Tue, 22 Jun 2010 20:53:26 +0530
+Lines: 177
+Message-ID: <1277220206-3559-1-git-send-email-sjayaraman@suse.de>
+References: <yes>
+Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 17:45:50 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OR5fZ-0000Vj-Mj
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 17:45:50 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752511Ab0FVPpJ (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 11:45:09 -0400
+Received: from victor.provo.novell.com ([137.65.250.26]:56189 "EHLO
+       victor.provo.novell.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752441Ab0FVPoA (ORCPT
+       <rfc822;groupwise-SJayaraman-Et1tbQHTxzrQT0dZR+AlfA@public.gmane.org:0:0>);
+       Tue, 22 Jun 2010 11:44:00 -0400
+Received: from localhost (prv-ext-foundry1int.gns.novell.com [137.65.251.240])
+       by victor.provo.novell.com with ESMTP; Tue, 22 Jun 2010 09:23:29 -0600
+X-Mailer: git-send-email 1.6.4.2
+In-Reply-To: <yes>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001766>
+
+Define superblock-level cache index objects (managed by cifsTconInfo structs).
+Each superblock object is created in a server-level index object and in itself
+an index into which inode-level objects are inserted.
+
+Currently, the superblock objects are keyed by sharename.
+
+Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+---
+ fs/cifs/cache.c    |   62 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ fs/cifs/cifsglob.h |    3 ++
+ fs/cifs/connect.c  |    4 +++
+ fs/cifs/fscache.c  |   17 ++++++++++++++
+ fs/cifs/fscache.h  |    6 +++++
+ 5 files changed, 92 insertions(+)
+
+Index: cifs-2.6/fs/cifs/cache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cache.c
++++ cifs-2.6/fs/cifs/cache.c
+@@ -76,3 +76,65 @@ const struct fscache_cookie_def cifs_fsc
+       .type = FSCACHE_COOKIE_TYPE_INDEX,
+       .get_key = cifs_server_get_key,
+ };
++
++static char *extract_sharename(const char *treename)
++{
++      const char *src;
++      char *delim, *dst;
++      int len;
++
++      /* skip double chars at the beginning */
++      src = treename + 2;
++
++      /* share name is always preceded by '\\' now */
++      delim = strchr(src, '\\');
++      if (!delim)
++              return ERR_PTR(-EINVAL);
++      delim++;
++      len = strlen(delim);
++
++      /* caller has to free the memory */
++      dst = kstrndup(delim, len, GFP_KERNEL);
++      if (!dst)
++              return ERR_PTR(-ENOMEM);
++
++      return dst;
++}
++
++/*
++ * Superblock object currently keyed by share name
++ */
++static uint16_t cifs_super_get_key(const void *cookie_netfs_data, void *buffer,
++                                 uint16_t maxbuf)
++{
++      const struct cifsTconInfo *tcon = cookie_netfs_data;
++      char *sharename;
++      uint16_t len;
++
++      sharename = extract_sharename(tcon->treeName);
++      if (IS_ERR(sharename)) {
++              cFYI(1, "CIFS: couldn't extract sharename\n");
++              sharename = NULL;
++              return 0;
++      }
++
++      len = strlen(sharename);
++      if (len > maxbuf)
++              return 0;
++
++      memcpy(buffer, sharename, len);
++
++      kfree(sharename);
++
++      return len;
++}
++
++/*
++ * Superblock object for FS-Cache
++ */
++const struct fscache_cookie_def cifs_fscache_super_index_def = {
++      .name = "CIFS.super",
++      .type = FSCACHE_COOKIE_TYPE_INDEX,
++      .get_key = cifs_super_get_key,
++};
++
+Index: cifs-2.6/fs/cifs/cifsglob.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/cifsglob.h
++++ cifs-2.6/fs/cifs/cifsglob.h
+@@ -317,6 +317,9 @@ struct cifsTconInfo {
+       bool local_lease:1; /* check leases (only) on local system not remote */
+       bool broken_posix_open; /* e.g. Samba server versions < 3.3.2, 3.2.9 */
+       bool need_reconnect:1; /* connection reset, tid now invalid */
++#ifdef CONFIG_CIFS_FSCACHE
++      struct fscache_cookie *fscache; /* cookie for share */
++#endif
+       /* BB add field for back pointer to sb struct(s)? */
+ };
+Index: cifs-2.6/fs/cifs/connect.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/connect.c
++++ cifs-2.6/fs/cifs/connect.c
+@@ -1773,6 +1773,8 @@ cifs_put_tcon(struct cifsTconInfo *tcon)
+       list_del_init(&tcon->tcon_list);
+       write_unlock(&cifs_tcp_ses_lock);
++      cifs_fscache_release_super_cookie(tcon);
++
+       xid = GetXid();
+       CIFSSMBTDis(xid, tcon);
+       _FreeXid(xid);
+@@ -1843,6 +1845,8 @@ cifs_get_tcon(struct cifsSesInfo *ses, s
+       tcon->nocase = volume_info->nocase;
+       tcon->local_lease = volume_info->local_lease;
++      cifs_fscache_get_super_cookie(tcon);
++
+       write_lock(&cifs_tcp_ses_lock);
+       list_add(&tcon->tcon_list, &ses->tcon_list);
+       write_unlock(&cifs_tcp_ses_lock);
+Index: cifs-2.6/fs/cifs/fscache.c
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.c
++++ cifs-2.6/fs/cifs/fscache.c
+@@ -45,3 +45,20 @@ void cifs_fscache_release_client_cookie(
+       server->fscache = NULL;
+ }
++void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon)
++{
++      tcon->fscache =
++              fscache_acquire_cookie(tcon->ses->server->fscache,
++                              &cifs_fscache_super_index_def, tcon);
++      cFYI(1, "CIFS: get superblock cookie (0x%p/0x%p)\n",
++                              tcon, tcon->fscache);
++}
++
++void cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon)
++{
++      cFYI(1, "CIFS: releasing superblock cookie (0x%p/0x%p)\n",
++                      tcon, tcon->fscache);
++      fscache_relinquish_cookie(tcon->fscache, 0);
++      tcon->fscache = NULL;
++}
++
+Index: cifs-2.6/fs/cifs/fscache.h
+===================================================================
+--- cifs-2.6.orig/fs/cifs/fscache.h
++++ cifs-2.6/fs/cifs/fscache.h
+@@ -28,6 +28,7 @@
+ extern struct fscache_netfs cifs_fscache_netfs;
+ extern const struct fscache_cookie_def cifs_fscache_server_index_def;
++extern const struct fscache_cookie_def cifs_fscache_super_index_def;
+ extern int cifs_fscache_register(void);
+ extern void cifs_fscache_unregister(void);
+@@ -37,6 +38,8 @@ extern void cifs_fscache_unregister(void
+  */
+ extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
+ extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
++extern void cifs_fscache_get_super_cookie(struct cifsTconInfo *);
++extern void cifs_fscache_release_super_cookie(struct cifsTconInfo *);
+ #else /* CONFIG_CIFS_FSCACHE */
+ static inline int cifs_fscache_register(void) { return 0; }
+@@ -46,6 +49,9 @@ static inline void
+ cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
+ static inline void
+ cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
++static inline void cifs_fscache_get_super_cookie(struct cifsTconInfo *tcon) {}
++static inline void
++cifs_fscache_release_super_cookie(struct cifsTconInfo *tcon) {}
+ #endif /* CONFIG_CIFS_FSCACHE */
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001887:2, b/test/corpora/lkml/cur/1382298770.001887:2,
new file mode 100644 (file)
index 0000000..8129048
--- /dev/null
@@ -0,0 +1,85 @@
+From: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+Subject: Re: [RFC][PATCH 02/10] cifs: guard cifsglob.h against multiple
+ inclusion
+Date: Tue, 22 Jun 2010 17:37:42 -0400
+Lines: 35
+Message-ID: <20100622173742.448e1e94@corrin.poochiereds.net>
+References: <yes>
+       <1277220170-3442-1-git-send-email-sjayaraman@suse.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 23:36:08 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORB8Z-00027v-R8
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 23:36:08 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751663Ab0FVVfq (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 17:35:46 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.121]:46190 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751933Ab0FVVfo (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Tue, 22 Jun 2010 17:35:44 -0400
+X-Authority-Analysis: v=1.0 c=1 a=Y4kVDsoNLLAA:10 a=yQWWgrYGNuUA:10 a=kj9zAlcOel0A:10 a=hGzw-44bAAAA:8 a=6UT2YofcClCzWf3PPoQA:9 a=Ipo6nwFRv7ENfF13HvmH_iG48b8A:4 a=CjuIK1q_8ugA:10 a=0kPLrQdw3YYA:10 a=dowx1zmaLagA:10
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:49036] helo=mail.poochiereds.net)
+       by cdptpa-oedge01.mail.rr.com (envelope-from <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id 29/22-24471-DAC212C4; Tue, 22 Jun 2010 21:35:42 +0000
+Received: from corrin.poochiereds.net (unknown [65.88.2.5])
+       by mail.poochiereds.net (Postfix) with ESMTPSA id 1C5A1580F4;
+       Tue, 22 Jun 2010 17:35:41 -0400 (EDT)
+In-Reply-To: <1277220170-3442-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001913>
+
+On Tue, 22 Jun 2010 20:52:50 +0530
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Add conditional compile macros to guard the header file against multiple
+> inclusion.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> ---
+>  fs/cifs/cifsglob.h |    5 +++++
+>  1 files changed, 5 insertions(+), 0 deletions(-)
+> 
+> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
+> index a88479c..6b2c39d 100644
+> --- a/fs/cifs/cifsglob.h
+> +++ b/fs/cifs/cifsglob.h
+> @@ -16,6 +16,9 @@
+>   *   the GNU Lesser General Public License for more details.
+>   *
+>   */
+> +#ifndef _CIFS_GLOB_H
+> +#define _CIFS_GLOB_H
+> +
+>  #include <linux/in.h>
+>  #include <linux/in6.h>
+>  #include <linux/slab.h>
+> @@ -733,3 +736,5 @@ GLOBAL_EXTERN unsigned int cifs_min_small;  /* min size of small buf pool */
+>  GLOBAL_EXTERN unsigned int cifs_max_pending; /* MAX requests at once to server*/
+>  
+>  extern const struct slow_work_ops cifs_oplock_break_ops;
+> +
+> +#endif      /* _CIFS_GLOB_H */
+
+Strong ACK
+
+Acked-by: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298770.001892:2, b/test/corpora/lkml/cur/1382298770.001892:2,
new file mode 100644 (file)
index 0000000..82603bf
--- /dev/null
@@ -0,0 +1,254 @@
+From: Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>
+Subject: Re: [RFC][PATCH 04/10] cifs: define server-level cache index
+ objects and register them with FS-Cache
+Date: Tue, 22 Jun 2010 17:52:14 -0400
+Lines: 204
+Message-ID: <20100622175214.4c56234f@corrin.poochiereds.net>
+References: <yes>
+       <1277220198-3522-1-git-send-email-sjayaraman@suse.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Tue Jun 22 23:50:23 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORBMJ-0005WJ-Lj
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Tue, 22 Jun 2010 23:50:20 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1750777Ab0FVVuS (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Tue, 22 Jun 2010 17:50:18 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.120]:55670 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1750749Ab0FVVuR (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Tue, 22 Jun 2010 17:50:17 -0400
+X-Authority-Analysis: v=1.1 cv=8MuG1bpxLlSbaYWWtODGdBCK7StbFcRsMXhWm1NVx/I= c=1 sm=0 a=wpY4Lvx3kJcA:10 a=UBIxAjGgU1YA:10 a=kj9zAlcOel0A:10 a=ld/erqUjW76FpBUqCqkKeA==:17 a=VwQbUJbxAAAA:8 a=qYub2k57AAAA:8 a=uYIlwBZcjrF9BUCsR4kA:9 a=OO1ZLbZb6q4TPdC5pcAA:7 a=jFshslHAf8hJVDYUYRlYN4n-w5YA:4 a=CjuIK1q_8ugA:10 a=x8gzFH9gYPwA:10 a=0kPLrQdw3YYA:10 a=jBoGP612-tUA:10 a=t5DF_bUGhurCx8LQ:21 a=W6P_Gh1y2IibdbqZ:21 a=ld/erqUjW76FpBUqCqkKeA==:117
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:59154] helo=mail.poochiereds.net)
+       by cdptpa-oedge03.mail.rr.com (envelope-from <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id AC/10-00502-710312C4; Tue, 22 Jun 2010 21:50:16 +0000
+Received: from corrin.poochiereds.net (unknown [65.88.2.5])
+       by mail.poochiereds.net (Postfix) with ESMTPSA id 03B11580F4;
+       Tue, 22 Jun 2010 17:50:14 -0400 (EDT)
+In-Reply-To: <1277220198-3522-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1001918>
+
+On Tue, 22 Jun 2010 20:53:18 +0530
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define server-level cache index objects (as managed by TCP_ServerInfo structs).
+> Each server object is created in the CIFS top-level index object and is itself
+> an index into which superblock-level objects are inserted.
+> 
+> Currently, the server objects are keyed by hostname.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> ---
+>  fs/cifs/Makefile   |    2 +-
+>  fs/cifs/cache.c    |   25 +++++++++++++++++++++++++
+>  fs/cifs/cifsglob.h |    3 +++
+>  fs/cifs/connect.c  |    4 ++++
+>  fs/cifs/fscache.c  |   47 +++++++++++++++++++++++++++++++++++++++++++++++
+>  fs/cifs/fscache.h  |   12 ++++++++++++
+>  6 files changed, 92 insertions(+), 1 deletion(-)
+>  create mode 100644 fs/cifs/fscache.c
+> 
+> Index: cifs-2.6/fs/cifs/Makefile
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/Makefile
+> +++ cifs-2.6/fs/cifs/Makefile
+> @@ -12,4 +12,4 @@ cifs-$(CONFIG_CIFS_UPCALL) += cifs_spneg
+>  
+>  cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o
+>  
+> -cifs-$(CONFIG_CIFS_FSCACHE) += cache.o
+> +cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o
+> Index: cifs-2.6/fs/cifs/cache.c
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/cache.c
+> +++ cifs-2.6/fs/cifs/cache.c
+> @@ -51,3 +51,28 @@ void cifs_fscache_unregister(void)
+>      fscache_unregister_netfs(&cifs_fscache_netfs);
+>  }
+>  
+> +/*
+> + * Server object currently keyed by hostname
+> + */
+> +static uint16_t cifs_server_get_key(const void *cookie_netfs_data,
+> +                               void *buffer, uint16_t maxbuf)
+> +{
+> +    const struct TCP_Server_Info *server = cookie_netfs_data;
+> +    uint16_t len = strnlen(server->hostname, sizeof(server->hostname));
+> +
+
+Would a tuple of address/family/port be a better choice here? Imagine I
+mount "foo" and then later mount "foor.bar.baz". If they are the same
+address and only the UNC differs, then you won't get the benefit of
+the cache, right?
+
+> +    if (len > maxbuf)
+> +            return 0;
+> +
+> +    memcpy(buffer, server->hostname, len);
+> +
+> +    return len;
+> +}
+> +
+> +/*
+> + * Server object for FS-Cache
+> + */
+> +const struct fscache_cookie_def cifs_fscache_server_index_def = {
+> +    .name = "CIFS.server",
+> +    .type = FSCACHE_COOKIE_TYPE_INDEX,
+> +    .get_key = cifs_server_get_key,
+> +};
+> Index: cifs-2.6/fs/cifs/cifsglob.h
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/cifsglob.h
+> +++ cifs-2.6/fs/cifs/cifsglob.h
+> @@ -193,6 +193,9 @@ struct TCP_Server_Info {
+>      bool    sec_mskerberos;         /* supports legacy MS Kerberos */
+>      bool    sec_kerberosu2u;        /* supports U2U Kerberos */
+>      bool    sec_ntlmssp;            /* supports NTLMSSP */
+> +#ifdef CONFIG_CIFS_FSCACHE
+> +    struct fscache_cookie   *fscache; /* client index cache cookie */
+> +#endif
+>  };
+>  
+>  /*
+> Index: cifs-2.6/fs/cifs/connect.c
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/connect.c
+> +++ cifs-2.6/fs/cifs/connect.c
+> @@ -48,6 +48,7 @@
+>  #include "nterr.h"
+>  #include "rfc1002pdu.h"
+>  #include "cn_cifs.h"
+> +#include "fscache.h"
+>  
+>  #define CIFS_PORT 445
+>  #define RFC1001_PORT 139
+> @@ -1453,6 +1454,8 @@ cifs_put_tcp_session(struct TCP_Server_I
+>              return;
+>      }
+>  
+> +    cifs_fscache_release_client_cookie(server);
+> +
+>      list_del_init(&server->tcp_ses_list);
+>      write_unlock(&cifs_tcp_ses_lock);
+>  
+> @@ -1572,6 +1575,7 @@ cifs_get_tcp_session(struct smb_vol *vol
+>              goto out_err;
+>      }
+>  
+> +    cifs_fscache_get_client_cookie(tcp_ses);
+>      /* thread spawned, put it on the list */
+>      write_lock(&cifs_tcp_ses_lock);
+>      list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
+> Index: cifs-2.6/fs/cifs/fscache.c
+> ===================================================================
+> --- /dev/null
+> +++ cifs-2.6/fs/cifs/fscache.c
+> @@ -0,0 +1,47 @@
+> +/*
+> + *   fs/cifs/fscache.c - CIFS filesystem cache interface
+> + *
+> + *   Copyright (c) 2010 Novell, Inc.
+> + *   Authors(s): Suresh Jayaraman (sjayaraman-l3A5Bk7waGM@public.gmane.org>
+> + *
+> + *   This library is free software; you can redistribute it and/or modify
+> + *   it under the terms of the GNU Lesser General Public License as published
+> + *   by the Free Software Foundation; either version 2.1 of the License, or
+> + *   (at your option) any later version.
+> + *
+> + *   This library 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 Lesser General Public License for more details.
+> + *
+> + *   You should have received a copy of the GNU Lesser General Public License
+> + *   along with this library; if not, write to the Free Software
+> + *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+> + */
+> +#include <linux/init.h>
+> +#include <linux/kernel.h>
+> +#include <linux/sched.h>
+> +#include <linux/mm.h>
+> +#include <linux/in6.h>
+> +
+> +#include "fscache.h"
+> +#include "cifsglob.h"
+> +#include "cifs_debug.h"
+> +
+> +void cifs_fscache_get_client_cookie(struct TCP_Server_Info *server)
+> +{
+> +    server->fscache =
+> +            fscache_acquire_cookie(cifs_fscache_netfs.primary_index,
+> +                            &cifs_fscache_server_index_def, server);
+> +    cFYI(1, "CIFS: get client cookie (0x%p/0x%p)\n",
+> +                            server, server->fscache);
+> +}
+> +
+> +void cifs_fscache_release_client_cookie(struct TCP_Server_Info *server)
+> +{
+> +    cFYI(1, "CIFS: release client cookie (0x%p/0x%p)\n",
+> +                            server, server->fscache);
+> +    fscache_relinquish_cookie(server->fscache, 0);
+> +    server->fscache = NULL;
+> +}
+> +
+> Index: cifs-2.6/fs/cifs/fscache.h
+> ===================================================================
+> --- cifs-2.6.orig/fs/cifs/fscache.h
+> +++ cifs-2.6/fs/cifs/fscache.h
+> @@ -27,14 +27,26 @@
+>  #ifdef CONFIG_CIFS_FSCACHE
+>  
+>  extern struct fscache_netfs cifs_fscache_netfs;
+> +extern const struct fscache_cookie_def cifs_fscache_server_index_def;
+>  
+>  extern int cifs_fscache_register(void);
+>  extern void cifs_fscache_unregister(void);
+>  
+> +/*
+> + * fscache.c
+> + */
+> +extern void cifs_fscache_get_client_cookie(struct TCP_Server_Info *);
+> +extern void cifs_fscache_release_client_cookie(struct TCP_Server_Info *);
+> +
+>  #else /* CONFIG_CIFS_FSCACHE */
+>  static inline int cifs_fscache_register(void) { return 0; }
+>  static inline void cifs_fscache_unregister(void) {}
+>  
+> +static inline void
+> +cifs_fscache_get_client_cookie(struct TCP_Server_Info *server) {}
+> +static inline void
+> +cifs_fscache_get_client_cookie(struct TCP_Server_Info *server); {}
+> +
+>  #endif /* CONFIG_CIFS_FSCACHE */
+>  
+>  #endif /* _CIFS_FSCACHE_H */
+> --
+> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
+> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+> More majordomo info at  http://vger.kernel.org/majordomo-info.html
+> 
+
+
+-- 
+Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002189:2, b/test/corpora/lkml/cur/1382298770.002189:2,
new file mode 100644 (file)
index 0000000..3cfc62e
--- /dev/null
@@ -0,0 +1,66 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 03/10] cifs: register CIFS for caching
+Date: Wed, 23 Jun 2010 17:51:17 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 20
+Message-ID: <9603.1277311877@redhat.com>
+References: <1277220189-3485-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 18:51:32 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTAg-0008Bt-CT
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 18:51:30 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751915Ab0FWQv3 (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 12:51:29 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50923 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751520Ab0FWQv3 (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 12:51:29 -0400
+Received: from int-mx05.intmail.prod.int.phx2.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.18])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGpLFc028550
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 12:51:21 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx05.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGpHIG010890;
+       Wed, 23 Jun 2010 12:51:18 -0400
+In-Reply-To: <1277220189-3485-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.18
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002219>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> +    rc = cifs_fscache_register();
+> +    if (rc)
+> +            goto out;
+> +
+>      rc = cifs_init_inodecache();
+>      if (rc)
+>              goto out_clean_proc;
+> @@ -949,8 +954,10 @@ init_cifs(void)
+>      cifs_destroy_mids();
+>   out_destroy_inodecache:
+>      cifs_destroy_inodecache();
+> +    cifs_fscache_unregister();
+>   out_clean_proc:
+
+This is incorrect.  You need to call cifs_fscache_unregister() if
+cifs_init_inodecache() fails.
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002191:2, b/test/corpora/lkml/cur/1382298770.002191:2,
new file mode 100644 (file)
index 0000000..56752a9
--- /dev/null
@@ -0,0 +1,65 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 04/10] cifs: define server-level cache index objects and register them with FS-Cache
+Date: Wed, 23 Jun 2010 17:54:52 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 19
+Message-ID: <9658.1277312092@redhat.com>
+References: <1277220198-3522-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Wed Jun 23 18:55:07 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1ORTE8-0002ll-VF
+       for lnx-linux-fsdevel@lo.gmane.org; Wed, 23 Jun 2010 18:55:05 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752263Ab0FWQzD (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Wed, 23 Jun 2010 12:55:03 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:18394 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751794Ab0FWQzB (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Wed, 23 Jun 2010 12:55:01 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGsu1L000993
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 12:54:56 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGsrUG016433;
+       Wed, 23 Jun 2010 12:54:54 -0400
+In-Reply-To: <1277220198-3522-1-git-send-email-sjayaraman@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002221>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Define server-level cache index objects (as managed by TCP_ServerInfo
+> structs).  Each server object is created in the CIFS top-level index object
+> and is itself an index into which superblock-level objects are inserted.
+> 
+> Currently, the server objects are keyed by hostname.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+
+Looks reasonable, apart from the index key.  I agree with Jeff that you
+probably want {address,port,family} rather than a hostname.
+
+David
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002193:2, b/test/corpora/lkml/cur/1382298770.002193:2,
new file mode 100644 (file)
index 0000000..e2ea626
--- /dev/null
@@ -0,0 +1,59 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Wed, 23 Jun 2010 17:58:10 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <9720.1277312290@redhat.com>
+References: <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 18:58:19 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTHG-0003Az-Ge
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 18:58:18 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751520Ab0FWQ6R (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 12:58:17 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:62343 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751372Ab0FWQ6R (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 12:58:17 -0400
+Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGwDC2031683
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 12:58:13 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NGwAfq021298;
+       Wed, 23 Jun 2010 12:58:11 -0400
+In-Reply-To: <1277220206-3559-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002223>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define superblock-level cache index objects (managed by cifsTconInfo
+> structs).  Each superblock object is created in a server-level index object
+> and in itself an index into which inode-level objects are inserted.
+> 
+> Currently, the superblock objects are keyed by sharename.
+
+Seems reasonable.  Is there any way you can check that the share you are
+looking at on a server is the same as the last time you looked?  Can you
+validate the root directory of the share in some way?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002194:2, b/test/corpora/lkml/cur/1382298770.002194:2,
new file mode 100644 (file)
index 0000000..d2d1efd
--- /dev/null
@@ -0,0 +1,61 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Wed, 23 Jun 2010 18:02:53 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 15
+Message-ID: <9822.1277312573@redhat.com>
+References: <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:03:04 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTLr-0007Bh-Cs
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:03:03 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752063Ab0FWRDB (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:03:01 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:30823 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRDA (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:03:00 -0400
+Received: from int-mx03.intmail.prod.int.phx2.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.16])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH2v0J030982
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:02:57 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx03.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH2r9N014323;
+       Wed, 23 Jun 2010 13:02:54 -0400
+In-Reply-To: <1277220214-3597-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.16
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002224>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Define inode-level data storage objects (managed by cifsInodeInfo structs).
+> Each inode-level object is created in a super-block level object and is
+> itself a data storage object in to which pages from the inode are stored.
+> 
+> The inode object is keyed by UniqueId. The coherency data being used is
+> LastWriteTime and the file size.
+
+Isn't there a file creation time too?
+
+I take it you don't support caching on files that are open for writing at this
+time?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002195:2, b/test/corpora/lkml/cur/1382298770.002195:2,
new file mode 100644 (file)
index 0000000..ec54a81
--- /dev/null
@@ -0,0 +1,59 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 07/10] cifs: FS-Cache page management
+Date: Wed, 23 Jun 2010 18:05:01 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <9866.1277312701@redhat.com>
+References: <1277220228-3635-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Wed Jun 23 19:05:19 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1ORTNz-0008Oj-Ho
+       for lnx-linux-fsdevel@lo.gmane.org; Wed, 23 Jun 2010 19:05:15 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752145Ab0FWRFO (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Wed, 23 Jun 2010 13:05:14 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:1689 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRFN (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Wed, 23 Jun 2010 13:05:13 -0400
+Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH59sl011966
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:05:09 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH52Jl022163;
+       Wed, 23 Jun 2010 13:05:03 -0400
+In-Reply-To: <1277220228-3635-1-git-send-email-sjayaraman@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002225>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Takes care of invalidation and release of FS-Cache marked pages and also
+> invalidation of the FsCache page flag when the inode is removed.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+
+Acked-by: David Howells <dhowells@redhat.com>
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002196:2, b/test/corpora/lkml/cur/1382298770.002196:2,
new file mode 100644 (file)
index 0000000..63838dc
--- /dev/null
@@ -0,0 +1,54 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 08/10] cifs: store pages into local cache
+Date: Wed, 23 Jun 2010 18:06:12 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 8
+Message-ID: <9890.1277312772@redhat.com>
+References: <1277220240-3674-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:06:21 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTP3-0000fp-01
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:06:21 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752403Ab0FWRGU (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:06:20 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:63621 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751804Ab0FWRGT (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:06:19 -0400
+Received: from int-mx08.intmail.prod.int.phx2.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.21])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH6FCB012081
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:06:15 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx08.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH6CKG013414;
+       Wed, 23 Jun 2010 13:06:13 -0400
+In-Reply-To: <1277220240-3674-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.21
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002226>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Store pages from an CIFS inode into the data storage object associated with
+> that inode.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+
+Acked-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002197:2, b/test/corpora/lkml/cur/1382298770.002197:2,
new file mode 100644 (file)
index 0000000..765c399
--- /dev/null
@@ -0,0 +1,53 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 09/10] cifs: read pages from FS-Cache
+Date: Wed, 23 Jun 2010 18:07:40 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 7
+Message-ID: <9918.1277312860@redhat.com>
+References: <1277220261-3717-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Wed Jun 23 19:07:51 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1ORTQR-0000nv-JF
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Wed, 23 Jun 2010 19:07:47 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751708Ab0FWRHr (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Wed, 23 Jun 2010 13:07:47 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:34413 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1750954Ab0FWRHq (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Wed, 23 Jun 2010 13:07:46 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH7h3Y005904
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:07:43 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH7efR020683;
+       Wed, 23 Jun 2010 13:07:41 -0400
+In-Reply-To: <1277220261-3717-1-git-send-email-sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002227>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> Read pages from a FS-Cache data storage object into a CIFS inode.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+
+Acked-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002201:2, b/test/corpora/lkml/cur/1382298770.002201:2,
new file mode 100644 (file)
index 0000000..bae1eef
--- /dev/null
@@ -0,0 +1,58 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Wed, 23 Jun 2010 18:08:34 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 12
+Message-ID: <9942.1277312914@redhat.com>
+References: <1277220309-3757-1-git-send-email-sjayaraman@suse.de> <yes>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 23 19:09:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1ORTRv-0002J8-2s
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 23 Jun 2010 19:09:19 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753275Ab0FWRIt (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 23 Jun 2010 13:08:49 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:6156 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753203Ab0FWRIr (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 23 Jun 2010 13:08:47 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH8dax006028
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 23 Jun 2010 13:08:39 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5NH8YmA020846;
+       Wed, 23 Jun 2010 13:08:36 -0400
+In-Reply-To: <1277220309-3757-1-git-send-email-sjayaraman@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002231>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Add a mount option 'fsc' to enable local caching on CIFS.
+> 
+> As the cifs-utils (userspace) changes are not done yet, this patch enables
+> 'fsc' by default to assist testing.
+> 
+> Signed-off-by: Suresh Jayaraman <sjayaraman@suse.de>
+
+Acked-by: David Howells <dhowells@redhat.com>
+
+(Give or take the debugging bit)
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002878:2, b/test/corpora/lkml/cur/1382298770.002878:2,
new file mode 100644 (file)
index 0000000..66a3e22
--- /dev/null
@@ -0,0 +1,90 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 10/10] cifs: add mount option to enable local caching
+Date: Fri, 25 Jun 2010 16:18:12 +0530
+Lines: 47
+Message-ID: <4C24896C.4000903@suse.de>
+References: <yes> <1277220309-3757-1-git-send-email-sjayaraman@suse.de> <4C225338.9010807@gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+To: Scott Lovenberg <scott.lovenberg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 12:48:27 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS6SO-0003QF-NW
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 12:48:25 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753965Ab0FYKsX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 06:48:23 -0400
+Received: from cantor.suse.de ([195.135.220.2]:46395 "EHLO mx1.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752612Ab0FYKsW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 06:48:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by mx1.suse.de (Postfix) with ESMTP id 60CED6CB00;
+       Fri, 25 Jun 2010 12:48:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <4C225338.9010807-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002912>
+
+On 06/24/2010 12:02 AM, Scott Lovenberg wrote:
+> On 6/22/2010 11:25 AM, Suresh Jayaraman wrote:
+>> Add a mount option 'fsc' to enable local caching on CIFS.
+>>
+>> As the cifs-utils (userspace) changes are not done yet, this patch
+>> enables
+>> 'fsc' by default to assist testing.
+>>    
+> [...]
+>> @@ -1332,6 +1336,8 @@ cifs_parse_mount_options(char *options, const
+>> char *devname,
+>>               printk(KERN_WARNING "CIFS: Mount option noac not "
+>>                   "supported. Instead set "
+>>                   "/proc/fs/cifs/LookupCacheEnabled to 0\n");
+>> +        } else if (strnicmp(data, "fsc", 3) == 0) {
+>> +            vol->fsc = true;
+>>           } else
+>>               printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
+>>                           data);
+>> @@ -2405,6 +2411,8 @@ static void setup_cifs_sb(struct smb_vol
+>> *pvolume_info,
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+>>       if (pvolume_info->dynperm)
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
+>> +    if (pvolume_info->fsc)
+>> +        cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE;
+>>       if (pvolume_info->direct_io) {
+>>           cFYI(1, "mounting share using direct i/o");
+>>           cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+>>    
+> I reworked the CIFS mount option parsing a while back; I'm not sure
+> whether that patch was going to be in the 2.6.35 tree or not (the window
+> just opened, didn't it?).
+
+Not a problem, I could redo this patch alone when the reworked option
+parsing patches get in.
+
+> Jeff, Steve, can you confirm if that patch is going to be in 2.6.35?
+> 
+> Patch refs: http://patchwork.ozlabs.org/patch/53059/  and
+> http://patchwork.ozlabs.org/patch/53674/
+> 
+
+Thanks,
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002879:2, b/test/corpora/lkml/cur/1382298770.002879:2,
new file mode 100644 (file)
index 0000000..5782037
--- /dev/null
@@ -0,0 +1,68 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 03/10] cifs: register CIFS for caching
+Date: Fri, 25 Jun 2010 16:26:22 +0530
+Lines: 26
+Message-ID: <4C248B56.8030207@suse.de>
+References: <1277220189-3485-1-git-send-email-sjayaraman@suse.de> <yes> <9603.1277311877@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 12:56:32 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS6aG-00066f-1L
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 12:56:32 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754188Ab0FYK4b (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 06:56:31 -0400
+Received: from cantor.suse.de ([195.135.220.2]:46564 "EHLO mx1.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753651Ab0FYK4a (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 06:56:30 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by mx1.suse.de (Postfix) with ESMTP id 17F1E6CB00;
+       Fri, 25 Jun 2010 12:56:30 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <9603.1277311877-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002913>
+
+On 06/23/2010 10:21 PM, David Howells wrote:
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+>> +   rc = cifs_fscache_register();
+>> +   if (rc)
+>> +           goto out;
+>> +
+>>     rc = cifs_init_inodecache();
+>>     if (rc)
+>>             goto out_clean_proc;
+>> @@ -949,8 +954,10 @@ init_cifs(void)
+>>     cifs_destroy_mids();
+>>   out_destroy_inodecache:
+>>     cifs_destroy_inodecache();
+>> +   cifs_fscache_unregister();
+>>   out_clean_proc:
+> 
+> This is incorrect.  You need to call cifs_fscache_unregister() if
+> cifs_init_inodecache() fails.
+> 
+
+Doh! I'll fix it.
+
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002911:2, b/test/corpora/lkml/cur/1382298770.002911:2,
new file mode 100644 (file)
index 0000000..8e172cb
--- /dev/null
@@ -0,0 +1,84 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index
+ objects and register them
+Date: Fri, 25 Jun 2010 18:14:16 +0530
+Lines: 41
+Message-ID: <4C24A4A0.90408@suse.de>
+References: <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 14:44:28 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8Gh-0005Bb-E2
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 14:44:27 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754703Ab0FYMo0 (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 08:44:26 -0400
+Received: from cantor.suse.de ([195.135.220.2]:51036 "EHLO mx1.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754222Ab0FYMoZ (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 08:44:25 -0400
+Received: from relay1.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by mx1.suse.de (Postfix) with ESMTP id E07FF8FEA2;
+       Fri, 25 Jun 2010 14:44:24 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <9720.1277312290-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002945>
+
+On 06/23/2010 10:28 PM, David Howells wrote:
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+>> Define superblock-level cache index objects (managed by cifsTconInfo
+>> structs).  Each superblock object is created in a server-level index object
+>> and in itself an index into which inode-level objects are inserted.
+>>
+>> Currently, the superblock objects are keyed by sharename.
+> 
+> Seems reasonable.  Is there any way you can check that the share you are
+> looking at on a server is the same as the last time you looked?  Can you
+
+Good point.
+
+I thought of using TID (Tree identifier; a unique ID for a resource in
+use by client) along with sharename. But, Server is free to reuse them
+when the tree connection closes and does not guarantee the same Tid for
+a particular resource across tree connections.
+
+Also, considering the UNC name of the resource (//server/share) may not
+be a good idea too as the cache will not be used when for e.g. IPaddress
+is used to mount.
+
+So, if a server does something like this:
+   - export a share 'foo' (original server path: /export/vol1/foo)
+   - client mounts and uses it
+   - server unexports the share 'foo'
+   - server exports 'foo' (original sever path: /export/vol2/foo)
+
+we have a bit of problem..
+
+> validate the root directory of the share in some way?
+> 
+
+I don't know if there is a way to do this.
+
+Thanks,
+
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002912:2, b/test/corpora/lkml/cur/1382298770.002912:2,
new file mode 100644 (file)
index 0000000..d9c761d
--- /dev/null
@@ -0,0 +1,65 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Fri, 25 Jun 2010 18:20:14 +0530
+Lines: 24
+Message-ID: <4C24A606.5040001@suse.de>
+References: <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 14:50:26 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8MR-0007EU-OS
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 14:50:24 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754607Ab0FYMuX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 08:50:23 -0400
+Received: from cantor2.suse.de ([195.135.220.15]:38716 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753675Ab0FYMuW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 08:50:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id B05E686A2E;
+       Fri, 25 Jun 2010 14:50:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <9822.1277312573-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002946>
+
+On 06/23/2010 10:32 PM, David Howells wrote:
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+>> Define inode-level data storage objects (managed by cifsInodeInfo structs).
+>> Each inode-level object is created in a super-block level object and is
+>> itself a data storage object in to which pages from the inode are stored.
+>>
+>> The inode object is keyed by UniqueId. The coherency data being used is
+>> LastWriteTime and the file size.
+> 
+> Isn't there a file creation time too?
+
+I think the creation time is currently being ignored as we won't be able
+to accomodate in POSIX stat struct.
+
+> I take it you don't support caching on files that are open for writing at this
+> time?
+> 
+
+Yes.
+
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002915:2, b/test/corpora/lkml/cur/1382298770.002915:2,
new file mode 100644 (file)
index 0000000..e43c909
--- /dev/null
@@ -0,0 +1,58 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Fri, 25 Jun 2010 13:55:49 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 12
+Message-ID: <22697.1277470549@redhat.com>
+References: <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 14:56:04 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8Rw-0002tq-3k
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 14:56:04 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1753622Ab0FYM4B (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 08:56:01 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50162 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752535Ab0FYM4B (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 08:56:01 -0400
+Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCtqOd018091
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 08:55:52 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCtn4G016466;
+       Fri, 25 Jun 2010 08:55:51 -0400
+In-Reply-To: <4C24A606.5040001-l3A5Bk7waGM@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002949>
+
+Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+
+> I think the creation time is currently being ignored as we won't be able
+> to accomodate in POSIX stat struct.
+
+The FS-Cache interface doesn't use the POSIX stat struct, but it could be
+really useful to save it and use it for cache coherency inside the kernel.
+
+Out of interest, what does Samba do when it comes to generating a creation time
+for UNIX where one does not exist?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002917:2, b/test/corpora/lkml/cur/1382298770.002917:2,
new file mode 100644 (file)
index 0000000..f7047f8
--- /dev/null
@@ -0,0 +1,67 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Fri, 25 Jun 2010 13:58:33 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 21
+Message-ID: <22746.1277470713@redhat.com>
+References: <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Fri Jun 25 15:02:20 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with smtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OS8Xz-000628-FG
+       for lnx-linux-fsdevel@lo.gmane.org; Fri, 25 Jun 2010 15:02:19 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755357Ab0FYM6k (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Fri, 25 Jun 2010 08:58:40 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:50417 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754086Ab0FYM6j (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Fri, 25 Jun 2010 08:58:39 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCwa7Z005113
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 08:58:36 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PCwXVB011094;
+       Fri, 25 Jun 2010 08:58:34 -0400
+In-Reply-To: <4C24A4A0.90408@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002951>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Also, considering the UNC name of the resource (//server/share) may not
+> be a good idea too as the cache will not be used when for e.g. IPaddress
+> is used to mount.
+
+You could convert the UNC name to an IP address, and just use that as your
+key.
+
+> > validate the root directory of the share in some way?
+>
+> I don't know if there is a way to do this.
+
+Is there an inode number or something?  Even the creation time might do.
+
+David
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002930:2, b/test/corpora/lkml/cur/1382298770.002930:2,
new file mode 100644 (file)
index 0000000..2041016
--- /dev/null
@@ -0,0 +1,81 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Fri, 25 Jun 2010 14:26:52 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 35
+Message-ID: <23204.1277472412@redhat.com>
+References: <22746.1277470713@redhat.com> <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>,
+       Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: unlisted-recipients:; (no To-header on input)
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 15:27:01 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OS8vt-0000Xv-FL
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 15:27:01 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1755944Ab0FYN1A (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 09:27:00 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:15634 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1755398Ab0FYN07 (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 09:26:59 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PDQu1D020638
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 09:26:56 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PDQruU018472;
+       Fri, 25 Jun 2010 09:26:54 -0400
+In-Reply-To: <22746.1277470713-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1002964>
+
+David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
+
+> > > validate the root directory of the share in some way?
+> >
+> > I don't know if there is a way to do this.
+> 
+> Is there an inode number or something?  Even the creation time might do.
+
+Looking in cifspdu.h, there are a number of things that it might be possible
+to use.
+
+ (1) FILE_ALL_INFO: CreationTime, IndexNumber, IndexNumber1, FileName
+     (assuming this isn't flattened to '\' or something for the root of a
+     share.
+
+ (2) FILE_UNIX_BASIC_INFO: DevMajor, DevMinor, UniqueId.
+
+ (3) FILE_INFO_STANDARD: CreationDate, CreationTime.
+
+ (4) FILE_INFO_BASIC: CreationTime.
+
+ (5) FILE_DIRECTORY_INFO: FileIndex, CreationTime, FileName.
+
+ (6) SEARCH_ID_FULL_DIR_INFO: FileIndex, CreationTime, UniqueId, FileName.
+
+ (7) FILE_BOTH_DIRECTORY_INFO: FileIndex, CreationTime, ShortName, FileName.
+
+ (8) OPEN_RSP_EXT: Fid, CreationTime, VolumeGUID, FileId.
+
+You may have to choose different sets of things, depending on what the server
+has on offer.  Also, don't forget, if you can't work out whether a share is
+coherent or not from the above, you can always use LastWriteTime, ChangeTime
+and EndOfFile and just discard the whole subtree if they differ.
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.002997:2, b/test/corpora/lkml/cur/1382298770.002997:2,
new file mode 100644 (file)
index 0000000..b78073c
--- /dev/null
@@ -0,0 +1,90 @@
+From: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Fri, 25 Jun 2010 12:53:06 -0400
+Lines: 36
+Message-ID: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+References: <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+       <yes>
+       <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>,
+       Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Fri Jun 25 18:53:12 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSC9P-0005Eb-SU
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Fri, 25 Jun 2010 18:53:12 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S932199Ab0FYQxK (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 12:53:10 -0400
+Received: from cdptpa-omtalb.mail.rr.com ([75.180.132.122]:53512 "EHLO
+       cdptpa-omtalb.mail.rr.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S932187Ab0FYQxJ (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Fri, 25 Jun 2010 12:53:09 -0400
+X-Authority-Analysis: v=1.0 c=1 a=iVNVO0OCT3kA:10 a=yQWWgrYGNuUA:10 a=kj9zAlcOel0A:10 a=20KFwNOVAAAA:8 a=hGzw-44bAAAA:8 a=f0L6POiToRdS6aViIA4A:9 a=tdNtT7bw1iHNm6ggrCkIte35EhAA:4 a=CjuIK1q_8ugA:10 a=jEp0ucaQiEUA:10 a=0kPLrQdw3YYA:10 a=dowx1zmaLagA:10 a=00U40p1LBqVLw4jT:21 a=gh7LVOPznGai4vo_:21
+X-Cloudmark-Score: 0
+X-Originating-IP: 71.70.153.3
+Received: from [71.70.153.3] ([71.70.153.3:42266] helo=mail.poochiereds.net)
+       by cdptpa-oedge01.mail.rr.com (envelope-from <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>)
+       (ecelerity 2.2.2.39 r()) with ESMTP
+       id 2D/E0-24471-3FED42C4; Fri, 25 Jun 2010 16:53:08 +0000
+Received: from tlielax.poochiereds.net (tlielax.poochiereds.net [192.168.1.3])
+       by mail.poochiereds.net (Postfix) with ESMTPS id E9B19580FA;
+       Fri, 25 Jun 2010 12:53:06 -0400 (EDT)
+In-Reply-To: <22697.1277470549-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-redhat-linux-gnu)
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003033>
+
+On Fri, 25 Jun 2010 13:55:49 +0100
+David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
+
+> Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org> wrote:
+> 
+> > I think the creation time is currently being ignored as we won't be able
+> > to accomodate in POSIX stat struct.
+> 
+> The FS-Cache interface doesn't use the POSIX stat struct, but it could be
+> really useful to save it and use it for cache coherency inside the kernel.
+> 
+> Out of interest, what does Samba do when it comes to generating a creation time
+> for UNIX where one does not exist?
+> 
+
+(cc'ing samba-technical since we're talking about the create time)
+
+Looks like it mostly uses the ctime. IMO, the mtime would be a better
+choice since it changes less frequently, but I don't guess that it
+matters very much.
+
+I have a few patches that make the cifs_iget code do more stringent
+checks. One of those makes it use the create time like an i_generation
+field to guard against matching inodes that have the same number but
+that have undergone a delete/create cycle. They need a bit more testing
+but I'm planning to post them in time for 2.6.36.
+
+Because of how samba generates this number, it could be somewhat
+problematic to do this. What may save us though is that Linux<->Samba
+mostly uses unix extensions unless someone has specifically disabled
+them on either end. The unix extension calls don't generally send any
+sort of create time field, so we can't rely on it in those codepaths
+anyway.
+
+-- 
+Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003106:2, b/test/corpora/lkml/cur/1382298770.003106:2,
new file mode 100644 (file)
index 0000000..19ea381
--- /dev/null
@@ -0,0 +1,60 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Fri, 25 Jun 2010 22:46:38 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 13
+Message-ID: <18628.1277502398@redhat.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com>
+Cc: dhowells@redhat.com, Suresh Jayaraman <sjayaraman@suse.de>,
+       Steve French <smfrench@gmail.com>, linux-cifs@vger.kernel.org,
+       linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
+       samba-technical@lists.samba.org
+To: Jeff Layton <jlayton@samba.org>
+X-From: linux-kernel-owner@vger.kernel.org Fri Jun 25 23:47:07 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSGjo-0006q8-ME
+       for glk-linux-kernel-3@lo.gmane.org; Fri, 25 Jun 2010 23:47:05 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932250Ab0FYVqv (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Fri, 25 Jun 2010 17:46:51 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:55406 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932088Ab0FYVqs (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Fri, 25 Jun 2010 17:46:48 -0400
+Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PLkhIG005974
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 17:46:43 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PLkd77017768;
+       Fri, 25 Jun 2010 17:46:40 -0400
+In-Reply-To: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.12
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003142>
+
+Jeff Layton <jlayton@samba.org> wrote:
+
+> Looks like it mostly uses the ctime. IMO, the mtime would be a better
+> choice since it changes less frequently, but I don't guess that it
+> matters very much.
+
+I'd've thought mtime changes more frequently since that's altered when data is
+written.  ctime is changed when attributes are changed.
+
+Note that Ext4 appears to have a file creation time field in its inode
+(struct ext4_inode::i_crtime[_extra]).  Can Samba be made to use that?
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003117:2, b/test/corpora/lkml/cur/1382298770.003117:2,
new file mode 100644 (file)
index 0000000..7f53e34
--- /dev/null
@@ -0,0 +1,65 @@
+From: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Sat, 26 Jun 2010 00:04:28 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 18
+Message-ID: <20123.1277507068@redhat.com>
+References: <20100625182651.36800d06@tlielax.poochiereds.net> <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <yes> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com> <18628.1277502398@redhat.com>
+Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org, Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>,
+       Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org
+To: Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Sat Jun 26 01:04:45 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSHww-0006Jk-NV
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Sat, 26 Jun 2010 01:04:43 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1751807Ab0FYXEl (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Fri, 25 Jun 2010 19:04:41 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:62977 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752149Ab0FYXEl (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Fri, 25 Jun 2010 19:04:41 -0400
+Received: from int-mx04.intmail.prod.int.phx2.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.17])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5PN4X40004498
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Fri, 25 Jun 2010 19:04:34 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx04.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5PN4Sld008220;
+       Fri, 25 Jun 2010 19:04:30 -0400
+In-Reply-To: <20100625182651.36800d06-9yPaYZwiELC+kQycOl6kW4xkIHaj4LzF@public.gmane.org>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.17
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003153>
+
+Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org> wrote:
+
+> IIUC, updating mtime for a write is also an attribute change, and that
+> affects ctime. According to the stat(2) manpage:
+
+You're right.  Okay, ctime is the more frequently changed.
+
+> > Note that Ext4 appears to have a file creation time field in its inode
+> > (struct ext4_inode::i_crtime[_extra]).  Can Samba be made to use that?
+> 
+> Is it exposed to userspace in any (standard) way? It would be handy to
+> have that. While we're wishing...it might also be nice to have a
+> standard way to get at the i_generation from userspace too.
+
+Not at present, but it's something that could be exported by ioctl() or
+getxattr().
+
+David
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003118:2, b/test/corpora/lkml/cur/1382298770.003118:2,
new file mode 100644 (file)
index 0000000..a1ec438
--- /dev/null
@@ -0,0 +1,122 @@
+From: Steve French <smfrench@gmail.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and 
+       register them
+Date: Fri, 25 Jun 2010 18:05:30 -0500
+Lines: 51
+Message-ID: <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+       <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+       <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>
+       <18628.1277502398@redhat.com>
+       <20100625182651.36800d06@tlielax.poochiereds.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: David Howells <dhowells@redhat.com>,
+       Suresh Jayaraman <sjayaraman@suse.de>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org, samba-technical@lists.samba.org,
+       Jeff Layton <jlayton@redhat.com>
+To: Jeff Layton <jlayton@samba.org>,
+       "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>,
+       Mingming Cao <mcao@us.ibm.com>
+X-From: linux-kernel-owner@vger.kernel.org Sat Jun 26 01:05:41 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSHxs-0006a8-BA
+       for glk-linux-kernel-3@lo.gmane.org; Sat, 26 Jun 2010 01:05:40 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756188Ab0FYXFd convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Fri, 25 Jun 2010 19:05:33 -0400
+Received: from mail-qw0-f46.google.com ([209.85.216.46]:51369 "EHLO
+       mail-qw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751575Ab0FYXFb convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Fri, 25 Jun 2010 19:05:31 -0400
+Received: by qwi4 with SMTP id 4so742644qwi.19
+        for <multiple recipients>; Fri, 25 Jun 2010 16:05:30 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:mime-version:received:received:in-reply-to
+         :references:date:message-id:subject:from:to:cc:content-type
+         :content-transfer-encoding;
+        bh=6wKQkGOEeUGN4oPR3Nm4SRxtJr/EBwN8ENmpLnfdCDU=;
+        b=X7L6W0MtpQeW/4iBuj+oDlcP2yCJ3qwUs9lHBq1fRW6WdYblHXjmaN8o++3GDPLAg5
+         0MD07zxbYTGXRSrgCjCrGVm0tT88/6hY2a/rB8g68h/Qso2sIHa7B1iIN8JRR4pPWle0
+         sVjp9Xy/bQn2e0uE481Ii1TLHuWYA/QDXZreU=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:date:message-id:subject:from:to
+         :cc:content-type:content-transfer-encoding;
+        b=B+7qQvdOpN5a/KCRrDbssKZX8D3SnP73VMHd9RpkqP9nCHCmSLAgbeH03+/m6CLVAo
+         G+NKWqWtknwPBkYqT/bdP2XEak1yr+0rjOqjUaNvaT7AhzsyHEJBkaNnsbS3qaRy39OP
+         S7OkAyHfmgdeNAHkKnKRF73hfpvgAqR9X4bn8=
+Received: by 10.224.59.223 with SMTP id m31mr1130670qah.63.1277507130411; Fri, 
+       25 Jun 2010 16:05:30 -0700 (PDT)
+Received: by 10.229.46.136 with HTTP; Fri, 25 Jun 2010 16:05:30 -0700 (PDT)
+In-Reply-To: <20100625182651.36800d06@tlielax.poochiereds.net>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003154>
+
+On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wrote:
+>
+> On Fri, 25 Jun 2010 22:46:38 +0100
+> David Howells <dhowells@redhat.com> wrote:
+>
+> > Jeff Layton <jlayton@samba.org> wrote:
+> >
+> > > Looks like it mostly uses the ctime. IMO, the mtime would be a be=
+tter
+> > > choice since it changes less frequently, but I don't guess that i=
+t
+> > > matters very much.
+> >
+> > I'd've thought mtime changes more frequently since that's altered w=
+hen data is
+> > written. =A0ctime is changed when attributes are changed.
+> >
+>
+> IIUC, updating mtime for a write is also an attribute change, and tha=
+t
+> affects ctime. According to the stat(2) manpage:
+>
+> =A0 =A0 =A0 The field st_ctime is changed by writing or by setting =A0=
+inode =A0informa-
+> =A0 =A0 =A0 tion (i.e., owner, group, link count, mode, etc.).
+>
+> > Note that Ext4 appears to have a file creation time field in its in=
+ode
+> > (struct ext4_inode::i_crtime[_extra]). =A0Can Samba be made to use =
+that?
+> >
+>
+> Is it exposed to userspace in any (standard) way? It would be handy t=
+o
+> have that. While we're wishing...it might also be nice to have a
+> standard way to get at the i_generation from userspace too.
+>
+
+Yes - I have talked with MingMing and Aneesh about those (NFS may
+someday be able to use those too).=A0 An obstacle in the past had been
+that samba server stores its own fake creation time in an ndr encoded
+xattr which complicates things.
+
+MingMing/Annesh -
+Xattr or other way to get at birth time?
+
+
+--
+Thanks,
+
+Steve
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003171:2, b/test/corpora/lkml/cur/1382298770.003171:2,
new file mode 100644 (file)
index 0000000..66e425e
--- /dev/null
@@ -0,0 +1,174 @@
+From: Mingming Cao <mcao@us.ibm.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+       register them
+Date: Fri, 25 Jun 2010 17:52:24 -0700
+Lines: 92
+Message-ID: <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>  <4C24A606.5040001@suse.de>
+       <1277220214-3597-1-git-send-email-sjayaraman@suse.de>   <9822.1277312573@redhat.com>
+       <22697.1277470549@redhat.com>   <18628.1277502398@redhat.com>   <20100625182651.36800d06@tlielax.poochiereds.net>
+       <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+Cc: linux-cifs@vger.kernel.org, Jeff Layton <jlayton@redhat.com>,
+       samba-technical@lists.samba.org, linux-kernel@vger.kernel.org,
+       David Howells <dhowells@redhat.com>, linux-fsdevel@vger.kernel.org,
+       "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>
+To: Steve French <smfrench@gmail.com>
+X-From: samba-technical-bounces@lists.samba.org Sat Jun 26 13:36:56 2010
+Return-path: <samba-technical-bounces@lists.samba.org>
+Envelope-to: gnsi-samba-technical@m.gmane.org
+Received: from fn.samba.org ([216.83.154.106] helo=lists.samba.org)
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <samba-technical-bounces@lists.samba.org>)
+       id 1OSTgu-00025d-6P
+       for gnsi-samba-technical@m.gmane.org; Sat, 26 Jun 2010 13:36:56 +0200
+Received: from fn.samba.org (localhost [127.0.0.1])
+       by lists.samba.org (Postfix) with ESMTP id 1ED11AD2C4;
+       Sat, 26 Jun 2010 05:36:45 -0600 (MDT)
+X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on fn.samba.org
+X-Spam-Level: 
+X-Spam-Status: No, score=-6.6 required=3.8 tests=BAYES_00,HTML_MESSAGE,
+       RCVD_IN_DNSWL_MED,SPF_PASS autolearn=ham version=3.2.5
+X-Original-To: samba-technical@lists.samba.org
+Delivered-To: samba-technical@lists.samba.org
+Received: from e34.co.us.ibm.com (e34.co.us.ibm.com [32.97.110.152])
+       by lists.samba.org (Postfix) with ESMTP id 30F90AD282
+       for <samba-technical@lists.samba.org>;
+       Fri, 25 Jun 2010 18:52:24 -0600 (MDT)
+Received: from d03relay01.boulder.ibm.com (d03relay01.boulder.ibm.com
+       [9.17.195.226])
+       by e34.co.us.ibm.com (8.14.4/8.13.1) with ESMTP id o5Q0iN1h017083
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:44:23 -0600
+Received: from d03av01.boulder.ibm.com (d03av01.boulder.ibm.com [9.17.195.167])
+       by d03relay01.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id
+       o5Q0qQTN175324
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:52:26 -0600
+Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1])
+       by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP
+       id o5Q0qPCF006767
+       for <samba-technical@lists.samba.org>; Fri, 25 Jun 2010 18:52:26 -0600
+Received: from d03nm128.boulder.ibm.com (d03nm128.boulder.ibm.com
+       [9.17.195.32])
+       by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id
+       o5Q0qPrh006760; Fri, 25 Jun 2010 18:52:25 -0600
+In-Reply-To: <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+X-KeepSent: B55E8EC7:E8DD23D5-8725774E:0004921E;
+ type=4; name=$KeepSent
+X-Mailer: Lotus Notes Build V852_M2_03302010 March 30, 2010
+X-MIMETrack: Serialize by Router on D03NM128/03/M/IBM(Release 8.0.1|February
+       07, 2008) at 06/25/2010 18:52:25
+X-Mailman-Approved-At: Sat, 26 Jun 2010 05:36:42 -0600
+X-Content-Filtered-By: Mailman/MimeDel 2.1.12
+X-BeenThere: samba-technical@lists.samba.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Discussions on Samba internals. For general questions please
+       subscribe to the list samba@samba.org"
+       <samba-technical.lists.samba.org>
+List-Unsubscribe: <https://lists.samba.org/mailman/options/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=unsubscribe>
+List-Archive: <http://lists.samba.org/pipermail/samba-technical>
+List-Post: <mailto:samba-technical@lists.samba.org>
+List-Help: <mailto:samba-technical-request@lists.samba.org?subject=help>
+List-Subscribe: <https://lists.samba.org/mailman/listinfo/samba-technical>,
+       <mailto:samba-technical-request@lists.samba.org?subject=subscribe>
+Sender: samba-technical-bounces@lists.samba.org
+Errors-To: samba-technical-bounces@lists.samba.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003208>
+
+
+
+Steve French <smfrench@gmail.com> wrote on 06/25/2010 04:05:30 PM:
+
+> Steve French <smfrench@gmail.com>
+> 06/25/2010 04:05 PM
+>
+> To
+>
+> Jeff Layton <jlayton@samba.org>, "Aneesh Kumar K.V"
+> <aneesh.kumar@linux.vnet.ibm.com>, Mingming Cao/Beaverton/IBM@IBMUS
+>
+> cc
+>
+> David Howells <dhowells@redhat.com>, Suresh Jayaraman
+> <sjayaraman@suse.de>, linux-cifs@vger.kernel.org, linux-
+> fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, samba-
+> technical@lists.samba.org, Jeff Layton <jlayton@redhat.com>
+>
+> Subject
+>
+> Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+> register them
+>
+> On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wrot=
+e:
+> >
+> > On Fri, 25 Jun 2010 22:46:38 +0100
+> > David Howells <dhowells@redhat.com> wrote:
+> >
+> > > Jeff Layton <jlayton@samba.org> wrote:
+> > >
+> > > > Looks like it mostly uses the ctime. IMO, the mtime would be a
+better
+> > > > choice since it changes less frequently, but I don't guess that=
+ it
+> > > > matters very much.
+> > >
+> > > I'd've thought mtime changes more frequently since that's
+> altered when data is
+> > > written. =A0ctime is changed when attributes are changed.
+> > >
+> >
+> > IIUC, updating mtime for a write is also an attribute change, and t=
+hat
+> > affects ctime. According to the stat(2) manpage:
+> >
+> > =A0 =A0 =A0 The field st_ctime is changed by writing or by setting
+> =A0inode =A0informa-
+> > =A0 =A0 =A0 tion (i.e., owner, group, link count, mode, etc.).
+> >
+> > > Note that Ext4 appears to have a file creation time field in its
+inode
+> > > (struct ext4_inode::i_crtime[_extra]). =A0Can Samba be made to us=
+e
+that?
+> > >
+> >
+> > Is it exposed to userspace in any (standard) way? It would be handy=
+ to
+> > have that. While we're wishing...it might also be nice to have a
+> > standard way to get at the i_generation from userspace too.
+> >
+>
+> Yes - I have talked with MingMing and Aneesh about those (NFS may
+> someday be able to use those too).=A0 An obstacle in the past had bee=
+n
+> that samba server stores its own fake creation time in an ndr encoded=
+
+> xattr which complicates things.
+>
+> MingMing/Annesh -
+> Xattr or other way to get at birth time?
+>
+>
+
+Not yet,
+ The ext4 file creation time only accesable from the kernel at the mome=
+nt.
+There were discussion
+to make this information avaliable via xattr before, but was rejected,
+since most people
+agree that making this info avalibele via stat() is more standard. Howe=
+ver
+modifying stat() would imply
+big interface change. thus no action has been taken yet.
+
+> --
+> Thanks,
+>
+> Steve=
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003317:2, b/test/corpora/lkml/cur/1382298770.003317:2,
new file mode 100644 (file)
index 0000000..6fce518
--- /dev/null
@@ -0,0 +1,156 @@
+From: "Aneesh Kumar K. V" <aneesh.kumar@linux.vnet.ibm.com>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and register them
+Date: Sun, 27 Jun 2010 23:47:21 +0530
+Lines: 100
+Message-ID: <871vbscpce.fsf@linux.vnet.ibm.com>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net> <4C24A606.5040001@suse.de> <1277220214-3597-1-git-send-email-sjayaraman@suse.de> <9822.1277312573@redhat.com> <22697.1277470549@redhat.com> <18628.1277502398@redhat.com> <20100625182651.36800d06@tlielax.poochiereds.net> <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com> <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: David Howells <dhowells@redhat.com>,
+       Jeff Layton <jlayton@redhat.com>,
+       Jeff Layton <jlayton@samba.org>, linux-cifs@vger.kernel.org,
+       linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
+       samba-technical@lists.samba.org,
+       Suresh Jayaraman <sjayaraman@suse.de>
+To: Mingming Cao <mcao@us.ibm.com>, Steve French <smfrench@gmail.com>,
+       "DENIEL Philippe" <philippe.deniel@cea.fr>
+X-From: linux-kernel-owner@vger.kernel.org Sun Jun 27 20:18:00 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OSwQZ-0003Kh-Vu
+       for glk-linux-kernel-3@lo.gmane.org; Sun, 27 Jun 2010 20:18:00 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754631Ab0F0SRq convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 27 Jun 2010 14:17:46 -0400
+Received: from e23smtp07.au.ibm.com ([202.81.31.140]:52430 "EHLO
+       e23smtp07.au.ibm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753837Ab0F0SRl convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 27 Jun 2010 14:17:41 -0400
+Received: from d23relay05.au.ibm.com (d23relay05.au.ibm.com [202.81.31.247])
+       by e23smtp07.au.ibm.com (8.14.4/8.13.1) with ESMTP id o5RIHbfJ012483;
+       Mon, 28 Jun 2010 04:17:37 +1000
+Received: from d23av03.au.ibm.com (d23av03.au.ibm.com [9.190.234.97])
+       by d23relay05.au.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id o5RIHW9f1130634;
+       Mon, 28 Jun 2010 04:17:32 +1000
+Received: from d23av03.au.ibm.com (loopback [127.0.0.1])
+       by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id o5RIHVcR027534;
+       Mon, 28 Jun 2010 04:17:32 +1000
+Received: from skywalker.linux.vnet.ibm.com ([9.77.196.78])
+       by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id o5RIHMFl027485;
+       Mon, 28 Jun 2010 04:17:24 +1000
+In-Reply-To: <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+User-Agent: Notmuch/ (http://notmuchmail.org) Emacs/24.0.50.1 (i686-pc-linux-gnu)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003357>
+
+On Fri, 25 Jun 2010 17:52:24 -0700, Mingming Cao <mcao@us.ibm.com> wrot=
+e:
+>=20
+>=20
+> Steve French <smfrench@gmail.com> wrote on 06/25/2010 04:05:30 PM:
+>=20
+> > Steve French <smfrench@gmail.com>
+> > 06/25/2010 04:05 PM
+> >
+> > To
+> >
+> > Jeff Layton <jlayton@samba.org>, "Aneesh Kumar K.V"
+> > <aneesh.kumar@linux.vnet.ibm.com>, Mingming Cao/Beaverton/IBM@IBMUS
+> >
+> > cc
+> >
+> > David Howells <dhowells@redhat.com>, Suresh Jayaraman
+> > <sjayaraman@suse.de>, linux-cifs@vger.kernel.org, linux-
+> > fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, samba-
+> > technical@lists.samba.org, Jeff Layton <jlayton@redhat.com>
+> >
+> > Subject
+> >
+> > Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+> > register them
+> >
+> > On Fri, Jun 25, 2010 at 5:26 PM, Jeff Layton <jlayton@samba.org> wr=
+ote:
+> > >
+> > > On Fri, 25 Jun 2010 22:46:38 +0100
+> > > David Howells <dhowells@redhat.com> wrote:
+> > >
+> > > > Jeff Layton <jlayton@samba.org> wrote:
+> > > >
+> > > > > Looks like it mostly uses the ctime. IMO, the mtime would be =
+a
+> better
+> > > > > choice since it changes less frequently, but I don't guess th=
+at it
+> > > > > matters very much.
+> > > >
+> > > > I'd've thought mtime changes more frequently since that's
+> > altered when data is
+> > > > written. =C2=A0ctime is changed when attributes are changed.
+> > > >
+> > >
+> > > IIUC, updating mtime for a write is also an attribute change, and=
+ that
+> > > affects ctime. According to the stat(2) manpage:
+> > >
+> > > =C2=A0 =C2=A0 =C2=A0 The field st_ctime is changed by writing or =
+by setting
+> > =C2=A0inode =C2=A0informa-
+> > > =C2=A0 =C2=A0 =C2=A0 tion (i.e., owner, group, link count, mode, =
+etc.).
+> > >
+> > > > Note that Ext4 appears to have a file creation time field in it=
+s
+> inode
+> > > > (struct ext4_inode::i_crtime[_extra]). =C2=A0Can Samba be made =
+to use
+> that?
+> > > >
+> > >
+> > > Is it exposed to userspace in any (standard) way? It would be han=
+dy to
+> > > have that. While we're wishing...it might also be nice to have a
+> > > standard way to get at the i_generation from userspace too.
+> > >
+> >
+> > Yes - I have talked with MingMing and Aneesh about those (NFS may
+> > someday be able to use those too).=C2=A0 An obstacle in the past ha=
+d been
+> > that samba server stores its own fake creation time in an ndr encod=
+ed
+> > xattr which complicates things.
+> >
+> > MingMing/Annesh -
+> > Xattr or other way to get at birth time?
+> >
+> >
+>=20
+> Not yet,
+>  The ext4 file creation time only accesable from the kernel at the mo=
+ment.
+> There were discussion
+> to make this information avaliable via xattr before, but was rejected=
+,
+> since most people
+> agree that making this info avalibele via stat() is more standard. Ho=
+wever
+> modifying stat() would imply
+> big interface change. thus no action has been taken yet.
+
+NFS ganesha pNFS also had a requirement for getting i_generation and
+inode number in userspace. So may be we should now look at updating
+stat or add a variant syscall that include i_generation and create time
+in the return value
+
+-aneesh
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003318:2, b/test/corpora/lkml/cur/1382298770.003318:2,
new file mode 100644 (file)
index 0000000..058d147
--- /dev/null
@@ -0,0 +1,66 @@
+From: Christoph Hellwig <hch-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org>
+Subject: Re: [RFC][PATCH 06/10] cifs: define inode-level cache object and
+ register them
+Date: Sun, 27 Jun 2010 14:22:29 -0400
+Lines: 9
+Message-ID: <20100627182229.GA492@infradead.org>
+References: <20100625125306.7f9b1966@tlielax.poochiereds.net>
+ <4C24A606.5040001@suse.de>
+ <1277220214-3597-1-git-send-email-sjayaraman@suse.de>
+ <9822.1277312573@redhat.com>
+ <22697.1277470549@redhat.com>
+ <18628.1277502398@redhat.com>
+ <20100625182651.36800d06@tlielax.poochiereds.net>
+ <AANLkTilOTrHLvLv4XWYZO6xCnYZgYT7gO2M-oKZ6VvqM@mail.gmail.com>
+ <OFB55E8EC7.E8DD23D5-ON8725774E.0004921E-8825774E.0004CC31@us.ibm.com>
+ <871vbscpce.fsf@linux.vnet.ibm.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Mingming Cao <mcao-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>, Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
+       DENIEL Philippe <philippe.deniel-KCE40YydGKI@public.gmane.org>,
+       David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>,
+       Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>,
+       Jeff Layton <jlayton-eUNUBHrolfbYtjvyW6yDsg@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       samba-technical-w/Ol4Ecudpl8XjKLYN78aQ@public.gmane.org,
+       Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+To: "Aneesh Kumar K. V" <aneesh.kumar-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Sun Jun 27 20:22:46 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OSwVB-0005TI-SG
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Sun, 27 Jun 2010 20:22:46 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1752811Ab0F0SWo (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Sun, 27 Jun 2010 14:22:44 -0400
+Received: from bombadil.infradead.org ([18.85.46.34]:55433 "EHLO
+       bombadil.infradead.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752728Ab0F0SWn (ORCPT
+       <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>); Sun, 27 Jun 2010 14:22:43 -0400
+Received: from hch by bombadil.infradead.org with local (Exim 4.72 #1 (Red Hat Linux))
+       id 1OSwUv-00009z-9N; Sun, 27 Jun 2010 18:22:29 +0000
+Content-Disposition: inline
+In-Reply-To: <871vbscpce.fsf-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+X-SRS-Rewrite: SMTP reverse-path rewritten from <hch-wEGCiKHe2LqWVfeAwA7xHQ@public.gmane.org> by bombadil.infradead.org
+       See http://www.infradead.org/rpr.html
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003358>
+
+On Sun, Jun 27, 2010 at 11:47:21PM +0530, Aneesh Kumar K. V wrote:
+> NFS ganesha pNFS also had a requirement for getting i_generation and
+> inode number in userspace. So may be we should now look at updating
+> stat or add a variant syscall that include i_generation and create time
+> in the return value
+
+What's missing in knfsd that you feel the sudden urge to move backwards
+to a userspace nfsd (one with a horribly crappy codebase, too).
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003486:2, b/test/corpora/lkml/cur/1382298770.003486:2,
new file mode 100644 (file)
index 0000000..8831b45
--- /dev/null
@@ -0,0 +1,89 @@
+From: Suresh Jayaraman <sjayaraman-l3A5Bk7waGM@public.gmane.org>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index
+ objects and register them
+Date: Mon, 28 Jun 2010 18:23:13 +0530
+Lines: 48
+Message-ID: <4C289B39.4060901@suse.de>
+References: <22746.1277470713@redhat.com> <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com> <23204.1277472412@redhat.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+Cc: Steve French <smfrench-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>, linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+X-From: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mon Jun 28 14:53:24 2010
+Return-path: <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1OTDq0-00054Q-At
+       for glkc-linux-cifs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Mon, 28 Jun 2010 14:53:24 +0200
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1754503Ab0F1MxX (ORCPT <rfc822;glkc-linux-cifs@m.gmane.org>);
+       Mon, 28 Jun 2010 08:53:23 -0400
+Received: from cantor2.suse.de ([195.135.220.15]:48374 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754456Ab0F1MxW (ORCPT <rfc822;linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Mon, 28 Jun 2010 08:53:22 -0400
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id 7BDC18672B;
+       Mon, 28 Jun 2010 14:53:21 +0200 (CEST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091130 SUSE/3.0.0-1.1.1 Thunderbird/3.0
+In-Reply-To: <23204.1277472412-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Sender: linux-cifs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-cifs.vger.kernel.org>
+X-Mailing-List: linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003530>
+
+On 06/25/2010 06:56 PM, David Howells wrote:
+> David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
+> 
+>>>> validate the root directory of the share in some way?
+>>>
+>>> I don't know if there is a way to do this.
+>>
+>> Is there an inode number or something?  Even the creation time might do.
+> 
+> Looking in cifspdu.h, there are a number of things that it might be possible
+> to use.
+> 
+>  (1) FILE_ALL_INFO: CreationTime, IndexNumber, IndexNumber1, FileName
+>      (assuming this isn't flattened to '\' or something for the root of a
+>      share.
+> 
+>  (2) FILE_UNIX_BASIC_INFO: DevMajor, DevMinor, UniqueId.
+> 
+>  (3) FILE_INFO_STANDARD: CreationDate, CreationTime.
+> 
+>  (4) FILE_INFO_BASIC: CreationTime.
+> 
+>  (5) FILE_DIRECTORY_INFO: FileIndex, CreationTime, FileName.
+> 
+>  (6) SEARCH_ID_FULL_DIR_INFO: FileIndex, CreationTime, UniqueId, FileName.
+> 
+>  (7) FILE_BOTH_DIRECTORY_INFO: FileIndex, CreationTime, ShortName, FileName.
+> 
+>  (8) OPEN_RSP_EXT: Fid, CreationTime, VolumeGUID, FileId.
+> 
+> You may have to choose different sets of things, depending on what the server
+> has on offer.  Also, don't forget, if you can't work out whether a share is
+
+Did you mean we need to validate differently for different servers?
+
+I just did some testing and it looks like we could rely on CreationTime,
+IndexNumber for validating with Windows servers (FileName is relative to
+the mapped drive) and UniqueId for validating with Samba servers. I did
+not test all possibilities (there could be more).
+
+> coherent or not from the above, you can always use LastWriteTime, ChangeTime
+> and EndOfFile and just discard the whole subtree if they differ.
+> 
+
+Thanks,
+
+-- 
+Suresh Jayaraman
+
+
diff --git a/test/corpora/lkml/cur/1382298770.003499:2, b/test/corpora/lkml/cur/1382298770.003499:2,
new file mode 100644 (file)
index 0000000..b10adc4
--- /dev/null
@@ -0,0 +1,63 @@
+From: David Howells <dhowells@redhat.com>
+Subject: Re: [RFC][PATCH 05/10] cifs: define superblock-level cache index objects and register them
+Date: Mon, 28 Jun 2010 14:24:45 +0100
+Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley
+       Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United
+       Kingdom.
+       Registered in England and Wales under Company Registration No. 3798903
+Lines: 17
+Message-ID: <9513.1277731485@redhat.com>
+References: <4C289B39.4060901@suse.de> <22746.1277470713@redhat.com> <4C24A4A0.90408@suse.de> <1277220206-3559-1-git-send-email-sjayaraman@suse.de> <yes> <9720.1277312290@redhat.com> <23204.1277472412@redhat.com>
+Cc: dhowells@redhat.com, Steve French <smfrench@gmail.com>,
+       linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Suresh Jayaraman <sjayaraman@suse.de>
+X-From: linux-fsdevel-owner@vger.kernel.org Mon Jun 28 15:24:57 2010
+Return-path: <linux-fsdevel-owner@vger.kernel.org>
+Envelope-to: lnx-linux-fsdevel@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-fsdevel-owner@vger.kernel.org>)
+       id 1OTEKW-00048k-S3
+       for lnx-linux-fsdevel@lo.gmane.org; Mon, 28 Jun 2010 15:24:57 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1751608Ab0F1NYz (ORCPT <rfc822;lnx-linux-fsdevel@m.gmane.org>);
+       Mon, 28 Jun 2010 09:24:55 -0400
+Received: from mx1.redhat.com ([209.132.183.28]:26085 "EHLO mx1.redhat.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751395Ab0F1NYy (ORCPT <rfc822;linux-fsdevel@vger.kernel.org>);
+       Mon, 28 Jun 2010 09:24:54 -0400
+Received: from int-mx08.intmail.prod.int.phx2.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.21])
+       by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id o5SDOmfA019811
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Mon, 28 Jun 2010 09:24:49 -0400
+Received: from redhat.com (ovpn01.gateway.prod.ext.phx2.redhat.com [10.5.9.1])
+       by int-mx08.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id o5SDOjHf030340;
+       Mon, 28 Jun 2010 09:24:47 -0400
+In-Reply-To: <4C289B39.4060901@suse.de>
+X-Scanned-By: MIMEDefang 2.67 on 10.5.11.21
+Sender: linux-fsdevel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-fsdevel.vger.kernel.org>
+X-Mailing-List: linux-fsdevel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1003543>
+
+Suresh Jayaraman <sjayaraman@suse.de> wrote:
+
+> Did you mean we need to validate differently for different servers?
+
+You may need to, yes, as different servers may make different attributes
+available.
+
+This isn't too bad.  Each server index record in the cache has freeform
+auxiliary data, just as does each file data record.  You could, say, stick a
+byte at the front that indicates what you've stored in there.
+
+David
+--
+To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298770.004581:2, b/test/corpora/lkml/cur/1382298770.004581:2,
new file mode 100644 (file)
index 0000000..732bfa0
--- /dev/null
@@ -0,0 +1,92 @@
+From: Timur Tabi <timur.tabi@gmail.com>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Wed, 30 Jun 2010 15:55:58 -0500
+Lines: 33
+Message-ID: <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+References: <20100308191005.GE4324@amak.tundra.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: mporter@kernel.crashing.org, linux-kernel@vger.kernel.org,
+       linuxppc-dev@lists.ozlabs.org, thomas.moll@sysgo.com
+To: Alexandre Bounine <abounine@tundra.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 30 22:56:40 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OU4Kl-0005Kf-V4
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 30 Jun 2010 22:56:40 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756668Ab0F3U4b convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 30 Jun 2010 16:56:31 -0400
+Received: from mail-vw0-f46.google.com ([209.85.212.46]:41333 "EHLO
+       mail-vw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753416Ab0F3U43 convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 30 Jun 2010 16:56:29 -0400
+Received: by vws5 with SMTP id 5so1449398vws.19
+        for <linux-kernel@vger.kernel.org>; Wed, 30 Jun 2010 13:56:28 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:mime-version:received:in-reply-to
+         :references:from:date:message-id:subject:to:cc:content-type
+         :content-transfer-encoding;
+        bh=FTlit9cHTz/9rLGcvA5/pEZlzxAQ5x20v8HE5XYFwYM=;
+        b=NFbjnxZ4KwcjTy4tFh+BnhWPEGeYTw6z918yIouRaMmbEDph56xq26K9aTBokuYHqe
+         UgFjBn7XWcxvqJPyCetfsDRG+F3M2XwCq/DSCswSPtXSLsy8WKm7cMXVS3hjiO8sMZ97
+         mRMGZkYBJHjWP+ulkBXiq6q7/OQuE8Dkl+rWM=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:from:date:message-id:subject:to
+         :cc:content-type:content-transfer-encoding;
+        b=r0N6AOAg+TSvY2kPQPahldj4iRU9oUoSLtHA7JXG2QU4CR9O5GBhxAtr2aY99qUPZd
+         tFS0ZWRAb9cmOgiZhTpNxsBjCJ/e/DQ1ccP5rZ/U40q1SJ1KwN92hqpOoppZ0tkqSB7/
+         UlQtsvPSK7a0bYqufEmscfAi98w1+mfZIbK6U=
+Received: by 10.220.161.203 with SMTP id s11mr5093041vcx.195.1277931388141; 
+       Wed, 30 Jun 2010 13:56:28 -0700 (PDT)
+Received: by 10.220.161.137 with HTTP; Wed, 30 Jun 2010 13:55:58 -0700 (PDT)
+In-Reply-To: <20100308191005.GE4324@amak.tundra.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1004632>
+
+On Mon, Mar 8, 2010 at 2:10 PM, Alexandre Bounine <abounine@tundra.com>=
+ wrote:
+>
+> From: Alexandre Bounine <alexandre.bounine@idt.com>
+>
+> Add Machine Check exception handling into RapidIO port driver
+> for Freescale SoCs (MPC85xx).
+>
+> Signed-off-by: Alexandre Bounine <alexandre.bounine@idt.com>
+> Tested-by: Thomas Moll <thomas.moll@sysgo.com>
+=2E..
+
+> +static int fsl_rio_mcheck_exception(struct pt_regs *regs)
+> +{
+> + =A0 =A0 =A0 const struct exception_table_entry *entry =3D NULL;
+> + =A0 =A0 =A0 unsigned long reason =3D (mfspr(SPRN_MCSR) & MCSR_MASK)=
+;
+
+MCSR_MASK is not defined anywhere, so when I compile this code, I get t=
+his:
+
+  CC      arch/powerpc/sysdev/fsl_rio.o
+arch/powerpc/sysdev/fsl_rio.c: In function 'fsl_rio_mcheck_exception':
+arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+(first use in this function)
+arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+is reported only once
+arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears =
+in.)
+
+--=20
+Timur Tabi
+Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298770.004582:2, b/test/corpora/lkml/cur/1382298770.004582:2,
new file mode 100644 (file)
index 0000000..d149b72
--- /dev/null
@@ -0,0 +1,68 @@
+From: Timur Tabi <timur.tabi@gmail.com>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Wed, 30 Jun 2010 16:00:56 -0500
+Lines: 12
+Message-ID: <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Cc: mporter@kernel.crashing.org, linux-kernel@vger.kernel.org,
+       linuxppc-dev@lists.ozlabs.org, thomas.moll@sysgo.com
+To: Alexandre Bounine <abounine@tundra.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Jun 30 23:01:37 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OU4PZ-0000HS-0T
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 30 Jun 2010 23:01:37 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755703Ab0F3VB2 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 30 Jun 2010 17:01:28 -0400
+Received: from mail-vw0-f46.google.com ([209.85.212.46]:53141 "EHLO
+       mail-vw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751784Ab0F3VB1 (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 30 Jun 2010 17:01:27 -0400
+Received: by vws5 with SMTP id 5so1454517vws.19
+        for <linux-kernel@vger.kernel.org>; Wed, 30 Jun 2010 14:01:26 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:mime-version:received:in-reply-to
+         :references:from:date:message-id:subject:to:cc:content-type;
+        bh=+BUKti+Oa03CrnVvRyT591FhcoxqR7S2rzZHtD6WSuY=;
+        b=O/b04HLJrmTE0aIq2mNCRznQrXxAAGHSMarHR5mrgYptmr68froM6UgmDqTZFLhNiH
+         BcT8g+AziiqSV1k/ckXjRyVR0s9Jdv4g2phMNtp8NStbPfOPpLDkUKTQadphOTonCfeK
+         e+ZrLBwh+FCoYNAOjvFioBKj6CxN2Oi5xIhPc=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:in-reply-to:references:from:date:message-id:subject:to
+         :cc:content-type;
+        b=UcKGhJIXCTTcSvBWwGwLUefPONGygVPsUnTt4nDSl4udB8JKMyi0EghzzgNXUyq4Dz
+         UCxzZAyxzjvjgsgPS3kzPhSsWG2PRG66pC1OA68RJ5YVOjt55/yOz/yfTqXBVvRSq2fV
+         QNcKACYHSjkIZ7Uq7ZEW9bEGI5tTKdz++N2UA=
+Received: by 10.220.124.73 with SMTP id t9mr5099129vcr.37.1277931686462; Wed, 
+       30 Jun 2010 14:01:26 -0700 (PDT)
+Received: by 10.220.161.137 with HTTP; Wed, 30 Jun 2010 14:00:56 -0700 (PDT)
+In-Reply-To: <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1004633>
+
+On Wed, Jun 30, 2010 at 3:55 PM, Timur Tabi <timur.tabi@gmail.com> wrote:
+
+> MCSR_MASK is not defined anywhere, so when I compile this code, I get this:
+
+Never mind.  I see that it's been fixed already, and that the patch
+that removed MCSR_MASK was posted around the same time that this patch
+was posted.
+
+
+-- 
+Timur Tabi
+Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298775.002830:2, b/test/corpora/lkml/cur/1382298775.002830:2,
new file mode 100644 (file)
index 0000000..1bf40bc
--- /dev/null
@@ -0,0 +1,60 @@
+From: Michael Neuling <mikey@neuling.org>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Tue, 03 Aug 2010 16:06:30 +1000
+Lines: 15
+Message-ID: <4381.1280815590@neuling.org>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com> <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+Cc: Alexandre Bounine <abounine@tundra.com>,
+       linuxppc-dev@lists.ozlabs.org, linux-kernel@vger.kernel.org,
+       thomas.moll@sysgo.com
+To: Timur Tabi <timur.tabi@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Aug 03 08:06:45 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OgAeD-00087x-ED
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 03 Aug 2010 08:06:45 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755287Ab0HCGGf (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 3 Aug 2010 02:06:35 -0400
+Received: from ozlabs.org ([203.10.76.45]:51158 "EHLO ozlabs.org"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1755139Ab0HCGGd (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 3 Aug 2010 02:06:33 -0400
+Received: from localhost.localdomain (localhost [127.0.0.1])
+       by ozlabs.org (Postfix) with ESMTP id B7A371007D1;
+       Tue,  3 Aug 2010 16:06:31 +1000 (EST)
+Received: by localhost.localdomain (Postfix, from userid 1000)
+       id EDBB7C5EB7; Tue,  3 Aug 2010 16:06:30 +1000 (EST)
+Received: from neuling.org (localhost [127.0.0.1])
+       by localhost.localdomain (Postfix) with ESMTP id E8003C51D3;
+       Tue,  3 Aug 2010 16:06:30 +1000 (EST)
+In-reply-to: <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+Comments: In-reply-to Timur Tabi <timur.tabi@gmail.com>
+   message dated "Wed, 30 Jun 2010 16:00:56 -0500."
+X-Mailer: MH-E 8.2; nmh 1.3; GNU Emacs 23.1.1
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1017846>
+
+> > MCSR_MASK is not defined anywhere, so when I compile this code, I get this:
+> 
+> Never mind.  I see that it's been fixed already, and that the patch
+> that removed MCSR_MASK was posted around the same time that this patch
+> was posted.
+
+I don't know what happened here but 2.6.35 is broken because of this
+problem:
+
+arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared (first use in this function)
+arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier is reported only once
+arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears in.)
+arch/powerpc/sysdev/fsl_rio.c:250: error: 'MCSR_BUS_RBERR' undeclared (first use in this function)
+
+Mikey
+
+
diff --git a/test/corpora/lkml/cur/1382298775.002978:2, b/test/corpora/lkml/cur/1382298775.002978:2,
new file mode 100644 (file)
index 0000000..21e2a10
--- /dev/null
@@ -0,0 +1,91 @@
+From: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+Subject: RE: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Tue, 3 Aug 2010 05:17:54 -0700
+Lines: 34
+Message-ID: <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com> <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com> <4381.1280815590@neuling.org>
+Mime-Version: 1.0
+Content-Type: text/plain;
+       charset="us-ascii"
+Content-Transfer-Encoding: 8BIT
+Cc: "Alexandre Bounine" <abounine@tundra.com>,
+       <linuxppc-dev@lists.ozlabs.org>, <linux-kernel@vger.kernel.org>,
+       <thomas.moll@sysgo.com>
+To: "Michael Neuling" <mikey@neuling.org>,
+       "Timur Tabi" <timur.tabi@gmail.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Aug 03 14:27:12 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OgGaG-0002zE-Fr
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 03 Aug 2010 14:27:04 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756073Ab0HCM0x (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 3 Aug 2010 08:26:53 -0400
+Received: from mxout1.idt.com ([157.165.5.25]:35046 "EHLO mxout1.idt.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1756010Ab0HCM0w convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 3 Aug 2010 08:26:52 -0400
+X-Greylist: delayed 521 seconds by postgrey-1.27 at vger.kernel.org; Tue, 03 Aug 2010 08:26:52 EDT
+Received: from mail.idt.com (localhost [127.0.0.1])
+       by mxout1.idt.com (8.13.1/8.13.1) with ESMTP id o73CHxil001904;
+       Tue, 3 Aug 2010 05:17:59 -0700
+Received: from corpml3.corp.idt.com (corpml3.corp.idt.com [157.165.140.25])
+       by mail.idt.com (8.13.8/8.13.8) with ESMTP id o73CHvit016488;
+       Tue, 3 Aug 2010 05:17:57 -0700 (PDT)
+Received: from CORPEXCH1.na.ads.idt.com (localhost [127.0.0.1])
+       by corpml3.corp.idt.com (8.11.7p1+Sun/8.11.7) with ESMTP id o73CHtN07516;
+       Tue, 3 Aug 2010 05:17:55 -0700 (PDT)
+X-MimeOLE: Produced By Microsoft Exchange V6.5
+Content-class: urn:content-classes:message
+In-Reply-To: <4381.1280815590@neuling.org>
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+Thread-Topic: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Thread-Index: Acsy0pTOmhzzm4GETvS4r2R2pYb40wAMtx8w
+X-Scanned-By: MIMEDefang 2.43
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1017995>
+
+This happened after change to book-e definitions.
+There are patches that address this issue.
+
+> -----Original Message-----
+> From: Michael Neuling [mailto:mikey@neuling.org]
+> Sent: Tuesday, August 03, 2010 2:07 AM
+> To: Timur Tabi
+> Cc: Alexandre Bounine; linuxppc-dev@lists.ozlabs.org;
+linux-kernel@vger.kernel.org;
+> thomas.moll@sysgo.com
+> Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO
+port
+> 
+> > > MCSR_MASK is not defined anywhere, so when I compile this code, I
+get this:
+> >
+> > Never mind.  I see that it's been fixed already, and that the patch
+> > that removed MCSR_MASK was posted around the same time that this
+patch
+> > was posted.
+> 
+> I don't know what happened here but 2.6.35 is broken because of this
+> problem:
+> 
+> arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+(first use in this function)
+> arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+is reported only once
+> arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears
+in.)
+> arch/powerpc/sysdev/fsl_rio.c:250: error: 'MCSR_BUS_RBERR' undeclared
+(first use in this function)
+> 
+> Mikey
+
+
diff --git a/test/corpora/lkml/cur/1382298775.002992:2, b/test/corpora/lkml/cur/1382298775.002992:2,
new file mode 100644 (file)
index 0000000..0f11acd
--- /dev/null
@@ -0,0 +1,87 @@
+From: Timur Tabi <timur@freescale.com>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Tue, 3 Aug 2010 08:01:51 -0500
+Lines: 25
+Message-ID: <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com> 
+       <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com> 
+       <4381.1280815590@neuling.org> <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Cc: Michael Neuling <mikey@neuling.org>,
+       Alexandre Bounine <abounine@tundra.com>,
+       linuxppc-dev@lists.ozlabs.org, linux-kernel@vger.kernel.org,
+       thomas.moll@sysgo.com, Kumar Gala <galak@kernel.crashing.org>
+To: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Aug 03 15:02:39 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OgH8b-0003r0-5v
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 03 Aug 2010 15:02:33 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756383Ab0HCNCY (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 3 Aug 2010 09:02:24 -0400
+Received: from mail-qy0-f181.google.com ([209.85.216.181]:47377 "EHLO
+       mail-qy0-f181.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1754253Ab0HCNCX (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 3 Aug 2010 09:02:23 -0400
+Received: by qyk7 with SMTP id 7so647758qyk.19
+        for <linux-kernel@vger.kernel.org>; Tue, 03 Aug 2010 06:02:22 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:mime-version:sender:received
+         :in-reply-to:references:from:date:x-google-sender-auth:message-id
+         :subject:to:cc:content-type;
+        bh=vTJghTE4Rwcgvgu1RS/86u/ljjztFlVQ5ODYWXBRkUM=;
+        b=p7S+ZVc0INWI6uXFwsLVTTEnV8wFAB0u0cDLt5qp0gyuMbF9yqXhukSTbYS8Vf8gCk
+         UFDmrOGjzC1whtvZnRS+Q80vVTR3+1URt/RTCUqirvalLvgluNrzP6sQ3xccFy4LkdLi
+         nGsgcNEqVwPPZgg3uSqew6B5UIoH7S00YzAYU=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=mime-version:sender:in-reply-to:references:from:date
+         :x-google-sender-auth:message-id:subject:to:cc:content-type;
+        b=qNVeIlTzozhY9MXH5PHYIsAL8T7zOBZ+0hWrBlbEy0PiBHW1AAv9nNd6FspugBZVUW
+         q7iPmhg0n6Oa3KFBNjs42dInyCPUqiQs10rGTQCJsSVITmZ/NA9sf8FFbI+Dg7xQiJKj
+         TN/8W0tBK9mUiqVvoO1avTKG1hqyMwTdMqlaM=
+Received: by 10.224.73.18 with SMTP id o18mr2669587qaj.354.1280840541149; Tue, 
+       03 Aug 2010 06:02:21 -0700 (PDT)
+Received: by 10.220.112.69 with HTTP; Tue, 3 Aug 2010 06:01:51 -0700 (PDT)
+In-Reply-To: <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+X-Google-Sender-Auth: lBedzmn1VMYh0pQjuCJuDw-lNh8
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1018009>
+
+On Tue, Aug 3, 2010 at 7:17 AM, Bounine, Alexandre
+<Alexandre.Bounine@idt.com> wrote:
+> This happened after change to book-e definitions.
+> There are patches that address this issue.
+
+And those patches should have been applied before 2.6.35 was released.
+ Someone dropped the ball.  2.6.35 is broken for a number of PowerPC
+boards:
+
+$ make mpc85xx_defconfig
+...
+$ make
+...
+  CC      arch/powerpc/sysdev/fsl_rio.o
+arch/powerpc/sysdev/fsl_rio.c: In function 'fsl_rio_mcheck_exception':
+arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+(first use in this function)
+arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+is reported only once
+arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears in.)
+make[1]: *** [arch/powerpc/sysdev/fsl_rio.o] Error 1
+
+-- 
+Timur Tabi
+Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298775.002999:2, b/test/corpora/lkml/cur/1382298775.002999:2,
new file mode 100644 (file)
index 0000000..e6456b6
--- /dev/null
@@ -0,0 +1,109 @@
+From: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+Subject: RE: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Tue, 3 Aug 2010 06:24:47 -0700
+Lines: 40
+Message-ID: <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com>
+References: <20100308191005.GE4324@amak.tundra.com>
+       <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+       <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+       <4381.1280815590@neuling.org>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+       <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Michael Neuling <mikey@neuling.org>, linux-kernel@vger.kernel.org,
+       Alexandre Bounine <abounine@tundra.com>, thomas.moll@sysgo.com,
+       linuxppc-dev@lists.ozlabs.org
+To: "Timur Tabi" <timur@freescale.com>
+X-From: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org Tue Aug 03 15:25:22 2010
+Return-path: <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>
+Envelope-to: glppe-linuxppc-embedded-2@m.gmane.org
+Received: from ozlabs.org ([203.10.76.45])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>)
+       id 1OgHUd-0006wG-MW
+       for glppe-linuxppc-embedded-2@m.gmane.org; Tue, 03 Aug 2010 15:25:20 +0200
+Received: from bilbo.ozlabs.org (localhost [127.0.0.1])
+       by ozlabs.org (Postfix) with ESMTP id 54FA51007E4
+       for <glppe-linuxppc-embedded-2@m.gmane.org>; Tue,  3 Aug 2010 23:25:09 +1000 (EST)
+Received: from mxout1.idt.com (mxout1.idt.com [157.165.5.25])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (Client CN "mxout1.idt.com", Issuer "idt.com" (not verified))
+       by ozlabs.org (Postfix) with ESMTPS id 5C917B70A6
+       for <linuxppc-dev@lists.ozlabs.org>;
+       Tue,  3 Aug 2010 23:25:00 +1000 (EST)
+Received: from mail.idt.com (localhost [127.0.0.1])
+       by mxout1.idt.com (8.13.1/8.13.1) with ESMTP id o73DOrjO005661;
+       Tue, 3 Aug 2010 06:24:54 -0700
+Received: from corpml1.corp.idt.com (corpml1.corp.idt.com [157.165.140.20])
+       by mail.idt.com (8.13.8/8.13.8) with ESMTP id o73DOndw022603;
+       Tue, 3 Aug 2010 06:24:50 -0700 (PDT)
+Received: from CORPEXCH1.na.ads.idt.com (localhost [127.0.0.1])
+       by corpml1.corp.idt.com (8.11.7p1+Sun/8.11.7) with ESMTP id
+       o73DOml00291; Tue, 3 Aug 2010 06:24:48 -0700 (PDT)
+X-MimeOLE: Produced By Microsoft Exchange V6.5
+Content-class: urn:content-classes:message
+In-Reply-To: <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com>
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+Thread-Topic: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Thread-Index: AcszC8UElBYHaZlHSsmvZEE2KBL+0wAAhg7A
+X-Scanned-By: MIMEDefang 2.43
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <linuxppc-dev.lists.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=subscribe>
+Sender: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Errors-To: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1018016>
+
+Yang Li pointed to these patches in his post from July 23, 2010.
+It would be nice to have these patches in mainline code. 
+
+> -----Original Message-----
+> From: timur.tabi@gmail.com [mailto:timur.tabi@gmail.com] On Behalf Of
+Timur Tabi
+> Sent: Tuesday, August 03, 2010 9:02 AM
+> To: Bounine, Alexandre
+> Cc: Michael Neuling; Alexandre Bounine; linuxppc-dev@lists.ozlabs.org;
+linux-kernel@vger.kernel.org;
+> thomas.moll@sysgo.com; Kumar Gala
+> Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO
+port
+> 
+> On Tue, Aug 3, 2010 at 7:17 AM, Bounine, Alexandre
+> <Alexandre.Bounine@idt.com> wrote:
+> > This happened after change to book-e definitions.
+> > There are patches that address this issue.
+> 
+> And those patches should have been applied before 2.6.35 was released.
+>  Someone dropped the ball.  2.6.35 is broken for a number of PowerPC
+> boards:
+> 
+> $ make mpc85xx_defconfig
+> ....
+> $ make
+> ....
+>   CC      arch/powerpc/sysdev/fsl_rio.o
+> arch/powerpc/sysdev/fsl_rio.c: In function 'fsl_rio_mcheck_exception':
+> arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+> (first use in this function)
+> arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+> is reported only once
+> arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears
+in.)
+> make[1]: *** [arch/powerpc/sysdev/fsl_rio.o] Error 1
+> 
+> --
+> Timur Tabi
+> Linux kernel developer at Freescale
+
+
diff --git a/test/corpora/lkml/cur/1382298775.003976:2, b/test/corpora/lkml/cur/1382298775.003976:2,
new file mode 100644 (file)
index 0000000..a6ff629
--- /dev/null
@@ -0,0 +1,96 @@
+From: Michael Neuling <mikey@neuling.org>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Thu, 05 Aug 2010 13:34:20 +1000
+Lines: 50
+Message-ID: <26581.1280979260@neuling.org>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com> <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com> <4381.1280815590@neuling.org> <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com> <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com> <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com>
+Cc: "Timur Tabi" <timur@freescale.com>,
+       "Alexandre Bounine" <abounine@tundra.com>,
+       linuxppc-dev@lists.ozlabs.org, linux-kernel@vger.kernel.org,
+       thomas.moll@sysgo.com, "Kumar Gala" <galak@kernel.crashing.org>
+To: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+X-From: linux-kernel-owner@vger.kernel.org Thu Aug 05 05:34:37 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1OgrE1-00024O-Bh
+       for glk-linux-kernel-3@lo.gmane.org; Thu, 05 Aug 2010 05:34:33 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1758775Ab0HEDeX (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 4 Aug 2010 23:34:23 -0400
+Received: from ozlabs.org ([203.10.76.45]:40810 "EHLO ozlabs.org"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1758704Ab0HEDeV (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 4 Aug 2010 23:34:21 -0400
+Received: from localhost.localdomain (localhost [127.0.0.1])
+       by ozlabs.org (Postfix) with ESMTP id 97995B70D8;
+       Thu,  5 Aug 2010 13:34:20 +1000 (EST)
+Received: by localhost.localdomain (Postfix, from userid 1000)
+       id 456CDCC199; Thu,  5 Aug 2010 13:34:20 +1000 (EST)
+Received: from neuling.org (localhost [127.0.0.1])
+       by localhost.localdomain (Postfix) with ESMTP id 404C8C6123;
+       Thu,  5 Aug 2010 13:34:20 +1000 (EST)
+In-reply-to: <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com>
+Comments: In-reply-to "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+   message dated "Tue, 03 Aug 2010 06:24:47 -0700."
+X-Mailer: MH-E 8.2; nmh 1.3; GNU Emacs 23.1.1
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1019004>
+
+
+
+In message <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com> you wrote:
+> Yang Li pointed to these patches in his post from July 23, 2010.
+> It would be nice to have these patches in mainline code.=20
+
+This is still broken in Kumar's latest tree.  Do you guys wanna repost
+them so Kumar can pick them up easily?
+
+Mikey
+
+> 
+> > -----Original Message-----
+> > From: timur.tabi@gmail.com [mailto:timur.tabi@gmail.com] On Behalf Of
+> Timur Tabi
+> > Sent: Tuesday, August 03, 2010 9:02 AM
+> > To: Bounine, Alexandre
+> > Cc: Michael Neuling; Alexandre Bounine; linuxppc-dev@lists.ozlabs.org;
+> linux-kernel@vger.kernel.org;
+> > thomas.moll@sysgo.com; Kumar Gala
+> > Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO
+> port
+> >=20
+> > On Tue, Aug 3, 2010 at 7:17 AM, Bounine, Alexandre
+> > <Alexandre.Bounine@idt.com> wrote:
+> > > This happened after change to book-e definitions.
+> > > There are patches that address this issue.
+> >=20
+> > And those patches should have been applied before 2.6.35 was released.
+> >  Someone dropped the ball.  2.6.35 is broken for a number of PowerPC
+> > boards:
+> >=20
+> > $ make mpc85xx_defconfig
+> > ....
+> > $ make
+> > ....
+> >   CC      arch/powerpc/sysdev/fsl_rio.o
+> > arch/powerpc/sysdev/fsl_rio.c: In function 'fsl_rio_mcheck_exception':
+> > arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+> > (first use in this function)
+> > arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared identifier
+> > is reported only once
+> > arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it appears
+> in.)
+> > make[1]: *** [arch/powerpc/sysdev/fsl_rio.o] Error 1
+> >=20
+> > --
+> > Timur Tabi
+> > Linux kernel developer at Freescale
+> 
+
+
diff --git a/test/corpora/lkml/cur/1382298775.004354:2, b/test/corpora/lkml/cur/1382298775.004354:2,
new file mode 100644 (file)
index 0000000..2d69a12
--- /dev/null
@@ -0,0 +1,170 @@
+From: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+Subject: RE: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Thu, 5 Aug 2010 10:25:18 -0700
+Lines: 99
+Message-ID: <0CE8B6BE3C4AD74AB97D9D29BD24E552011935BD@CORPEXCH1.na.ads.idt.com>
+References: <20100308191005.GE4324@amak.tundra.com>
+       <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+       <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+       <4381.1280815590@neuling.org>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+       <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com>
+       <26581.1280979260@neuling.org>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Li Yang-R58472 <r58472@freescale.com>, linux-kernel@vger.kernel.org,
+       Alexandre Bounine <abounine@tundra.com>, thomas.moll@sysgo.com,
+       linuxppc-dev@lists.ozlabs.org, Timur Tabi <timur@freescale.com>
+To: "Michael Neuling" <mikey@neuling.org>
+X-From: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org Thu Aug 05 19:25:54 2010
+Return-path: <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>
+Envelope-to: glppe-linuxppc-embedded-2@m.gmane.org
+Received: from ozlabs.org ([203.10.76.45])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>)
+       id 1Oh4CX-0002Xd-Hu
+       for glppe-linuxppc-embedded-2@m.gmane.org; Thu, 05 Aug 2010 19:25:54 +0200
+Received: from bilbo.ozlabs.org (localhost [127.0.0.1])
+       by ozlabs.org (Postfix) with ESMTP id ED044100873
+       for <glppe-linuxppc-embedded-2@m.gmane.org>; Fri,  6 Aug 2010 03:25:45 +1000 (EST)
+Received: from mxout1.idt.com (mxout1.idt.com [157.165.5.25])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (Client CN "mxout1.idt.com", Issuer "idt.com" (not verified))
+       by ozlabs.org (Postfix) with ESMTPS id 43F72B6EEA
+       for <linuxppc-dev@lists.ozlabs.org>;
+       Fri,  6 Aug 2010 03:25:34 +1000 (EST)
+Received: from mail.idt.com (localhost [127.0.0.1])
+       by mxout1.idt.com (8.13.1/8.13.1) with ESMTP id o75HPQdX013269;
+       Thu, 5 Aug 2010 10:25:26 -0700
+Received: from corpml1.corp.idt.com (corpml1.corp.idt.com [157.165.140.20])
+       by mail.idt.com (8.13.8/8.13.8) with ESMTP id o75HPMOi016437;
+       Thu, 5 Aug 2010 10:25:23 -0700 (PDT)
+Received: from CORPEXCH1.na.ads.idt.com (localhost [127.0.0.1])
+       by corpml1.corp.idt.com (8.11.7p1+Sun/8.11.7) with ESMTP id
+       o75HPKp19185; Thu, 5 Aug 2010 10:25:21 -0700 (PDT)
+X-MimeOLE: Produced By Microsoft Exchange V6.5
+Content-class: urn:content-classes:message
+In-Reply-To: <26581.1280979260@neuling.org>
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+Thread-Topic: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Thread-Index: Acs0TsVj0+lZKwxtSsOT8qDn1XxpJAAdBmhA
+X-Scanned-By: MIMEDefang 2.43
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <linuxppc-dev.lists.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=subscribe>
+Sender: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Errors-To: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1019383>
+
+Below is a copy of Leo's message with pointers to the patches.
+
+Alex.
+>Subject: [PATCH] RapidIO,powerpc/85xx: remove MCSR_MASK in fsl_rio
+>
+>Fixes compile problem caused by MCSR_MASK removal from book-E
+definitions.
+
+Hi Alex,
+
+Only with your patch, there will still be problem on SRIO platforms
+other than MPC85xx.
+
+I have posted a patch series to fix this together with several
+compatibility issues a month before.
+
+http://patchwork.ozlabs.org/patch/56135/
+http://patchwork.ozlabs.org/patch/56136/
+http://patchwork.ozlabs.org/patch/56138/
+http://patchwork.ozlabs.org/patch/56137/
+
+
+Can anyone pick the patch series quickly as currently there is a compile
+error when SRIO is enabled.
+
+- Leo
+
+
+> -----Original Message-----
+> From: Michael Neuling [mailto:mikey@neuling.org]
+> Sent: Wednesday, August 04, 2010 11:34 PM
+> To: Bounine, Alexandre
+> Cc: Timur Tabi; Alexandre Bounine; linuxppc-dev@lists.ozlabs.org;
+linux-kernel@vger.kernel.org;
+> thomas.moll@sysgo.com; Kumar Gala
+> Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO
+port
+> 
+> 
+> 
+> In message
+<0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com> you
+wrote:
+> > Yang Li pointed to these patches in his post from July 23, 2010.
+> > It would be nice to have these patches in mainline code.=20
+> 
+> This is still broken in Kumar's latest tree.  Do you guys wanna repost
+> them so Kumar can pick them up easily?
+> 
+> Mikey
+> 
+> >
+> > > -----Original Message-----
+> > > From: timur.tabi@gmail.com [mailto:timur.tabi@gmail.com] On Behalf
+Of
+> > Timur Tabi
+> > > Sent: Tuesday, August 03, 2010 9:02 AM
+> > > To: Bounine, Alexandre
+> > > Cc: Michael Neuling; Alexandre Bounine;
+linuxppc-dev@lists.ozlabs.org;
+> > linux-kernel@vger.kernel.org;
+> > > thomas.moll@sysgo.com; Kumar Gala
+> > > Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for
+SRIO
+> > port
+> > >=20
+> > > On Tue, Aug 3, 2010 at 7:17 AM, Bounine, Alexandre
+> > > <Alexandre.Bounine@idt.com> wrote:
+> > > > This happened after change to book-e definitions.
+> > > > There are patches that address this issue.
+> > >=20
+> > > And those patches should have been applied before 2.6.35 was
+released.
+> > >  Someone dropped the ball.  2.6.35 is broken for a number of
+PowerPC
+> > > boards:
+> > >=20
+> > > $ make mpc85xx_defconfig
+> > > ....
+> > > $ make
+> > > ....
+> > >   CC      arch/powerpc/sysdev/fsl_rio.o
+> > > arch/powerpc/sysdev/fsl_rio.c: In function
+'fsl_rio_mcheck_exception':
+> > > arch/powerpc/sysdev/fsl_rio.c:248: error: 'MCSR_MASK' undeclared
+> > > (first use in this function)
+> > > arch/powerpc/sysdev/fsl_rio.c:248: error: (Each undeclared
+identifier
+> > > is reported only once
+> > > arch/powerpc/sysdev/fsl_rio.c:248: error: for each function it
+appears
+> > in.)
+> > > make[1]: *** [arch/powerpc/sysdev/fsl_rio.o] Error 1
+> > >=20
+> > > --
+> > > Timur Tabi
+> > > Linux kernel developer at Freescale
+> >
+
+
diff --git a/test/corpora/lkml/cur/1382298775.004363:2, b/test/corpora/lkml/cur/1382298775.004363:2,
new file mode 100644 (file)
index 0000000..f4198fb
--- /dev/null
@@ -0,0 +1,95 @@
+From: Kumar Gala <galak@kernel.crashing.org>
+Subject: Re: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Thu, 5 Aug 2010 12:53:03 -0500
+Lines: 34
+Message-ID: <C9528078-D64C-4944-B960-0E985B3EE0BA@kernel.crashing.org>
+References: <20100308191005.GE4324@amak.tundra.com>
+       <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com>
+       <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com>
+       <4381.1280815590@neuling.org>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com>
+       <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com>
+       <26581.1280979260@neuling.org>
+       <0CE8B6BE3C4AD74AB97D9D29BD24E552011935BD@CORPEXCH1.na.ads.idt.com>
+Mime-Version: 1.0 (Apple Message framework v1081)
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Michael Neuling <mikey@neuling.org>, Li Yang-R58472 <r58472@freescale.com>,
+       linux-kernel@vger.kernel.org,
+       Alexandre Bounine <abounine@tundra.com>, thomas.moll@sysgo.com,
+       linuxppc-dev@lists.ozlabs.org, Timur Tabi <timur@freescale.com>
+To: "Bounine, Alexandre" <Alexandre.Bounine@IDT.COM>
+X-From: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org Thu Aug 05 19:53:49 2010
+Return-path: <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>
+Envelope-to: glppe-linuxppc-embedded-2@m.gmane.org
+Received: from ozlabs.org ([203.10.76.45])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org>)
+       id 1Oh4dY-0000mU-OI
+       for glppe-linuxppc-embedded-2@m.gmane.org; Thu, 05 Aug 2010 19:53:49 +0200
+Received: from bilbo.ozlabs.org (localhost [127.0.0.1])
+       by ozlabs.org (Postfix) with ESMTP id C0974B71BD
+       for <glppe-linuxppc-embedded-2@m.gmane.org>; Fri,  6 Aug 2010 03:53:41 +1000 (EST)
+Received: from gate.crashing.org (gate.crashing.org [63.228.1.57])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (Client did not present a certificate)
+       by ozlabs.org (Postfix) with ESMTPS id 707ADB6EF1
+       for <linuxppc-dev@lists.ozlabs.org>;
+       Fri,  6 Aug 2010 03:53:31 +1000 (EST)
+Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1])
+       by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o75Hr4pE020296;
+       Thu, 5 Aug 2010 12:53:05 -0500
+In-Reply-To: <0CE8B6BE3C4AD74AB97D9D29BD24E552011935BD@CORPEXCH1.na.ads.idt.com>
+X-Mailer: Apple Mail (2.1081)
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <linuxppc-dev.lists.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=subscribe>
+Sender: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Errors-To: linuxppc-dev-bounces+glppe-linuxppc-embedded-2=m.gmane.org@lists.ozlabs.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1019393>
+
+
+On Aug 5, 2010, at 12:25 PM, Bounine, Alexandre wrote:
+
+> Below is a copy of Leo's message with pointers to the patches.
+> 
+> Alex.
+> 
+>> Subject: [PATCH] RapidIO,powerpc/85xx: remove MCSR_MASK in fsl_rio
+>> 
+>> Fixes compile problem caused by MCSR_MASK removal from book-E
+> definitions.
+> 
+> Hi Alex,
+> 
+> Only with your patch, there will still be problem on SRIO platforms
+> other than MPC85xx.
+> 
+> I have posted a patch series to fix this together with several
+> compatibility issues a month before.
+> 
+> http://patchwork.ozlabs.org/patch/56135/
+> http://patchwork.ozlabs.org/patch/56136/
+> http://patchwork.ozlabs.org/patch/56138/
+> http://patchwork.ozlabs.org/patch/56137/
+> 
+> 
+> Can anyone pick the patch series quickly as currently there is a compile
+> error when SRIO is enabled.
+> 
+> - Leo
+
+I'm looking at this now and wondering what we added the mcheck handler for in the first place and what its trying to accomplish.
+
+- k
+
+
diff --git a/test/corpora/lkml/cur/1382298775.004374:2, b/test/corpora/lkml/cur/1382298775.004374:2,
new file mode 100644 (file)
index 0000000..48558ad
--- /dev/null
@@ -0,0 +1,75 @@
+From: "Bounine, Alexandre" <Alexandre.Bounine@idt.com>
+Subject: RE: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Date: Thu, 5 Aug 2010 11:17:58 -0700
+Lines: 18
+Message-ID: <0CE8B6BE3C4AD74AB97D9D29BD24E55201193609@CORPEXCH1.na.ads.idt.com>
+References: <20100308191005.GE4324@amak.tundra.com> <AANLkTine3pc2Ai2Woj81Y9fS_KgGs1sIMb2NMR6G74ww@mail.gmail.com> <AANLkTinKbimKyLpvFD7KOvavshu_n8gRcp2BvEJj0XZQ@mail.gmail.com> <4381.1280815590@neuling.org> <0CE8B6BE3C4AD74AB97D9D29BD24E5520114309D@CORPEXCH1.na.ads.idt.com> <AANLkTinpwYnyc1oN1VbtBgUF6bk6E5q_Gq1Dj3WXV3wc@mail.gmail.com> <0CE8B6BE3C4AD74AB97D9D29BD24E552011430BC@CORPEXCH1.na.ads.idt.com> <26581.1280979260@neuling.org> <0CE8B6BE3C4AD74AB97D9D29BD24E552011935BD@CORPEXCH1.na.ads.idt.com> <C9528078-D64C-4944-B960-0E985B3EE0BA@kernel.crashing.org>
+Mime-Version: 1.0
+Content-Type: text/plain;
+       charset="us-ascii"
+Content-Transfer-Encoding: 8BIT
+Cc: "Michael Neuling" <mikey@neuling.org>,
+       "Timur Tabi" <timur@freescale.com>,
+       "Alexandre Bounine" <abounine@tundra.com>,
+       <linuxppc-dev@lists.ozlabs.org>, <linux-kernel@vger.kernel.org>,
+       <thomas.moll@sysgo.com>, "Li Yang-R58472" <r58472@freescale.com>
+To: "Kumar Gala" <galak@kernel.crashing.org>
+X-From: linux-kernel-owner@vger.kernel.org Thu Aug 05 20:18:33 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1Oh51V-00075S-1W
+       for glk-linux-kernel-3@lo.gmane.org; Thu, 05 Aug 2010 20:18:33 +0200
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S934019Ab0HESSU (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Thu, 5 Aug 2010 14:18:20 -0400
+Received: from mxout1.idt.com ([157.165.5.25]:47318 "EHLO mxout1.idt.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S933252Ab0HESSS convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Thu, 5 Aug 2010 14:18:18 -0400
+Received: from mail.idt.com (localhost [127.0.0.1])
+       by mxout1.idt.com (8.13.1/8.13.1) with ESMTP id o75II315017058;
+       Thu, 5 Aug 2010 11:18:03 -0700
+Received: from corpml1.corp.idt.com (corpml1.corp.idt.com [157.165.140.20])
+       by mail.idt.com (8.13.8/8.13.8) with ESMTP id o75II1Ek021771;
+       Thu, 5 Aug 2010 11:18:01 -0700 (PDT)
+Received: from CORPEXCH1.na.ads.idt.com (localhost [127.0.0.1])
+       by corpml1.corp.idt.com (8.11.7p1+Sun/8.11.7) with ESMTP id o75II0M19896;
+       Thu, 5 Aug 2010 11:18:00 -0700 (PDT)
+X-MimeOLE: Produced By Microsoft Exchange V6.5
+Content-class: urn:content-classes:message
+In-Reply-To: <C9528078-D64C-4944-B960-0E985B3EE0BA@kernel.crashing.org>
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+Thread-Topic: [PATCH v2 5/7] powerpc/85xx: Add MChk handler for SRIO port
+Thread-Index: Acs0x5rSJ4s7P9ssRjKYVWxFQe3GMgAARQEw
+X-Scanned-By: MIMEDefang 2.43
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1019404>
+
+> I'm looking at this now and wondering what we added the mcheck handler
+for in the first place and what
+> its trying to accomplish.
+> 
+> - k
+
+This protects system from hanging if RIO link fails or enters error
+state. In some situations following maintenance read may initiate link
+recovery from error state.
+
+As it is now, MCheck mostly prevents system from hanging, but it also
+adds sense to return status of maintenance read routine. I am using
+return status in my new set of patches to check if RIO link is valid
+during error recovery.
+
+Alex.
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002253:2, b/test/corpora/lkml/cur/1382298793.002253:2,
new file mode 100644 (file)
index 0000000..cbd67e8
--- /dev/null
@@ -0,0 +1,208 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 00/44] remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:19 -0800
+Lines: 158
+Message-ID: <cover.1289789604.git.joe@perches.com>
+Cc: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org,
+       linux-tegra@vger.kernel.org, microblaze-uclinux@itee.uq.edu.au,
+       user-mode-linux-devel@lists.sourceforge.net,
+       user-mode-linux-user@lists.sourceforge.net,
+       cpufreq@vger.kernel.org, linux-i2c@vger.kernel.org,
+       netdev@vger.kernel.org, linux-media@vger.kernel.org,
+       linux-mmc@vger.kernel.org, e1000-devel@lists.sourceforge.net,
+       linux-wireless@vger.kernel.org, ath9k-devel@lists.ath9k.org,
+       platform-driver-x86@vger.kernel.org,
+       ibm-acpi-devel@lists.sourceforge.net, linux-s390@vger.kernel.org,
+       linux-scsi@vger.kernel.org,
+       spi-devel-general@lists.sourceforge.net,
+       devel@driverdev.osuosl.org, linux-usb@vger.kernel.org,
+       xen-devel@lists.xensource.com, virtualization@lists.osdl.org,
+       v9fs-developer@lists.sourceforge.net, ceph-devel@vger.kernel.org,
+       logfs@logfs.org, linux-nfs@vger.kernel.org,
+       ocfs2-devel@oss.oracle.com, linu
+To: Jiri Kosina <trivial@kernel.org>
+X-From: cpufreq-owner@vger.kernel.org Mon Nov 15 04:05:30 2010
+Return-path: <cpufreq-owner@vger.kernel.org>
+Envelope-to: glkc-cpufreq2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <cpufreq-owner@vger.kernel.org>)
+       id 1PHpNp-0000PT-Vh
+       for glkc-cpufreq2@lo.gmane.org; Mon, 15 Nov 2010 04:05:30 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754566Ab0KODF2 (ORCPT <rfc822;glkc-cpufreq2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:28 -0500
+Received: from mail.perches.com ([173.55.12.10]:1118 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751901Ab0KODF1 (ORCPT <rfc822;cpufreq@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:27 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 0D82A24368;
+       Sun, 14 Nov 2010 19:03:52 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+Sender: cpufreq-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <cpufreq.vger.kernel.org>
+X-Mailing-List: cpufreq@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062272>
+
+ya trivial series...
+
+Joe Perches (44):
+  arch/arm: Remove unnecessary semicolons
+  arch/microblaze: Remove unnecessary semicolons
+  arch/um: Remove unnecessary semicolons
+  drivers/cpufreq: Remove unnecessary semicolons
+  drivers/gpio: Remove unnecessary semicolons
+  drivers/i2c: Remove unnecessary semicolons
+  drivers/isdn: Remove unnecessary semicolons
+  drivers/leds: Remove unnecessary semicolons
+  drivers/media/video: Remove unnecessary semicolons
+  drivers/misc: Remove unnecessary semicolons
+  drivers/mmc: Remove unnecessary semicolons
+  drivers/net/bnx2x: Remove unnecessary semicolons
+  drivers/net/e1000e: Remove unnecessary semicolons
+  drivers/net/ixgbe: Remove unnecessary semicolons
+  drivers/net/vxge: Remove unnecessary semicolons
+  drivers/net/wireless/ath: Remove unnecessary semicolons
+  drivers/net/wireless/iwlwifi: Remove unnecessary semicolons
+  drivers/net/cnic.c: Remove unnecessary semicolons
+  drivers/platform/x86: Remove unnecessary semicolons
+  drivers/power: Remove unnecessary semicolons
+  drivers/s390/net: Remove unnecessary semicolons
+  drivers/scsi/be2iscsi: Remove unnecessary semicolons
+  drivers/scsi/bfa: Remove unnecessary semicolons
+  drivers/scsi/lpfc: Remove unnecessary semicolons
+  drivers/scsi/pm8001: Remove unnecessary semicolons
+  drivers/scsi/qla2xxx: Remove unnecessary semicolons
+  drivers/serial: Remove unnecessary semicolons
+  drivers/spi: Remove unnecessary semicolons
+  drivers/staging: Remove unnecessary semicolons
+  drivers/usb/gadget: Remove unnecessary semicolons
+  drivers/xen: Remove unnecessary semicolons
+  fs/9p: Remove unnecessary semicolons
+  fs/ceph: Remove unnecessary semicolons
+  fs/logfs: Remove unnecessary semicolons
+  fs/nfs: Remove unnecessary semicolons
+  fs/ocfs2: Remove unnecessary semicolons
+  fs/ubifs: Remove unnecessary semicolons
+  include/linux/if_macvlan.h: Remove unnecessary semicolons
+  include/net/caif/cfctrl.h: Remove unnecessary semicolons
+  mm/hugetlb.c: Remove unnecessary semicolons
+  net/ipv6/mcast.c: Remove unnecessary semicolons
+  net/sunrpc/addr.c: Remove unnecessary semicolons
+  sound/core/pcm_lib.c: Remove unnecessary semicolons
+  sound/soc/codecs: Remove unnecessary semicolons
+
+ arch/arm/mach-at91/at91cap9_devices.c              |    2 +-
+ arch/arm/mach-at91/at91sam9g45_devices.c           |    2 +-
+ arch/arm/mach-at91/at91sam9rl_devices.c            |    2 +-
+ arch/arm/mach-nuc93x/time.c                        |    2 +-
+ arch/arm/mach-tegra/tegra2_clocks.c                |    2 +-
+ arch/arm/mach-w90x900/cpu.c                        |    2 +-
+ arch/arm/plat-mxc/irq.c                            |    2 +-
+ arch/microblaze/lib/memmove.c                      |    2 +-
+ arch/um/drivers/mmapper_kern.c                     |    2 +-
+ drivers/cpufreq/cpufreq_conservative.c             |    2 +-
+ drivers/gpio/langwell_gpio.c                       |    2 +-
+ drivers/i2c/busses/i2c-designware.c                |    2 +-
+ drivers/isdn/hardware/mISDN/mISDNinfineon.c        |    4 ++--
+ drivers/isdn/hardware/mISDN/mISDNisar.c            |    2 +-
+ drivers/leds/leds-mc13783.c                        |    2 +-
+ drivers/media/video/cx88/cx88-blackbird.c          |    2 +-
+ drivers/media/video/davinci/vpfe_capture.c         |    2 +-
+ drivers/media/video/em28xx/em28xx-cards.c          |    2 +-
+ drivers/misc/bmp085.c                              |    2 +-
+ drivers/misc/isl29020.c                            |    2 +-
+ drivers/mmc/host/davinci_mmc.c                     |    2 +-
+ drivers/net/bnx2x/bnx2x_link.c                     |    4 ++--
+ drivers/net/bnx2x/bnx2x_main.c                     |    2 +-
+ drivers/net/cnic.c                                 |    2 +-
+ drivers/net/e1000e/netdev.c                        |    2 +-
+ drivers/net/ixgbe/ixgbe_sriov.c                    |    2 +-
+ drivers/net/vxge/vxge-main.c                       |    2 +-
+ drivers/net/wireless/ath/ath9k/htc.h               |    2 +-
+ drivers/net/wireless/iwlwifi/iwl-agn.c             |    2 +-
+ drivers/platform/x86/classmate-laptop.c            |    2 +-
+ drivers/platform/x86/thinkpad_acpi.c               |    2 +-
+ drivers/power/intel_mid_battery.c                  |    2 +-
+ drivers/s390/net/qeth_core_sys.c                   |    2 +-
+ drivers/scsi/be2iscsi/be_main.c                    |    4 ++--
+ drivers/scsi/bfa/bfa_fcs_lport.c                   |    2 +-
+ drivers/scsi/lpfc/lpfc_bsg.c                       |    2 +-
+ drivers/scsi/pm8001/pm8001_init.c                  |    2 +-
+ drivers/scsi/qla2xxx/qla_isr.c                     |    4 ++--
+ drivers/scsi/qla2xxx/qla_nx.c                      |    2 +-
+ drivers/serial/mrst_max3110.c                      |    2 +-
+ drivers/spi/amba-pl022.c                           |    2 +-
+ drivers/spi/spi_nuc900.c                           |    2 +-
+ .../staging/ath6kl/hif/sdio/linux_sdio/src/hif.c   |    2 +-
+ drivers/staging/ath6kl/os/linux/ar6000_drv.c       |    2 +-
+ drivers/staging/bcm/InterfaceInit.c                |    2 +-
+ drivers/staging/bcm/InterfaceIsr.c                 |    2 +-
+ drivers/staging/bcm/Misc.c                         |    4 ++--
+ .../comedi/drivers/addi-data/APCI1710_Tor.c        |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci1500.c      |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci1516.c      |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci3501.c      |    2 +-
+ drivers/staging/comedi/drivers/amplc_pci230.c      |    2 +-
+ drivers/staging/comedi/drivers/cb_das16_cs.c       |    2 +-
+ drivers/staging/comedi/drivers/comedi_bond.c       |    2 +-
+ drivers/staging/crystalhd/crystalhd_hw.c           |    2 +-
+ drivers/staging/go7007/go7007-driver.c             |    2 +-
+ drivers/staging/iio/accel/lis3l02dq_ring.c         |    2 +-
+ .../staging/intel_sst/intel_sst_drv_interface.c    |    4 ++--
+ drivers/staging/keucr/smilmain.c                   |    4 ++--
+ drivers/staging/keucr/smilsub.c                    |    4 ++--
+ drivers/staging/msm/lcdc_toshiba_wvga_pt.c         |    2 +-
+ drivers/staging/rt2860/common/cmm_data_pci.c       |    4 ++--
+ drivers/staging/rt2860/rt_linux.c                  |    2 +-
+ drivers/staging/rt2860/rtmp.h                      |    2 +-
+ drivers/staging/rtl8192e/ieee80211/ieee80211_tx.c  |    2 +-
+ drivers/staging/rtl8192e/r819xE_phy.c              |    2 +-
+ drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c  |    2 +-
+ drivers/staging/rtl8192u/r8192U_core.c             |    2 +-
+ drivers/staging/rtl8192u/r819xU_phy.c              |    2 +-
+ drivers/staging/rtl8712/rtl8712_efuse.c            |    2 +-
+ drivers/staging/rtl8712/rtl8712_xmit.c             |    2 +-
+ drivers/staging/rtl8712/rtl871x_xmit.c             |    2 +-
+ drivers/staging/tidspbridge/core/tiomap3430.c      |    4 ++--
+ drivers/staging/tidspbridge/rmgr/nldr.c            |    2 +-
+ drivers/staging/vt6655/card.c                      |    2 +-
+ drivers/staging/vt6655/iwctl.c                     |    2 +-
+ drivers/staging/vt6655/wpa2.c                      |    4 ++--
+ drivers/staging/vt6656/baseband.c                  |    2 +-
+ drivers/staging/vt6656/iwctl.c                     |    2 +-
+ drivers/staging/vt6656/power.c                     |    2 +-
+ drivers/staging/vt6656/wpa2.c                      |    4 ++--
+ drivers/usb/gadget/f_fs.c                          |    2 +-
+ drivers/xen/swiotlb-xen.c                          |    2 +-
+ fs/9p/acl.c                                        |    2 +-
+ fs/9p/xattr.c                                      |    2 +-
+ fs/ceph/mds_client.c                               |    2 +-
+ fs/logfs/readwrite.c                               |    2 +-
+ fs/nfs/getroot.c                                   |    2 +-
+ fs/ocfs2/refcounttree.c                            |    2 +-
+ fs/ubifs/scan.c                                    |    2 +-
+ include/linux/if_macvlan.h                         |    2 +-
+ include/net/caif/cfctrl.h                          |    2 +-
+ mm/hugetlb.c                                       |    2 +-
+ net/ipv6/mcast.c                                   |    2 +-
+ net/sunrpc/addr.c                                  |    2 +-
+ sound/core/pcm_lib.c                               |    2 +-
+ sound/soc/codecs/wm8904.c                          |    2 +-
+ sound/soc/codecs/wm8940.c                          |    1 -
+ sound/soc/codecs/wm8993.c                          |    2 +-
+ sound/soc/codecs/wm_hubs.c                         |    2 +-
+ 100 files changed, 111 insertions(+), 112 deletions(-)
+
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe cpufreq" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002254:2, b/test/corpora/lkml/cur/1382298793.002254:2,
new file mode 100644 (file)
index 0000000..b495912
--- /dev/null
@@ -0,0 +1,100 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 13/44] drivers/net/e1000e: Remove unnecessary
+       semicolons
+Date: Sun, 14 Nov 2010 19:04:32 -0800
+Lines: 34
+Message-ID: <e5cf92d50de7924930d660a5865c3d60d9cd9dc5.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: e1000-devel@lists.sourceforge.net, Bruce Allan <bruce.w.allan@intel.com>,
+       Jesse Brandeburg <jesse.brandeburg@intel.com>,
+       linux-kernel@vger.kernel.org, Greg Rose <gregory.v.rose@intel.com>,
+       John Ronciak <john.ronciak@intel.com>,
+       Jeff Kirsher <jeffrey.t.kirsher@intel.com>, netdev@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: e1000-devel-bounces@lists.sourceforge.net Mon Nov 15 04:05:53 2010
+Return-path: <e1000-devel-bounces@lists.sourceforge.net>
+Envelope-to: glded-e1000-devel@m.gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PHpOD-0000a9-4r
+       for glded-e1000-devel@m.gmane.org; Mon, 15 Nov 2010 04:05:53 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PHpO5-0002a8-SR; Mon, 15 Nov 2010 03:05:45 +0000
+Received: from sog-mx-2.v43.ch3.sourceforge.com ([172.29.43.192]
+       helo=mx.sourceforge.net)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <joe@perches.com>) id 1PHpO5-0002Zz-D2
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 03:05:45 +0000
+X-ACL-Warn: 
+Received: from mail.perches.com ([173.55.12.10])
+       by sog-mx-2.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PHpO1-0002b4-4y
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 03:05:45 +0000
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 462EB24376;
+       Sun, 14 Nov 2010 19:04:03 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-Spam-Score: 0.1 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
+       domain 0.1 AWL AWL: From: address is in the auto white-list
+X-Headers-End: 1PHpO1-0002b4-4y
+X-BeenThere: e1000-devel@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "e100/e1000\(e\)/ixgb/igb/ixgbe development and discussion"
+       <e1000-devel.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>, 
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=e1000-devel>
+List-Post: <mailto:e1000-devel@lists.sourceforge.net>
+List-Help: <mailto:e1000-devel-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>,
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: e1000-devel-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062273>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/e1000e/netdev.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/e1000e/netdev.c b/drivers/net/e1000e/netdev.c
+index c4ca162..a6d54e4 100644
+--- a/drivers/net/e1000e/netdev.c
++++ b/drivers/net/e1000e/netdev.c
+@@ -4595,7 +4595,7 @@ dma_error:
+                       i += tx_ring->count;
+               i--;
+               buffer_info = &tx_ring->buffer_info[i];
+-              e1000_put_txbuf(adapter, buffer_info);;
++              e1000_put_txbuf(adapter, buffer_info);
+       }
+       return 0;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+_______________________________________________
+E1000-devel mailing list
+E1000-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/e1000-devel
+To learn more about Intel&#174; Ethernet, visit http://communities.intel.com/community/wired
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002255:2, b/test/corpora/lkml/cur/1382298793.002255:2,
new file mode 100644 (file)
index 0000000..1c158be
--- /dev/null
@@ -0,0 +1,100 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 14/44] drivers/net/ixgbe: Remove unnecessary
+       semicolons
+Date: Sun, 14 Nov 2010 19:04:33 -0800
+Lines: 34
+Message-ID: <7d2c334daa75c5221946a17d45c9de1901cf06e7.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: e1000-devel@lists.sourceforge.net, Bruce Allan <bruce.w.allan@intel.com>,
+       Jesse Brandeburg <jesse.brandeburg@intel.com>,
+       linux-kernel@vger.kernel.org, Greg Rose <gregory.v.rose@intel.com>,
+       John Ronciak <john.ronciak@intel.com>,
+       Jeff Kirsher <jeffrey.t.kirsher@intel.com>, netdev@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: e1000-devel-bounces@lists.sourceforge.net Mon Nov 15 04:05:55 2010
+Return-path: <e1000-devel-bounces@lists.sourceforge.net>
+Envelope-to: glded-e1000-devel@m.gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PHpOE-0000bY-KU
+       for glded-e1000-devel@m.gmane.org; Mon, 15 Nov 2010 04:05:54 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com)
+       by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PHpO6-0004H7-Hr; Mon, 15 Nov 2010 03:05:46 +0000
+Received: from sog-mx-4.v43.ch3.sourceforge.com ([172.29.43.194]
+       helo=mx.sourceforge.net)
+       by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <joe@perches.com>) id 1PHpO6-0004H2-2t
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 03:05:46 +0000
+X-ACL-Warn: 
+Received: from mail.perches.com ([173.55.12.10])
+       by sog-mx-4.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PHpO1-0006jE-SS
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 03:05:46 +0000
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 0D6062436F;
+       Sun, 14 Nov 2010 19:04:04 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-Spam-Score: 0.1 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
+       domain 0.1 AWL AWL: From: address is in the auto white-list
+X-Headers-End: 1PHpO1-0006jE-SS
+X-BeenThere: e1000-devel@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "e100/e1000\(e\)/ixgb/igb/ixgbe development and discussion"
+       <e1000-devel.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>, 
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=e1000-devel>
+List-Post: <mailto:e1000-devel@lists.sourceforge.net>
+List-Help: <mailto:e1000-devel-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>,
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: e1000-devel-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062274>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/ixgbe/ixgbe_sriov.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/ixgbe/ixgbe_sriov.c b/drivers/net/ixgbe/ixgbe_sriov.c
+index 5428153..93f40bc 100644
+--- a/drivers/net/ixgbe/ixgbe_sriov.c
++++ b/drivers/net/ixgbe/ixgbe_sriov.c
+@@ -68,7 +68,7 @@ static int ixgbe_set_vf_multicasts(struct ixgbe_adapter *adapter,
+        * addresses
+        */
+       for (i = 0; i < entries; i++) {
+-              vfinfo->vf_mc_hashes[i] = hash_list[i];;
++              vfinfo->vf_mc_hashes[i] = hash_list[i];
+       }
+       for (i = 0; i < vfinfo->num_vf_mc_hashes; i++) {
+-- 
+1.7.3.1.g432b3.dirty
+
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+_______________________________________________
+E1000-devel mailing list
+E1000-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/e1000-devel
+To learn more about Intel&#174; Ethernet, visit http://communities.intel.com/community/wired
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002256:2, b/test/corpora/lkml/cur/1382298793.002256:2,
new file mode 100644 (file)
index 0000000..8626b81
--- /dev/null
@@ -0,0 +1,89 @@
+From: Joe Perches <joe@perches.com>
+Subject: [uml-user] [PATCH 03/44] arch/um: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:22 -0800
+Lines: 28
+Message-ID: <9ab60a1761dde357ebc028c525dae7572e072588.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Jeff Dike <jdike@addtoit.com>, user-mode-linux-user@lists.sourceforge.net,
+       user-mode-linux-devel@lists.sourceforge.net, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: user-mode-linux-user-bounces@lists.sourceforge.net Mon Nov 15 04:06:03 2010
+Return-path: <user-mode-linux-user-bounces@lists.sourceforge.net>
+Envelope-to: gluu-user-mode-linux-user-592@gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <user-mode-linux-user-bounces@lists.sourceforge.net>)
+       id 1PHpOM-0000er-Jl
+       for gluu-user-mode-linux-user-592@gmane.org; Mon, 15 Nov 2010 04:06:02 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <user-mode-linux-user-bounces@lists.sourceforge.net>)
+       id 1PHpNz-0002ZS-PM; Mon, 15 Nov 2010 03:05:39 +0000
+Received: from sog-mx-2.v43.ch3.sourceforge.com ([172.29.43.192]
+       helo=mx.sourceforge.net)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <joe@perches.com>)
+       id 1PHpNy-0002ZA-Q9; Mon, 15 Nov 2010 03:05:38 +0000
+X-ACL-Warn: 
+Received: from mail.perches.com ([173.55.12.10])
+       by sog-mx-2.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PHpNu-0002aj-Ks; Mon, 15 Nov 2010 03:05:38 +0000
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 845BB2436D;
+       Sun, 14 Nov 2010 19:03:56 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-Spam-Score: 0.1 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
+       domain 0.1 AWL AWL: From: address is in the auto white-list
+X-Headers-End: 1PHpNu-0002aj-Ks
+X-BeenThere: user-mode-linux-user@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: The user-mode Linux user list
+       <user-mode-linux-user.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/user-mode-linux-user>,
+       <mailto:user-mode-linux-user-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=user-mode-linux-user>
+List-Post: <mailto:user-mode-linux-user@lists.sourceforge.net>
+List-Help: <mailto:user-mode-linux-user-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/user-mode-linux-user>,
+       <mailto:user-mode-linux-user-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: user-mode-linux-user-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062275>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ arch/um/drivers/mmapper_kern.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/arch/um/drivers/mmapper_kern.c b/arch/um/drivers/mmapper_kern.c
+index 8501e7d..6256fa9 100644
+--- a/arch/um/drivers/mmapper_kern.c
++++ b/arch/um/drivers/mmapper_kern.c
+@@ -122,7 +122,7 @@ static int __init mmapper_init(void)
+       if (err) {
+               printk(KERN_ERR "mmapper - misc_register failed, err = %d\n",
+                      err);
+-              return err;;
++              return err;
+       }
+       return 0;
+ }
+-- 
+1.7.3.1.g432b3.dirty
+
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002257:2, b/test/corpora/lkml/cur/1382298793.002257:2,
new file mode 100644 (file)
index 0000000..849da33
--- /dev/null
@@ -0,0 +1,81 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 40/44] mm/hugetlb.c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:59 -0800
+Lines: 28
+Message-ID: <59705f848d35b12ace640f92afcffea02cee0976.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: linux-mm@kvack.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: owner-linux-mm@kvack.org Mon Nov 15 04:06:05 2010
+Return-path: <owner-linux-mm@kvack.org>
+Envelope-to: glkm-linux-mm-2@m.gmane.org
+Received: from kanga.kvack.org ([205.233.56.17])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <owner-linux-mm@kvack.org>)
+       id 1PHpOO-0000gl-5U
+       for glkm-linux-mm-2@m.gmane.org; Mon, 15 Nov 2010 04:06:04 +0100
+Received: by kanga.kvack.org (Postfix)
+       id 6220A8D003C; Sun, 14 Nov 2010 22:06:03 -0500 (EST)
+Delivered-To: linux-mm-outgoing@kvack.org
+Received: by kanga.kvack.org (Postfix, from userid 40)
+       id 5D2638D0017; Sun, 14 Nov 2010 22:06:03 -0500 (EST)
+X-Original-To: int-list-linux-mm@kvack.org
+Delivered-To: int-list-linux-mm@kvack.org
+Received: by kanga.kvack.org (Postfix, from userid 63042)
+       id 427128D003C; Sun, 14 Nov 2010 22:06:03 -0500 (EST)
+X-Original-To: linux-mm@kvack.org
+Delivered-To: linux-mm@kvack.org
+Received: from mail203.messagelabs.com (mail203.messagelabs.com [216.82.254.243])
+       by kanga.kvack.org (Postfix) with SMTP id C08B98D0017
+       for <linux-mm@kvack.org>; Sun, 14 Nov 2010 22:06:02 -0500 (EST)
+X-VirusChecked: Checked
+X-Env-Sender: joe@perches.com
+X-Msg-Ref: server-4.tower-203.messagelabs.com!1289790361!41887937!1
+X-StarScan-Version: 6.2.9; banners=-,-,-
+X-Originating-IP: [173.55.12.10]
+X-SpamReason: No, hits=0.0 required=7.0 tests=
+Received: (qmail 4485 invoked from network); 15 Nov 2010 03:06:01 -0000
+Received: from mail.perches.com (HELO mail.perches.com) (173.55.12.10)
+  by server-4.tower-203.messagelabs.com with SMTP; 15 Nov 2010 03:06:01 -0000
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id EB70E2436B;
+       Sun, 14 Nov 2010 19:04:28 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.2
+Sender: owner-linux-mm@kvack.org
+Precedence: bulk
+X-Loop: owner-majordomo@kvack.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062276>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ mm/hugetlb.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/mm/hugetlb.c b/mm/hugetlb.c
+index c4a3558..8875242 100644
+--- a/mm/hugetlb.c
++++ b/mm/hugetlb.c
+@@ -540,7 +540,7 @@ static struct page *dequeue_huge_page_vma(struct hstate *h,
+       /* If reserves cannot be used, ensure enough pages are in the pool */
+       if (avoid_reserve && h->free_huge_pages - h->resv_huge_pages == 0)
+-              goto err;;
++              goto err;
+       for_each_zone_zonelist_nodemask(zone, z, zonelist,
+                                               MAX_NR_ZONES - 1, nodemask) {
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe, send a message with 'unsubscribe linux-mm' in
+the body to majordomo@kvack.org.  For more info on Linux MM,
+see: http://www.linux-mm.org/ .
+Fight unfair telecom policy in Canada: sign http://dissolvethecrtc.ca/
+Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002258:2, b/test/corpora/lkml/cur/1382298793.002258:2,
new file mode 100644 (file)
index 0000000..271136b
--- /dev/null
@@ -0,0 +1,86 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 31/44] drivers/xen: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:50 -0800
+Lines: 20
+Message-ID: <b3f95cd997859d5d714de322ce17810fe73460cd.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: virtualization@lists.osdl.org,
+       Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>,
+       xen-devel@lists.xensource.com, linux-kernel@vger.kernel.org,
+       Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
+To: Jiri Kosina <trivial@kernel.org>
+X-From: xen-devel-bounces@lists.xensource.com Mon Nov 15 04:06:05 2010
+Return-path: <xen-devel-bounces@lists.xensource.com>
+Envelope-to: gcvxd-xen-devel@m.gmane.org
+Received: from lists.colo.xensource.com ([70.42.241.110] helo=lists.xensource.com)
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <xen-devel-bounces@lists.xensource.com>)
+       id 1PHpON-0000fV-A9
+       for gcvxd-xen-devel@m.gmane.org; Mon, 15 Nov 2010 04:06:03 +0100
+Received: from localhost ([127.0.0.1] helo=lists.colo.xensource.com)
+       by lists.xensource.com with esmtp (Exim 4.43)
+       id 1PHpOK-0008Sa-VZ; Sun, 14 Nov 2010 19:06:01 -0800
+Received: from spam.xensource.com ([70.42.241.90])
+       by lists.xensource.com with esmtp (Exim 4.43) id 1PHpOG-0008R4-01
+       for xen-devel@lists.xensource.com; Sun, 14 Nov 2010 19:05:56 -0800
+X-ASG-Debug-ID: 1289790355-0739cd1c0001-8pertM
+Received: from mail.perches.com (mail.perches.com [173.55.12.10]) by
+       spam.xensource.com with ESMTP id XhkGr3VGEwXLx5vl for
+       <xen-devel@lists.xensource.com>;
+       Sun, 14 Nov 2010 19:05:55 -0800 (PST)
+X-Barracuda-Envelope-From: joe@perches.com
+X-Barracuda-Apparent-Source-IP: 173.55.12.10
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 044672436E;
+       Sun, 14 Nov 2010 19:04:23 -0800 (PST)
+X-ASG-Orig-Subj: [PATCH 31/44] drivers/xen: Remove unnecessary semicolons
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-Barracuda-Connect: mail.perches.com[173.55.12.10]
+X-Barracuda-Start-Time: 1289790355
+X-Barracuda-URL: http://spam.xensource.com:8000/cgi-mod/mark.cgi
+X-Virus-Scanned: by bsmtpd at xensource.com
+X-Barracuda-Spam-Score: 0.00
+X-Barracuda-Spam-Status: No, SCORE=0.00 using per-user scores of TAG_LEVEL=3.5
+       QUARANTINE_LEVEL=6.0 KILL_LEVEL=1000.0 tests=
+X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.2.46657
+       Rule breakdown below
+       pts rule name              description
+       ---- ----------------------
+       --------------------------------------------------
+X-BeenThere: xen-devel@lists.xensource.com
+X-Mailman-Version: 2.1.5
+Precedence: list
+List-Id: Xen developer discussion <xen-devel.lists.xensource.com>
+List-Unsubscribe: <http://lists.xensource.com/mailman/listinfo/xen-devel>,
+       <mailto:xen-devel-request@lists.xensource.com?subject=unsubscribe>
+List-Post: <mailto:xen-devel@lists.xensource.com>
+List-Help: <mailto:xen-devel-request@lists.xensource.com?subject=help>
+List-Subscribe: <http://lists.xensource.com/mailman/listinfo/xen-devel>,
+       <mailto:xen-devel-request@lists.xensource.com?subject=subscribe>
+Sender: xen-devel-bounces@lists.xensource.com
+Errors-To: xen-devel-bounces@lists.xensource.com
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062277>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/xen/swiotlb-xen.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/xen/swiotlb-xen.c b/drivers/xen/swiotlb-xen.c
+index 54469c3..65ea21a 100644
+--- a/drivers/xen/swiotlb-xen.c
++++ b/drivers/xen/swiotlb-xen.c
+@@ -54,7 +54,7 @@ u64 start_dma_addr;
+ static dma_addr_t xen_phys_to_bus(phys_addr_t paddr)
+ {
+-      return phys_to_machine(XPADDR(paddr)).maddr;;
++      return phys_to_machine(XPADDR(paddr)).maddr;
+ }
+ static phys_addr_t xen_bus_to_phys(dma_addr_t baddr)
+-- 
+1.7.3.1.g432b3.dirty
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002259:2, b/test/corpora/lkml/cur/1382298793.002259:2,
new file mode 100644 (file)
index 0000000..de8e427
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 11/44] drivers/mmc: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:30 -0800
+Lines: 21
+Message-ID: <6391af02ba7ec4a76c5c5f462d8013fc1f52f999.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Chris Ball <cjb@laptop.org>, linux-mmc@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:20 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOe-0000ny-CV
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:20 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932688Ab0KODFg (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:36 -0500
+Received: from mail.perches.com ([173.55.12.10]:1153 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932652Ab0KODFe (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:34 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id DD07924374;
+       Sun, 14 Nov 2010 19:04:01 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062278>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/mmc/host/davinci_mmc.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c
+index e15547c..b643dde 100644
+--- a/drivers/mmc/host/davinci_mmc.c
++++ b/drivers/mmc/host/davinci_mmc.c
+@@ -480,7 +480,7 @@ static void mmc_davinci_send_dma_request(struct mmc_davinci_host *host,
+       struct scatterlist      *sg;
+       unsigned                sg_len;
+       unsigned                bytes_left = host->bytes_left;
+-      const unsigned          shift = ffs(rw_threshold) - 1;;
++      const unsigned          shift = ffs(rw_threshold) - 1;
+       if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) {
+               template = &host->tx_template;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002260:2, b/test/corpora/lkml/cur/1382298793.002260:2,
new file mode 100644 (file)
index 0000000..2b5145f
--- /dev/null
@@ -0,0 +1,57 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 20/44] drivers/power: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:39 -0800
+Lines: 21
+Message-ID: <2f2ed8aa6745be23063fed55243313839d7ba523.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOe-0000ny-TB
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:21 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932773Ab0KODFm (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:42 -0500
+Received: from mail.perches.com ([173.55.12.10]:1185 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932746Ab0KODFk (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:40 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 3664A2436B;
+       Sun, 14 Nov 2010 19:04:08 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062279>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/power/intel_mid_battery.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c
+index 2a10cd3..8397978 100644
+--- a/drivers/power/intel_mid_battery.c
++++ b/drivers/power/intel_mid_battery.c
+@@ -522,7 +522,7 @@ static int pmic_battery_set_charger(struct pmic_power_module_info *pbi,
+       if (retval) {
+               dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+                                                               __func__);
+-              return retval;;
++              return retval;
+       }
+       return 0;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002261:2, b/test/corpora/lkml/cur/1382298793.002261:2,
new file mode 100644 (file)
index 0000000..28aac74
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 02/44] arch/microblaze: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:21 -0800
+Lines: 21
+Message-ID: <5d57b90b488b4338bcdc3f0fbf5f6996842bd44d.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Michal Simek <monstr@monstr.eu>, microblaze-uclinux@itee.uq.edu.au,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOd-0000ny-SE
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:20 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932624Ab0KODFc (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:32 -0500
+Received: from mail.perches.com ([173.55.12.10]:1127 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1757330Ab0KODF3 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:29 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id EF7FB2436C;
+       Sun, 14 Nov 2010 19:03:55 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062280>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ arch/microblaze/lib/memmove.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/arch/microblaze/lib/memmove.c b/arch/microblaze/lib/memmove.c
+index 123e361..810fd68 100644
+--- a/arch/microblaze/lib/memmove.c
++++ b/arch/microblaze/lib/memmove.c
+@@ -182,7 +182,7 @@ void *memmove(void *v_dst, const void *v_src, __kernel_size_t c)
+                       for (; c >= 4; c -= 4) {
+                               value = *--i_src;
+                               *--i_dst = buf_hold | ((value & 0xFF000000)>> 24);
+-                              buf_hold = (value & 0xFFFFFF) << 8;;
++                              buf_hold = (value & 0xFFFFFF) << 8;
+                       }
+ #endif
+                       /* Realign the source */
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002262:2, b/test/corpora/lkml/cur/1382298793.002262:2,
new file mode 100644 (file)
index 0000000..b931922
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 25/44] drivers/scsi/pm8001: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:44 -0800
+Lines: 21
+Message-ID: <20b352f91642ca45ad730d8eeec0bbd323d26626.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: jack_wang@usish.com, lindar_liu@usish.com,
+       "James E.J. Bottomley" <James.Bottomley@suse.de>,
+       linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOf-0000ny-V3
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:22 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932817Ab0KODFp (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:45 -0500
+Received: from mail.perches.com ([173.55.12.10]:1198 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932690Ab0KODFn (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:43 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 22C0624374;
+       Sun, 14 Nov 2010 19:04:11 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062281>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/scsi/pm8001/pm8001_init.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c
+index f8c86b2..be210dd 100644
+--- a/drivers/scsi/pm8001/pm8001_init.c
++++ b/drivers/scsi/pm8001/pm8001_init.c
+@@ -160,7 +160,7 @@ static void pm8001_free(struct pm8001_hba_info *pm8001_ha)
+ static void pm8001_tasklet(unsigned long opaque)
+ {
+       struct pm8001_hba_info *pm8001_ha;
+-      pm8001_ha = (struct pm8001_hba_info *)opaque;;
++      pm8001_ha = (struct pm8001_hba_info *)opaque;
+       if (unlikely(!pm8001_ha))
+               BUG_ON(1);
+       PM8001_CHIP_DISP->isr(pm8001_ha);
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002263:2, b/test/corpora/lkml/cur/1382298793.002263:2,
new file mode 100644 (file)
index 0000000..4014b08
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 43/44] sound/core/pcm_lib.c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:05:02 -0800
+Lines: 21
+Message-ID: <9fa8e193ce125ef4fd19a952792629c5ee84953f.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:24 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOg-0000ny-Vl
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:23 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933024Ab0KODGF (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:06:05 -0500
+Received: from mail.perches.com ([173.55.12.10]:1272 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932998Ab0KODGD (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:06:03 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id E309C24378;
+       Sun, 14 Nov 2010 19:04:30 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062282>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ sound/core/pcm_lib.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
+index a1707cc..b75db8e 100644
+--- a/sound/core/pcm_lib.c
++++ b/sound/core/pcm_lib.c
+@@ -223,7 +223,7 @@ static void xrun_log(struct snd_pcm_substream *substream,
+       entry->jiffies = jiffies;
+       entry->pos = pos;
+       entry->period_size = runtime->period_size;
+-      entry->buffer_size = runtime->buffer_size;;
++      entry->buffer_size = runtime->buffer_size;
+       entry->old_hw_ptr = runtime->status->hw_ptr;
+       entry->hw_ptr_base = runtime->hw_ptr_base;
+       log->idx = (log->idx + 1) % XRUN_LOG_CNT;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002264:2, b/test/corpora/lkml/cur/1382298793.002264:2,
new file mode 100644 (file)
index 0000000..17d8127
--- /dev/null
@@ -0,0 +1,103 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:05:03 -0800
+Lines: 62
+Message-ID: <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:24 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOh-0000ny-G0
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:23 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933041Ab0KODGR (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:06:17 -0500
+Received: from mail.perches.com ([173.55.12.10]:1275 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932999Ab0KODGE (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:06:04 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 2AAD92436C;
+       Sun, 14 Nov 2010 19:04:32 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062283>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ sound/soc/codecs/wm8904.c  |    2 +-
+ sound/soc/codecs/wm8940.c  |    1 -
+ sound/soc/codecs/wm8993.c  |    2 +-
+ sound/soc/codecs/wm_hubs.c |    2 +-
+ 4 files changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
+index 33be84e..99ae66d 100644
+--- a/sound/soc/codecs/wm8904.c
++++ b/sound/soc/codecs/wm8904.c
+@@ -1590,7 +1590,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
+                      - wm8904->fs);
+       for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+               cur_val = abs((wm8904->sysclk_rate /
+-                             clk_sys_rates[i].ratio) - wm8904->fs);;
++                             clk_sys_rates[i].ratio) - wm8904->fs);
+               if (cur_val < best_val) {
+                       best = i;
+                       best_val = cur_val;
+diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
+index 2cb16f8..e3f3572 100644
+--- a/sound/soc/codecs/wm8940.c
++++ b/sound/soc/codecs/wm8940.c
+@@ -735,7 +735,6 @@ static int wm8940_probe(struct snd_soc_codec *codec)
+               return ret;
+       return ret;
+-;
+ }
+ static int wm8940_remove(struct snd_soc_codec *codec)
+diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
+index 589e3fa..74af9c5 100644
+--- a/sound/soc/codecs/wm8993.c
++++ b/sound/soc/codecs/wm8993.c
+@@ -1225,7 +1225,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream,
+                      - wm8993->fs);
+       for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+               cur_val = abs((wm8993->sysclk_rate /
+-                             clk_sys_rates[i].ratio) - wm8993->fs);;
++                             clk_sys_rates[i].ratio) - wm8993->fs);
+               if (cur_val < best_val) {
+                       best = i;
+                       best_val = cur_val;
+diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
+index 19ca782..2afbc7a 100644
+--- a/sound/soc/codecs/wm_hubs.c
++++ b/sound/soc/codecs/wm_hubs.c
+@@ -112,7 +112,7 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
+               switch (hubs->dcs_readback_mode) {
+               case 0:
+                       reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
+-                              & WM8993_DCS_INTEG_CHAN_0_MASK;;
++                              & WM8993_DCS_INTEG_CHAN_0_MASK;
+                       reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
+                               & WM8993_DCS_INTEG_CHAN_1_MASK;
+                       break;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002265:2, b/test/corpora/lkml/cur/1382298793.002265:2,
new file mode 100644 (file)
index 0000000..c4c55c7
--- /dev/null
@@ -0,0 +1,76 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 19/44] drivers/platform/x86: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:38 -0800
+Lines: 35
+Message-ID: <eda82bcfaad265fc5cd3901bc4f41bfcfac2403b.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>,
+       Daniel Oliveira Nascimento <don@syst.com.br>,
+       Matthew Garrett <mjg@redhat.com>,
+       Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>,
+       platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org,
+       ibm-acpi-devel@lists.sourceforge.net
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOf-0000ny-DL
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:21 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932793Ab0KODFn (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:43 -0500
+Received: from mail.perches.com ([173.55.12.10]:1183 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932690Ab0KODFk (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:40 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 96EF224372;
+       Sun, 14 Nov 2010 19:04:07 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062284>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/platform/x86/classmate-laptop.c |    2 +-
+ drivers/platform/x86/thinkpad_acpi.c    |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
+index 341cbfe..d2b7720 100644
+--- a/drivers/platform/x86/classmate-laptop.c
++++ b/drivers/platform/x86/classmate-laptop.c
+@@ -653,7 +653,7 @@ static void cmpc_keys_handler(struct acpi_device *dev, u32 event)
+       if ((event & 0x0F) < ARRAY_SIZE(cmpc_keys_codes))
+               code = cmpc_keys_codes[event & 0x0F];
+-      inputdev = dev_get_drvdata(&dev->dev);;
++      inputdev = dev_get_drvdata(&dev->dev);
+       input_report_key(inputdev, code, !(event & 0x10));
+ }
+diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
+index 2d61186..3cd7814 100644
+--- a/drivers/platform/x86/thinkpad_acpi.c
++++ b/drivers/platform/x86/thinkpad_acpi.c
+@@ -6345,7 +6345,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
+                       "as change notification\n");
+       tpacpi_hotkey_driver_mask_set(hotkey_driver_mask
+                               | TP_ACPI_HKEY_BRGHTUP_MASK
+-                              | TP_ACPI_HKEY_BRGHTDWN_MASK);;
++                              | TP_ACPI_HKEY_BRGHTDWN_MASK);
+       return 0;
+ }
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002266:2, b/test/corpora/lkml/cur/1382298793.002266:2,
new file mode 100644 (file)
index 0000000..cc36531
--- /dev/null
@@ -0,0 +1,75 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 28/44] drivers/spi: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:47 -0800
+Lines: 35
+Message-ID: <fe5e5e0efbd97eaa32530eef5ed47efdc3252dad.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: David Brownell <dbrownell@users.sourceforge.net>,
+       Grant Likely <grant.likely@secretlab.ca>,
+       Wan ZongShun <mcuos.com@gmail.com>,
+       spi-devel-general@lists.sourceforge.net,
+       linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:06:22 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpOg-0000ny-FJ
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:06:22 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932836Ab0KODFr (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:47 -0500
+Received: from mail.perches.com ([173.55.12.10]:1208 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932690Ab0KODFq (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:46 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 454B624375;
+       Sun, 14 Nov 2010 19:04:13 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062285>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/spi/amba-pl022.c |    2 +-
+ drivers/spi/spi_nuc900.c |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c
+index fb3d1b3..2e50631 100644
+--- a/drivers/spi/amba-pl022.c
++++ b/drivers/spi/amba-pl022.c
+@@ -956,7 +956,7 @@ static int configure_dma(struct pl022 *pl022)
+               tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+               break;
+       case WRITING_U32:
+-              tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;;
++              tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+               break;
+       }
+diff --git a/drivers/spi/spi_nuc900.c b/drivers/spi/spi_nuc900.c
+index dff63be..d5be18b 100644
+--- a/drivers/spi/spi_nuc900.c
++++ b/drivers/spi/spi_nuc900.c
+@@ -449,7 +449,7 @@ err_iomap:
+       release_mem_region(hw->res->start, resource_size(hw->res));
+       kfree(hw->ioarea);
+ err_pdata:
+-      spi_master_put(hw->master);;
++      spi_master_put(hw->master);
+ err_nomem:
+       return err;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002267:2, b/test/corpora/lkml/cur/1382298793.002267:2,
new file mode 100644 (file)
index 0000000..d3acae5
--- /dev/null
@@ -0,0 +1,773 @@
+From: Joe Perches <joe@perches.com>
+Subject: =?UTF-8?q?=5BPATCH=2029/44=5D=20drivers/staging=3A=20Remove=20unnecessary=20semicolons?=
+Date: Sun, 14 Nov 2010 19:04:48 -0800
+Lines: 724
+Message-ID: <3246dc176a2c553078e73332f02d802dd8ef7942.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: multipart/mixed; boundary="===============1088501263=="
+Cc: devel@driverdev.osuosl.org, Greg Kroah-Hartman <gregkh@suse.de>,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: devel-bounces@linuxdriverproject.org Mon Nov 15 04:06:51 2010
+Return-path: <devel-bounces@linuxdriverproject.org>
+Envelope-to: glddd-devel@m.gmane.org
+Received: from driverdev.linuxdriverproject.org ([140.211.166.17])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <devel-bounces@linuxdriverproject.org>)
+       id 1PHpP7-000137-TU
+       for glddd-devel@m.gmane.org; Mon, 15 Nov 2010 04:06:50 +0100
+Received: from driverdev.linuxdriverproject.org (localhost [127.0.0.1])
+       by driverdev.linuxdriverproject.org (Postfix) with ESMTP id 01FCE460C4;
+       Sun, 14 Nov 2010 19:05:00 -0800 (PST)
+X-Original-To: devel@driverdev.osuosl.org
+Delivered-To: devel@driverdev.osuosl.org
+Received: from mail.perches.com (mail.perches.com [173.55.12.10])
+       by driverdev.linuxdriverproject.org (Postfix) with ESMTP id 8F70A460C6
+       for <devel@driverdev.osuosl.org>; Sun, 14 Nov 2010 19:04:05 -0800 (PST)
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id C136B2436B;
+       Sun, 14 Nov 2010 19:04:21 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+X-BeenThere: devel@linuxdriverproject.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: Linux Driver Project Developer List <devel.linuxdriverproject.org>
+List-Unsubscribe: <http://driverdev.linuxdriverproject.org/mailman/options/devel>,
+       <mailto:devel-request@linuxdriverproject.org?subject=unsubscribe>
+List-Archive: <http://driverdev.linuxdriverproject.org/pipermail/devel>
+List-Post: <mailto:devel@linuxdriverproject.org>
+List-Help: <mailto:devel-request@linuxdriverproject.org?subject=help>
+List-Subscribe: <http://driverdev.linuxdriverproject.org/mailman/listinfo/devel>,
+       <mailto:devel-request@linuxdriverproject.org?subject=subscribe>
+Sender: devel-bounces@linuxdriverproject.org
+Errors-To: devel-bounces@linuxdriverproject.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062286>
+
+--===============1088501263==
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ .../staging/ath6kl/hif/sdio/linux_sdio/src/hif.c   |    2 +-
+ drivers/staging/ath6kl/os/linux/ar6000_drv.c       |    2 +-
+ drivers/staging/bcm/InterfaceInit.c                |    2 +-
+ drivers/staging/bcm/InterfaceIsr.c                 |    2 +-
+ drivers/staging/bcm/Misc.c                         |    4 ++--
+ .../comedi/drivers/addi-data/APCI1710_Tor.c        |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci1500.c      |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci1516.c      |    2 +-
+ .../comedi/drivers/addi-data/hwdrv_apci3501.c      |    2 +-
+ drivers/staging/comedi/drivers/amplc_pci230.c      |    2 +-
+ drivers/staging/comedi/drivers/cb_das16_cs.c       |    2 +-
+ drivers/staging/comedi/drivers/comedi_bond.c       |    2 +-
+ drivers/staging/crystalhd/crystalhd_hw.c           |    2 +-
+ drivers/staging/go7007/go7007-driver.c             |    2 +-
+ drivers/staging/iio/accel/lis3l02dq_ring.c         |    2 +-
+ .../staging/intel_sst/intel_sst_drv_interface.c    |    4 ++--
+ drivers/staging/keucr/smilmain.c                   |    4 ++--
+ drivers/staging/keucr/smilsub.c                    |    4 ++--
+ drivers/staging/msm/lcdc_toshiba_wvga_pt.c         |    2 +-
+ drivers/staging/rt2860/common/cmm_data_pci.c       |    4 ++--
+ drivers/staging/rt2860/rt_linux.c                  |    2 +-
+ drivers/staging/rt2860/rtmp.h                      |    2 +-
+ drivers/staging/rtl8192e/ieee80211/ieee80211_tx.c  |    2 +-
+ drivers/staging/rtl8192e/r819xE_phy.c              |    2 +-
+ drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c  |    2 +-
+ drivers/staging/rtl8192u/r8192U_core.c             |    2 +-
+ drivers/staging/rtl8192u/r819xU_phy.c              |    2 +-
+ drivers/staging/rtl8712/rtl8712_efuse.c            |    2 +-
+ drivers/staging/rtl8712/rtl8712_xmit.c             |    2 +-
+ drivers/staging/rtl8712/rtl871x_xmit.c             |    2 +-
+ drivers/staging/tidspbridge/core/tiomap3430.c      |    4 ++--
+ drivers/staging/tidspbridge/rmgr/nldr.c            |    2 +-
+ drivers/staging/vt6655/card.c                      |    2 +-
+ drivers/staging/vt6655/iwctl.c                     |    2 +-
+ drivers/staging/vt6655/wpa2.c                      |    4 ++--
+ drivers/staging/vt6656/baseband.c                  |    2 +-
+ drivers/staging/vt6656/iwctl.c                     |    2 +-
+ drivers/staging/vt6656/power.c                     |    2 +-
+ drivers/staging/vt6656/wpa2.c                      |    4 ++--
+ 39 files changed, 47 insertions(+), 47 deletions(-)
+
+diff --git a/drivers/staging/ath6kl/hif/sdio/linux_sdio/src/hif.c b/drive=
+rs/staging/ath6kl/hif/sdio/linux_sdio/src/hif.c
+index c307a55..3963038 100644
+--- a/drivers/staging/ath6kl/hif/sdio/linux_sdio/src/hif.c
++++ b/drivers/staging/ath6kl/hif/sdio/linux_sdio/src/hif.c
+@@ -876,7 +876,7 @@ HIFAckInterrupt(HIF_DEVICE *device)
+ void
+ HIFUnMaskInterrupt(HIF_DEVICE *device)
+ {
+-    int ret;;
++    int ret;
+=20
+     AR_DEBUG_ASSERT(device !=3D NULL);
+     AR_DEBUG_ASSERT(device->func !=3D NULL);
+diff --git a/drivers/staging/ath6kl/os/linux/ar6000_drv.c b/drivers/stagi=
+ng/ath6kl/os/linux/ar6000_drv.c
+index a659f70..126a36a 100644
+--- a/drivers/staging/ath6kl/os/linux/ar6000_drv.c
++++ b/drivers/staging/ath6kl/os/linux/ar6000_drv.c
+@@ -4439,7 +4439,7 @@ skip_key:
+         for (i =3D assoc_req_ie_pos; i < assoc_req_ie_pos + assocReqLen =
+- 4; i++) {
+             AR_DEBUG_PRINTF(ATH_DEBUG_WLAN_CONNECT,("%2.2x ", assocInfo[=
+i]));
+             sprintf(pos, "%2.2x", assocInfo[i]);
+-            pos +=3D 2;;
++            pos +=3D 2;
+         }
+         AR_DEBUG_PRINTF(ATH_DEBUG_WLAN_CONNECT,("\n"));
+=20
+diff --git a/drivers/staging/bcm/InterfaceInit.c b/drivers/staging/bcm/In=
+terfaceInit.c
+index 824f9a4..a368011 100644
+--- a/drivers/staging/bcm/InterfaceInit.c
++++ b/drivers/staging/bcm/InterfaceInit.c
+@@ -265,7 +265,7 @@ usbbcm_device_probe(struct usb_interface *intf, const=
+ struct usb_device_id *id)
+               uint32_t uiNackZeroLengthInt=3D4;
+               if(wrmalt(psAdapter, DISABLE_USB_ZERO_LEN_INT, &uiNackZeroLengthInt, s=
+izeof(uiNackZeroLengthInt)))
+               {
+-                      return -EIO;;
++                      return -EIO;
+               }
+       }
+=20
+diff --git a/drivers/staging/bcm/InterfaceIsr.c b/drivers/staging/bcm/Int=
+erfaceIsr.c
+index f928fe4..604d07f 100644
+--- a/drivers/staging/bcm/InterfaceIsr.c
++++ b/drivers/staging/bcm/InterfaceIsr.c
+@@ -87,7 +87,7 @@ static void read_int_callback(struct urb *urb/*, struct=
+ pt_regs *regs*/)
+                               BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Int=
+errupt IN endPoint  has got halted/stalled...need to clear this");
+                               Adapter->bEndPointHalted =3D TRUE ;
+                               wake_up(&Adapter->tx_packet_wait_queue);
+-                              urb->status =3D STATUS_SUCCESS ;;
++                              urb->status =3D STATUS_SUCCESS ;
+                               return;
+               }
+           /* software-driven interface shutdown */
+diff --git a/drivers/staging/bcm/Misc.c b/drivers/staging/bcm/Misc.c
+index 22550f7..cd14fec 100644
+--- a/drivers/staging/bcm/Misc.c
++++ b/drivers/staging/bcm/Misc.c
+@@ -764,7 +764,7 @@ void SendIdleModeResponse(PMINI_ADAPTER Adapter)
+=20
+                       /* Wake the LED Thread with IDLEMODE_ENTER State */
+                       Adapter->DriverState =3D LOWPOWER_MODE_ENTER;
+-                      BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL,"LED Thread =
+is Running..Hence Setting LED Event as IDLEMODE_ENTER jiffies:%ld",jiffie=
+s);;
++                      BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL,"LED Thread =
+is Running..Hence Setting LED Event as IDLEMODE_ENTER jiffies:%ld",jiffie=
+s);
+                       wake_up(&Adapter->LEDInfo.notify_led_event);
+=20
+                       /* Wait for 1 SEC for LED to OFF */
+@@ -1410,7 +1410,7 @@ int bcm_parse_target_params(PMINI_ADAPTER Adapter)
+=20
+ void beceem_parse_target_struct(PMINI_ADAPTER Adapter)
+ {
+-      UINT uiHostDrvrCfg6 =3D0, uiEEPROMFlag =3D 0;;
++      UINT uiHostDrvrCfg6 =3D0, uiEEPROMFlag =3D 0;
+=20
+       if(ntohl(Adapter->pstargetparams->m_u32PhyParameter2) & AUTO_SYNC_DISAB=
+LE)
+       {
+diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c b/dr=
+ivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c
+index 7361d50..0e6affd 100644
+--- a/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c
++++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c
+@@ -1008,7 +1008,7 @@ int i_APCI1710_InsnWriteEnableDisableTorCounter(str=
+uct comedi_device *dev,
+       b_ExternGate =3D (unsigned char) data[3];
+       b_CycleMode =3D (unsigned char) data[4];
+       b_InterruptEnable =3D (unsigned char) data[5];
+-      i_ReturnValue =3D insn->n;;
++      i_ReturnValue =3D insn->n;
+       devpriv->tsk_Current =3D current;       /*  Save the current process task str=
+ucture */
+       /**************************/
+       /* Test the module number */
+diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c b/=
+drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c
+index 2a8a6c7..62f421a 100644
+--- a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c
++++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c
+@@ -2850,7 +2850,7 @@ static int i_APCI1500_Reset(struct comedi_device *d=
+ev)
+       i_Logic =3D 0;
+       i_CounterLogic =3D 0;
+       i_InterruptMask =3D 0;
+-      i_InputChannel =3D 0;;
++      i_InputChannel =3D 0;
+       i_TimerCounter1Enabled =3D 0;
+       i_TimerCounter2Enabled =3D 0;
+       i_WatchdogCounter3Enabled =3D 0;
+diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c b/=
+drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c
+index 12fcc35..8a584a0 100644
+--- a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c
++++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c
+@@ -335,7 +335,7 @@ int i_APCI1516_WriteDigitalOutput(struct comedi_devic=
+e *dev, struct comedi_subde
+                       return -EINVAL;
+               }               /* if else data[3]=3D=3D1) */
+       }                       /* if else data[3]=3D=3D0) */
+-      return (insn->n);;
++      return (insn->n);
+ }
+=20
+ /*
+diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c b/=
+drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
+index 356a189..acaceb0 100644
+--- a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
++++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
+@@ -339,7 +339,7 @@ int i_APCI3501_ConfigAnalogOutput(struct comedi_devic=
+e *dev, struct comedi_subde
+ int i_APCI3501_WriteAnalogOutput(struct comedi_device *dev, struct comed=
+i_subdevice *s,
+       struct comedi_insn *insn, unsigned int *data)
+ {
+-      unsigned int ul_Command1 =3D 0, ul_Channel_no, ul_Polarity, ul_DAC_Read=
+y =3D 0;;
++      unsigned int ul_Command1 =3D 0, ul_Channel_no, ul_Polarity, ul_DAC_Read=
+y =3D 0;
+=20
+       ul_Channel_no =3D CR_CHAN(insn->chanspec);
+=20
+diff --git a/drivers/staging/comedi/drivers/amplc_pci230.c b/drivers/stag=
+ing/comedi/drivers/amplc_pci230.c
+index 5d06457..7edeb11 100644
+--- a/drivers/staging/comedi/drivers/amplc_pci230.c
++++ b/drivers/staging/comedi/drivers/amplc_pci230.c
+@@ -971,7 +971,7 @@ static int pci230_attach(struct comedi_device *dev, s=
+truct comedi_devconfig *it)
+       if (thisboard->ao_chans > 0) {
+               s->type =3D COMEDI_SUBD_AO;
+               s->subdev_flags =3D SDF_WRITABLE | SDF_GROUND;
+-              s->n_chan =3D thisboard->ao_chans;;
++              s->n_chan =3D thisboard->ao_chans;
+               s->maxdata =3D (1 << thisboard->ao_bits) - 1;
+               s->range_table =3D &pci230_ao_range;
+               s->insn_write =3D &pci230_ao_winsn;
+diff --git a/drivers/staging/comedi/drivers/cb_das16_cs.c b/drivers/stagi=
+ng/comedi/drivers/cb_das16_cs.c
+index 0345b4c..bb93685 100644
+--- a/drivers/staging/comedi/drivers/cb_das16_cs.c
++++ b/drivers/staging/comedi/drivers/cb_das16_cs.c
+@@ -169,7 +169,7 @@ static int das16cs_attach(struct comedi_device *dev,
+       if (!link)
+               return -EIO;
+=20
+-      dev->iobase =3D link->resource[0]->start;;
++      dev->iobase =3D link->resource[0]->start;
+       printk("I/O base=3D0x%04lx ", dev->iobase);
+=20
+       printk("fingerprint:\n");
+diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/stagi=
+ng/comedi/drivers/comedi_bond.c
+index cfcbd9b..d8aefb2 100644
+--- a/drivers/staging/comedi/drivers/comedi_bond.c
++++ b/drivers/staging/comedi/drivers/comedi_bond.c
+@@ -370,7 +370,7 @@ static int doDevConfig(struct comedi_device *dev, str=
+uct comedi_devconfig *it)
+       struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
+=20
+       memset(devs_opened, 0, sizeof(devs_opened));
+-      devpriv->name[0] =3D 0;;
++      devpriv->name[0] =3D 0;
+       /* Loop through all comedi devices specified on the command-line,
+          building our device list */
+       for (i =3D 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
+diff --git a/drivers/staging/crystalhd/crystalhd_hw.c b/drivers/staging/c=
+rystalhd/crystalhd_hw.c
+index f631857..153ddbf 100644
+--- a/drivers/staging/crystalhd/crystalhd_hw.c
++++ b/drivers/staging/crystalhd/crystalhd_hw.c
+@@ -1711,7 +1711,7 @@ enum BC_STATUS crystalhd_download_fw(struct crystal=
+hd_adp *adp, void *buffer, ui
+       }
+=20
+       BCMLOG(BCMLOG_INFO, "Firmware Downloaded Successfully\n");
+-      return BC_STS_SUCCESS;;
++      return BC_STS_SUCCESS;
+ }
+=20
+ enum BC_STATUS crystalhd_do_fw_cmd(struct crystalhd_hw *hw,
+diff --git a/drivers/staging/go7007/go7007-driver.c b/drivers/staging/go7=
+007/go7007-driver.c
+index b3f42f3..8426a02 100644
+--- a/drivers/staging/go7007/go7007-driver.c
++++ b/drivers/staging/go7007/go7007-driver.c
+@@ -624,7 +624,7 @@ struct go7007 *go7007_alloc(struct go7007_board_info =
+*board, struct device *dev)
+       go->dvd_mode =3D 0;
+       go->interlace_coding =3D 0;
+       for (i =3D 0; i < 4; ++i)
+-              go->modet[i].enable =3D 0;;
++              go->modet[i].enable =3D 0;
+       for (i =3D 0; i < 1624; ++i)
+               go->modet_map[i] =3D 0;
+       go->audio_deliver =3D NULL;
+diff --git a/drivers/staging/iio/accel/lis3l02dq_ring.c b/drivers/staging=
+/iio/accel/lis3l02dq_ring.c
+index 330d5d6..1fd088a 100644
+--- a/drivers/staging/iio/accel/lis3l02dq_ring.c
++++ b/drivers/staging/iio/accel/lis3l02dq_ring.c
+@@ -517,7 +517,7 @@ int lis3l02dq_configure_ring(struct iio_dev *indio_de=
+v)
+=20
+       ret =3D iio_alloc_pollfunc(indio_dev, NULL, &lis3l02dq_poll_func_th);
+       if (ret)
+-              goto error_iio_sw_rb_free;;
++              goto error_iio_sw_rb_free;
+       indio_dev->modes |=3D INDIO_RING_TRIGGERED;
+       return 0;
+=20
+diff --git a/drivers/staging/intel_sst/intel_sst_drv_interface.c b/driver=
+s/staging/intel_sst/intel_sst_drv_interface.c
+index 669e298..6443fbd 100644
+--- a/drivers/staging/intel_sst/intel_sst_drv_interface.c
++++ b/drivers/staging/intel_sst/intel_sst_drv_interface.c
+@@ -171,9 +171,9 @@ static int sst_get_sfreq(struct snd_sst_params *str_p=
+aram)
+       case SST_CODEC_TYPE_MP3:
+               return str_param->sparams.uc.mp3_params.sfreq;
+       case SST_CODEC_TYPE_AAC:
+-              return str_param->sparams.uc.aac_params.sfreq;;
++              return str_param->sparams.uc.aac_params.sfreq;
+       case SST_CODEC_TYPE_WMA9:
+-              return str_param->sparams.uc.wma_params.sfreq;;
++              return str_param->sparams.uc.wma_params.sfreq;
+       default:
+               return 0;
+       }
+diff --git a/drivers/staging/keucr/smilmain.c b/drivers/staging/keucr/smi=
+lmain.c
+index bdfbf76..2cbe9f8 100644
+--- a/drivers/staging/keucr/smilmain.c
++++ b/drivers/staging/keucr/smilmain.c
+@@ -153,9 +153,9 @@ int Media_D_ReadSector(struct us_data *us, DWORD star=
+t,WORD count,BYTE *buf)
+       WORD len, bn;
+=20
+       //if (Check_D_MediaPower())        ; =A6b 6250 don't care
+-      //    return(ErrCode);             ;
++      //    return(ErrCode);
+       //if (Check_D_MediaFmt(fdoExt))    ;
+-      //    return(ErrCode);             ;
++      //    return(ErrCode);
+       if (Conv_D_MediaAddr(us, start))
+               return(ErrCode);
+=20
+diff --git a/drivers/staging/keucr/smilsub.c b/drivers/staging/keucr/smil=
+sub.c
+index 1b52535..ce10cf2 100644
+--- a/drivers/staging/keucr/smilsub.c
++++ b/drivers/staging/keucr/smilsub.c
+@@ -763,8 +763,8 @@ int Ssfdc_D_WriteSectForCopy(struct us_data *us, BYTE=
+ *buf, BYTE *redundant)
+       bcb->CDB[7]                     =3D (BYTE)addr;
+       bcb->CDB[6]                     =3D (BYTE)(addr/0x0100);
+       bcb->CDB[5]                     =3D Media.Zone/2;
+-      bcb->CDB[8]                     =3D *(redundant+REDT_ADDR1H);;
+-      bcb->CDB[9]                     =3D *(redundant+REDT_ADDR1L);;
++      bcb->CDB[8]                     =3D *(redundant+REDT_ADDR1H);
++      bcb->CDB[9]                     =3D *(redundant+REDT_ADDR1L);
+=20
+       result =3D ENE_SendScsiCmd(us, FDIR_WRITE, buf, 0);
+       if (result !=3D USB_STOR_XFER_GOOD)
+diff --git a/drivers/staging/msm/lcdc_toshiba_wvga_pt.c b/drivers/staging=
+/msm/lcdc_toshiba_wvga_pt.c
+index 864d7c1..edba78a 100644
+--- a/drivers/staging/msm/lcdc_toshiba_wvga_pt.c
++++ b/drivers/staging/msm/lcdc_toshiba_wvga_pt.c
+@@ -77,7 +77,7 @@ static void toshiba_spi_write(char cmd, uint32 data, in=
+t num)
+=20
+       /* followed by parameter bytes */
+       if (num) {
+-              bp =3D (char *)&data;;
++              bp =3D (char *)&data;
+               bp +=3D (num - 1);
+               while (num) {
+                       toshiba_spi_write_byte(1, *bp);
+diff --git a/drivers/staging/rt2860/common/cmm_data_pci.c b/drivers/stagi=
+ng/rt2860/common/cmm_data_pci.c
+index 43d73a0..7af59ff 100644
+--- a/drivers/staging/rt2860/common/cmm_data_pci.c
++++ b/drivers/staging/rt2860/common/cmm_data_pci.c
+@@ -137,7 +137,7 @@ u16 RtmpPCI_WriteSingleTxResource(struct rt_rtmp_adap=
+ter *pAd,
+=20
+       pTxD->SDPtr0 =3D BufBasePaLow;
+       pTxD->SDLen0 =3D TXINFO_SIZE + TXWI_SIZE + hwHeaderLen; /* include padd=
+ing */
+-      pTxD->SDPtr1 =3D PCI_MAP_SINGLE(pAd, pTxBlk, 0, 1, PCI_DMA_TODEVICE);;
++      pTxD->SDPtr1 =3D PCI_MAP_SINGLE(pAd, pTxBlk, 0, 1, PCI_DMA_TODEVICE);
+       pTxD->SDLen1 =3D pTxBlk->SrcBufLen;
+       pTxD->LastSec0 =3D 0;
+       pTxD->LastSec1 =3D (bIsLast) ? 1 : 0;
+@@ -215,7 +215,7 @@ u16 RtmpPCI_WriteMultiTxResource(struct rt_rtmp_adapt=
+er *pAd,
+=20
+       pTxD->SDPtr0 =3D BufBasePaLow;
+       pTxD->SDLen0 =3D firstDMALen;   /* include padding */
+-      pTxD->SDPtr1 =3D PCI_MAP_SINGLE(pAd, pTxBlk, 0, 1, PCI_DMA_TODEVICE);;
++      pTxD->SDPtr1 =3D PCI_MAP_SINGLE(pAd, pTxBlk, 0, 1, PCI_DMA_TODEVICE);
+       pTxD->SDLen1 =3D pTxBlk->SrcBufLen;
+       pTxD->LastSec0 =3D 0;
+       pTxD->LastSec1 =3D (bIsLast) ? 1 : 0;
+diff --git a/drivers/staging/rt2860/rt_linux.c b/drivers/staging/rt2860/r=
+t_linux.c
+index abfeea1..7dad6ee 100644
+--- a/drivers/staging/rt2860/rt_linux.c
++++ b/drivers/staging/rt2860/rt_linux.c
+@@ -854,7 +854,7 @@ void send_monitor_packets(struct rt_rtmp_adapter *pAd=
+, struct rt_rx_blk *pRxBlk)
+                                                                        RSSI1,
+                                                                        RSSI_1),
+                                   ConvertToRssi(pAd, pRxBlk->pRxWI->RSSI2,
+-                                                RSSI_2));;
++                                                RSSI_2));
+=20
+       ph->signal.did =3D DIDmsg_lnxind_wlansniffrm_signal;
+       ph->signal.status =3D 0;
+diff --git a/drivers/staging/rt2860/rtmp.h b/drivers/staging/rt2860/rtmp.=
+h
+index ca54e53..26cc823 100644
+--- a/drivers/staging/rt2860/rtmp.h
++++ b/drivers/staging/rt2860/rtmp.h
+@@ -2978,7 +2978,7 @@ void LinkDown(struct rt_rtmp_adapter *pAd, IN BOOLE=
+AN IsReqFromAP);
+=20
+ void IterateOnBssTab(struct rt_rtmp_adapter *pAd);
+=20
+-void IterateOnBssTab2(struct rt_rtmp_adapter *pAd);;
++void IterateOnBssTab2(struct rt_rtmp_adapter *pAd);
+=20
+ void JoinParmFill(struct rt_rtmp_adapter *pAd,
+                 struct rt_mlme_join_req *JoinReq, unsigned long BssIdx);
+diff --git a/drivers/staging/rtl8192e/ieee80211/ieee80211_tx.c b/drivers/=
+staging/rtl8192e/ieee80211/ieee80211_tx.c
+index dd8a221..b26b5a8 100644
+--- a/drivers/staging/rtl8192e/ieee80211/ieee80211_tx.c
++++ b/drivers/staging/rtl8192e/ieee80211/ieee80211_tx.c
+@@ -822,7 +822,7 @@ int ieee80211_rtl_xmit(struct sk_buff *skb, struct ne=
+t_device *dev)
+               {
+                       txb->queue_index =3D UP2AC(skb->priority);
+               } else {
+-                      txb->queue_index =3D WME_AC_BK;;
++                      txb->queue_index =3D WME_AC_BK;
+               }
+=20
+=20
+diff --git a/drivers/staging/rtl8192e/r819xE_phy.c b/drivers/staging/rtl8=
+192e/r819xE_phy.c
+index d83bcbc..50cd0e5 100644
+--- a/drivers/staging/rtl8192e/r819xE_phy.c
++++ b/drivers/staging/rtl8192e/r819xE_phy.c
+@@ -2596,7 +2596,7 @@ u8 rtl8192_phy_ConfigRFWithHeaderFile(struct net_de=
+vice* dev, RF90_RADIO_PATH_E
+                       break;
+       }
+=20
+-      return ret;;
++      return ret;
+=20
+ }
+ /***********************************************************************=
+*******
+diff --git a/drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c b/drivers/=
+staging/rtl8192u/ieee80211/ieee80211_tx.c
+index 81aa2ed..ec7845e 100644
+--- a/drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c
++++ b/drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c
+@@ -754,7 +754,7 @@ int ieee80211_xmit(struct sk_buff *skb, struct net_de=
+vice *dev)
+               {
+                       txb->queue_index =3D UP2AC(skb->priority);
+               } else {
+-                      txb->queue_index =3D WME_AC_BK;;
++                      txb->queue_index =3D WME_AC_BK;
+               }
+=20
+=20
+diff --git a/drivers/staging/rtl8192u/r8192U_core.c b/drivers/staging/rtl=
+8192u/r8192U_core.c
+index 494f180..1139a27 100644
+--- a/drivers/staging/rtl8192u/r8192U_core.c
++++ b/drivers/staging/rtl8192u/r8192U_core.c
+@@ -5085,7 +5085,7 @@ static void rtl8192_query_rxphystatus(
+                       //Get Rx snr value in DB
+                       tmp_rxsnr =3D   pofdm_buf->rxsnr_X[i];
+                       rx_snrX =3D (char)(tmp_rxsnr);
+-                      //rx_snrX >>=3D 1;;
++                      //rx_snrX >>=3D 1;
+                       rx_snrX /=3D 2;
+                       priv->stats.rxSNRdB[i] =3D (long)rx_snrX;
+=20
+diff --git a/drivers/staging/rtl8192u/r819xU_phy.c b/drivers/staging/rtl8=
+192u/r819xU_phy.c
+index a3adaed..8e10992 100644
+--- a/drivers/staging/rtl8192u/r819xU_phy.c
++++ b/drivers/staging/rtl8192u/r819xU_phy.c
+@@ -1011,7 +1011,7 @@ u8 rtl8192_phy_ConfigRFWithHeaderFile(struct net_de=
+vice* dev, RF90_RADIO_PATH_E
+                       break;
+       }
+=20
+-      return ret;;
++      return ret;
+=20
+ }
+ /***********************************************************************=
+*******
+diff --git a/drivers/staging/rtl8712/rtl8712_efuse.c b/drivers/staging/rt=
+l8712/rtl8712_efuse.c
+index 9730ae1..1dc12b7 100644
+--- a/drivers/staging/rtl8712/rtl8712_efuse.c
++++ b/drivers/staging/rtl8712/rtl8712_efuse.c
+@@ -428,7 +428,7 @@ u8 r8712_efuse_access(struct _adapter *padapter, u8 b=
+Read, u16 start_addr,
+                     u16 cnts, u8 *data)
+ {
+       int i;
+-      u8 res =3D true;;
++      u8 res =3D true;
+=20
+       if (start_addr > EFUSE_MAX_SIZE)
+               return false;
+diff --git a/drivers/staging/rtl8712/rtl8712_xmit.c b/drivers/staging/rtl=
+8712/rtl8712_xmit.c
+index 8edc518..88a1504 100644
+--- a/drivers/staging/rtl8712/rtl8712_xmit.c
++++ b/drivers/staging/rtl8712/rtl8712_xmit.c
+@@ -148,7 +148,7 @@ static u32 get_ff_hwaddr(struct xmit_frame *pxmitfram=
+e)
+               case 0x11:
+               case 0x12:
+               case 0x13:
+-                      addr =3D RTL8712_DMA_H2CCMD;;
++                      addr =3D RTL8712_DMA_H2CCMD;
+                       break;
+               default:
+                       addr =3D RTL8712_DMA_BEQ;/*RTL8712_EP_LO;*/
+diff --git a/drivers/staging/rtl8712/rtl871x_xmit.c b/drivers/staging/rtl=
+8712/rtl871x_xmit.c
+index b8195e3..75f1a6b 100644
+--- a/drivers/staging/rtl8712/rtl871x_xmit.c
++++ b/drivers/staging/rtl8712/rtl871x_xmit.c
+@@ -372,7 +372,7 @@ static sint xmitframe_addmic(struct _adapter *padapte=
+r,
+                                          0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+                                          0x0, 0x0};
+                       datalen =3D pattrib->pktlen - pattrib->hdrlen;
+-                      pframe =3D pxmitframe->buf_addr + TXDESC_OFFSET;;
++                      pframe =3D pxmitframe->buf_addr + TXDESC_OFFSET;
+                       if (bmcst) {
+                               if (!memcmp(psecuritypriv->XGrptxmickey
+                                  [psecuritypriv->XGrpKeyid].skey,
+diff --git a/drivers/staging/tidspbridge/core/tiomap3430.c b/drivers/stag=
+ing/tidspbridge/core/tiomap3430.c
+index 1be081f..a3b0a18 100644
+--- a/drivers/staging/tidspbridge/core/tiomap3430.c
++++ b/drivers/staging/tidspbridge/core/tiomap3430.c
+@@ -596,7 +596,7 @@ static int bridge_brd_start(struct bridge_dev_context=
+ *dev_ctxt,
+               dev_dbg(bridge, "DSP c_int00 Address =3D  0x%x\n", dsp_addr);
+               if (dsp_debug)
+                       while (__raw_readw(dw_sync_addr))
+-                              ;;
++                              ;
+=20
+               /* Wait for DSP to clear word in shared memory */
+               /* Read the Location */
+@@ -1671,7 +1671,7 @@ static int pte_set(struct pg_table_attrs *pt, u32 p=
+a, u32 va,
+                       /* Find a free L2 PT. */
+                       for (i =3D 0; (i < pt->l2_num_pages) &&
+                            (pt->pg_info[i].num_entries !=3D 0); i++)
+-                              ;;
++                              ;
+                       if (i < pt->l2_num_pages) {
+                               l2_page_num =3D i;
+                               l2_base_pa =3D pt->l2_base_pa + (l2_page_num *
+diff --git a/drivers/staging/tidspbridge/rmgr/nldr.c b/drivers/staging/ti=
+dspbridge/rmgr/nldr.c
+index a6ae007..28354bb 100644
+--- a/drivers/staging/tidspbridge/rmgr/nldr.c
++++ b/drivers/staging/tidspbridge/rmgr/nldr.c
+@@ -943,7 +943,7 @@ static int add_ovly_info(void *handle, struct dbll_se=
+ct_info *sect_info,
+=20
+       /* Determine which phase this section belongs to */
+       for (pch =3D sect_name + 1; *pch && *pch !=3D seps; pch++)
+-              ;;
++              ;
+=20
+       if (*pch) {
+               pch++;          /* Skip over the ':' */
+diff --git a/drivers/staging/vt6655/card.c b/drivers/staging/vt6655/card.=
+c
+index 32d095c..951a3a8 100644
+--- a/drivers/staging/vt6655/card.c
++++ b/drivers/staging/vt6655/card.c
+@@ -2058,7 +2058,7 @@ bool CARDbSoftwareReset (void *pDeviceHandler)
+ QWORD CARDqGetTSFOffset (unsigned char byRxRate, QWORD qwTSF1, QWORD qwT=
+SF2)
+ {
+     QWORD   qwTSFOffset;
+-    unsigned short wRxBcnTSFOffst=3D 0;;
++    unsigned short wRxBcnTSFOffst=3D 0;
+=20
+     HIDWORD(qwTSFOffset) =3D 0;
+     LODWORD(qwTSFOffset) =3D 0;
+diff --git a/drivers/staging/vt6655/iwctl.c b/drivers/staging/vt6655/iwct=
+l.c
+index 92e3399..5e425d1 100644
+--- a/drivers/staging/vt6655/iwctl.c
++++ b/drivers/staging/vt6655/iwctl.c
+@@ -2073,7 +2073,7 @@ int iwctl_giwencodeext(struct net_device *dev,
+              struct iw_point *wrq,
+              char *extra)
+ {
+-              return -EOPNOTSUPP;;
++              return -EOPNOTSUPP;
+ }
+=20
+ int iwctl_siwmlme(struct net_device *dev,
+diff --git a/drivers/staging/vt6655/wpa2.c b/drivers/staging/vt6655/wpa2.=
+c
+index 805164b..744799c 100644
+--- a/drivers/staging/vt6655/wpa2.c
++++ b/drivers/staging/vt6655/wpa2.c
+@@ -216,7 +216,7 @@ WPA2vParseRSN (
+         m =3D *((unsigned short *) &(pRSN->abyRSN[4]));
+=20
+         if (pRSN->len >=3D 10+m*4) { // ver(2) + GK(4) + PK count(2) + P=
+KS(4*m) + AKMSS count(2)
+-            pBSSNode->wAKMSSAuthCount =3D *((unsigned short *) &(pRSN->a=
+byRSN[6+4*m]));;
++            pBSSNode->wAKMSSAuthCount =3D *((unsigned short *) &(pRSN->a=
+byRSN[6+4*m]));
+             j =3D 0;
+             pbyOUI =3D &(pRSN->abyRSN[8+4*m]);
+             for (i =3D 0; (i < pBSSNode->wAKMSSAuthCount) && (j < sizeof=
+(pBSSNode->abyAKMSSAuthType)/sizeof(unsigned char)); i++) {
+@@ -235,7 +235,7 @@ WPA2vParseRSN (
+             pBSSNode->wAKMSSAuthCount =3D (unsigned short)j;
+             DBG_PRT(MSG_LEVEL_DEBUG, KERN_INFO"wAKMSSAuthCount: %d\n", p=
+BSSNode->wAKMSSAuthCount);
+=20
+-            n =3D *((unsigned short *) &(pRSN->abyRSN[6+4*m]));;
++            n =3D *((unsigned short *) &(pRSN->abyRSN[6+4*m]));
+             if (pRSN->len >=3D 12+4*m+4*n) { // ver(2)+GK(4)+PKCnt(2)+PK=
+S(4*m)+AKMSSCnt(2)+AKMSS(4*n)+Cap(2)
+                 pBSSNode->sRSNCapObj.bRSNCapExist =3D true;
+                 pBSSNode->sRSNCapObj.wRSNCap =3D *((unsigned short *) &(=
+pRSN->abyRSN[8+4*m+4*n]));
+diff --git a/drivers/staging/vt6656/baseband.c b/drivers/staging/vt6656/b=
+aseband.c
+index e5add20..0d11147 100644
+--- a/drivers/staging/vt6656/baseband.c
++++ b/drivers/staging/vt6656/baseband.c
+@@ -963,7 +963,7 @@ BBvSetAntennaMode (PSDevice pDevice, BYTE byAntennaMo=
+de)
+             break;
+         case ANT_RXB:
+             pDevice->byBBRxConf &=3D 0xFE;
+-            pDevice->byBBRxConf |=3D 0x02;;
++            pDevice->byBBRxConf |=3D 0x02;
+             break;
+     }
+=20
+diff --git a/drivers/staging/vt6656/iwctl.c b/drivers/staging/vt6656/iwct=
+l.c
+index 0004be8..2121205 100644
+--- a/drivers/staging/vt6656/iwctl.c
++++ b/drivers/staging/vt6656/iwctl.c
+@@ -1883,7 +1883,7 @@ int iwctl_giwencodeext(struct net_device *dev,
+              struct iw_point *wrq,
+              char *extra)
+ {
+-              return -EOPNOTSUPP;;
++              return -EOPNOTSUPP;
+ }
+=20
+ int iwctl_siwmlme(struct net_device *dev,
+diff --git a/drivers/staging/vt6656/power.c b/drivers/staging/vt6656/powe=
+r.c
+index 0c12fd3..e8c1b35 100644
+--- a/drivers/staging/vt6656/power.c
++++ b/drivers/staging/vt6656/power.c
+@@ -192,7 +192,7 @@ BOOL PSbConsiderPowerDown(void *hDeviceContext,
+     // check if already in Doze mode
+     ControlvReadByte(pDevice, MESSAGE_REQUEST_MACREG, MAC_REG_PSCTL, &by=
+Data);
+     if ( (byData & PSCTL_PS) !=3D 0 )
+-        return TRUE;;
++        return TRUE;
+=20
+     if (pMgmt->eCurrMode !=3D WMAC_MODE_IBSS_STA) {
+         // check if in TIM wake period
+diff --git a/drivers/staging/vt6656/wpa2.c b/drivers/staging/vt6656/wpa2.=
+c
+index 6d13190..d4f3f75 100644
+--- a/drivers/staging/vt6656/wpa2.c
++++ b/drivers/staging/vt6656/wpa2.c
+@@ -215,7 +215,7 @@ WPA2vParseRSN (
+         m =3D *((PWORD) &(pRSN->abyRSN[4]));
+=20
+         if (pRSN->len >=3D 10+m*4) { // ver(2) + GK(4) + PK count(2) + P=
+KS(4*m) + AKMSS count(2)
+-            pBSSNode->wAKMSSAuthCount =3D *((PWORD) &(pRSN->abyRSN[6+4*m=
+]));;
++            pBSSNode->wAKMSSAuthCount =3D *((PWORD) &(pRSN->abyRSN[6+4*m=
+]));
+             j =3D 0;
+             pbyOUI =3D &(pRSN->abyRSN[8+4*m]);
+             for (i =3D 0; (i < pBSSNode->wAKMSSAuthCount) && (j < sizeof=
+(pBSSNode->abyAKMSSAuthType)/sizeof(BYTE)); i++) {
+@@ -234,7 +234,7 @@ WPA2vParseRSN (
+             pBSSNode->wAKMSSAuthCount =3D (WORD)j;
+             DBG_PRT(MSG_LEVEL_DEBUG, KERN_INFO"wAKMSSAuthCount: %d\n", p=
+BSSNode->wAKMSSAuthCount);
+=20
+-            n =3D *((PWORD) &(pRSN->abyRSN[6+4*m]));;
++            n =3D *((PWORD) &(pRSN->abyRSN[6+4*m]));
+             if (pRSN->len >=3D 12+4*m+4*n) { // ver(2)+GK(4)+PKCnt(2)+PK=
+S(4*m)+AKMSSCnt(2)+AKMSS(4*n)+Cap(2)
+                 pBSSNode->sRSNCapObj.bRSNCapExist =3D TRUE;
+                 pBSSNode->sRSNCapObj.wRSNCap =3D *((PWORD) &(pRSN->abyRS=
+N[8+4*m+4*n]));
+--=20
+1.7.3.1.g432b3.dirty
+
+
+--===============1088501263==
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+devel mailing list
+devel@linuxdriverproject.org
+http://driverdev.linuxdriverproject.org/mailman/listinfo/devel
+
+--===============1088501263==--
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002268:2, b/test/corpora/lkml/cur/1382298793.002268:2,
new file mode 100644 (file)
index 0000000..116de34
--- /dev/null
@@ -0,0 +1,66 @@
+From: Joe Perches <joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+Subject: [PATCH 42/44] net/sunrpc/addr.c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:05:01 -0800
+Lines: 26
+Message-ID: <aca92092a705e0d21176b5ac7d3581c4f9140dbc.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: "J. Bruce Fields" <bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>,
+       Neil Brown <neilb-l3A5Bk7waGM@public.gmane.org>,
+       Trond Myklebust <Trond.Myklebust-HgOvQuBEEgTQT0dZR+AlfA@public.gmane.org>,
+       "David S. Miller" <davem-fT/PcQaiUtIeIZ0/mPfg9Q@public.gmane.org>, linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
+       netdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Jiri Kosina <trivial-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+X-From: linux-nfs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mon Nov 15 04:07:25 2010
+Return-path: <linux-nfs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glN-linux-nfs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-nfs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1PHpPh-0001H8-D9
+       for glN-linux-nfs-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Mon, 15 Nov 2010 04:07:25 +0100
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S932828Ab0KODGd (ORCPT <rfc822;glN-linux-nfs@m.gmane.org>);
+       Sun, 14 Nov 2010 22:06:33 -0500
+Received: from mail.perches.com ([173.55.12.10]:1267 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932988Ab0KODGD (ORCPT <rfc822;linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Sun, 14 Nov 2010 22:06:03 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 3EB452436E;
+       Sun, 14 Nov 2010 19:04:30 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+Sender: linux-nfs-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-nfs.vger.kernel.org>
+X-Mailing-List: linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062287>
+
+Signed-off-by: Joe Perches <joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+---
+ net/sunrpc/addr.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/net/sunrpc/addr.c b/net/sunrpc/addr.c
+index 1419d0c..4195233 100644
+--- a/net/sunrpc/addr.c
++++ b/net/sunrpc/addr.c
+@@ -151,7 +151,7 @@ static size_t rpc_pton4(const char *buf, const size_t buflen,
+               return 0;
+       sin->sin_family = AF_INET;
+-      return sizeof(struct sockaddr_in);;
++      return sizeof(struct sockaddr_in);
+ }
+ #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
+the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002269:2, b/test/corpora/lkml/cur/1382298793.002269:2,
new file mode 100644 (file)
index 0000000..2e2652a
--- /dev/null
@@ -0,0 +1,74 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 32/44] fs/9p: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:51 -0800
+Lines: 35
+Message-ID: <f6ae29dfdd0b9ad401f5eb77ae670212689ee921.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Eric Van Hensbergen <ericvh@gmail.com>,
+       Ron Minnich <rminnich@sandia.gov>,
+       Latchesar Ionkov <lucho@ionkov.net>,
+       v9fs-developer@lists.sourceforge.net, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:07:26 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpPg-0001H8-Sy
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:07:25 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932992Ab0KODGD (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:06:03 -0500
+Received: from mail.perches.com ([173.55.12.10]:1227 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932846Ab0KODF4 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:56 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id C9F2E2436F;
+       Sun, 14 Nov 2010 19:04:23 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062288>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/9p/acl.c   |    2 +-
+ fs/9p/xattr.c |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/fs/9p/acl.c b/fs/9p/acl.c
+index 12d6023..bc4da9a 100644
+--- a/fs/9p/acl.c
++++ b/fs/9p/acl.c
+@@ -28,7 +28,7 @@ static struct posix_acl *__v9fs_get_acl(struct p9_fid *fid, char *name)
+ {
+       ssize_t size;
+       void *value = NULL;
+-      struct posix_acl *acl = NULL;;
++      struct posix_acl *acl = NULL;
+       size = v9fs_fid_xattr_get(fid, name, NULL, 0);
+       if (size > 0) {
+diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c
+index 43ec7df..d288773 100644
+--- a/fs/9p/xattr.c
++++ b/fs/9p/xattr.c
+@@ -133,7 +133,7 @@ int v9fs_xattr_set(struct dentry *dentry, const char *name,
+                       "p9_client_xattrcreate failed %d\n", retval);
+               goto error;
+       }
+-      msize = fid->clnt->msize;;
++      msize = fid->clnt->msize;
+       while (value_len) {
+               if (value_len > (msize - P9_IOHDRSZ))
+                       write_count = msize - P9_IOHDRSZ;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002270:2, b/test/corpora/lkml/cur/1382298793.002270:2,
new file mode 100644 (file)
index 0000000..8e09306
--- /dev/null
@@ -0,0 +1,68 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 41/44] net/ipv6/mcast.c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:05:00 -0800
+Lines: 26
+Message-ID: <1f3e1f7e454f3c62b66fc5f3e1e1ed90d62b7fb0.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: "David S. Miller" <davem@davemloft.net>,
+       Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>,
+       "Pekka Savola (ipv6)" <pekkas@netcore.fi>,
+       James Morris <jmorris@namei.org>,
+       Hideaki YOSHIFUJI <yoshfuji@linux-ipv6.org>,
+       Patrick McHardy <kaber@trash.net>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: netdev-owner@vger.kernel.org Mon Nov 15 04:07:30 2010
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PHpPi-0001H8-UT
+       for linux-netdev-2@lo.gmane.org; Mon, 15 Nov 2010 04:07:27 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933051Ab0KODGq (ORCPT <rfc822;linux-netdev-2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:06:46 -0500
+Received: from mail.perches.com ([173.55.12.10]:1258 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932980Ab0KODGC (ORCPT <rfc822;netdev@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:06:02 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 8F37A24377;
+       Sun, 14 Nov 2010 19:04:29 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062289>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ net/ipv6/mcast.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
+index d1444b9..9c50745 100644
+--- a/net/ipv6/mcast.c
++++ b/net/ipv6/mcast.c
+@@ -257,7 +257,7 @@ static struct inet6_dev *ip6_mc_find_dev_rcu(struct net *net,
+               return NULL;
+       idev = __in6_dev_get(dev);
+       if (!idev)
+-              return NULL;;
++              return NULL;
+       read_lock_bh(&idev->lock);
+       if (idev->dead) {
+               read_unlock_bh(&idev->lock);
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002271:2, b/test/corpora/lkml/cur/1382298793.002271:2,
new file mode 100644 (file)
index 0000000..144fc89
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 38/44] include/linux/if_macvlan.h: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:57 -0800
+Lines: 21
+Message-ID: <186ca914f887b2354ea3178696edc81cacbb28c6.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Patrick McHardy <kaber@trash.net>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:08:18 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpQX-0001fQ-U4
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:08:18 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933080Ab0KODHz (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:07:55 -0500
+Received: from mail.perches.com ([173.55.12.10]:1249 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932730Ab0KODGA (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:06:00 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id AAEDA24375;
+       Sun, 14 Nov 2010 19:04:27 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062290>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ include/linux/if_macvlan.h |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/include/linux/if_macvlan.h b/include/linux/if_macvlan.h
+index 8a2fd66..ac96a2d 100644
+--- a/include/linux/if_macvlan.h
++++ b/include/linux/if_macvlan.h
+@@ -69,7 +69,7 @@ static inline void macvlan_count_rx(const struct macvlan_dev *vlan,
+       rx_stats = this_cpu_ptr(vlan->rx_stats);
+       if (likely(success)) {
+               u64_stats_update_begin(&rx_stats->syncp);
+-              rx_stats->rx_packets++;;
++              rx_stats->rx_packets++;
+               rx_stats->rx_bytes += len;
+               if (multicast)
+                       rx_stats->rx_multicast++;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002272:2, b/test/corpora/lkml/cur/1382298793.002272:2,
new file mode 100644 (file)
index 0000000..f276bb4
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 37/44] fs/ubifs: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:56 -0800
+Lines: 21
+Message-ID: <902d76520da2f65e5dc44339dccb07159947f23d.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Artem Bityutskiy <dedekind1@gmail.com>,
+       Adrian Hunter <adrian.hunter@nokia.com>,
+       linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:08:19 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpQY-0001fQ-EH
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:08:18 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933129Ab0KODH5 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:07:57 -0500
+Received: from mail.perches.com ([173.55.12.10]:1247 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932907Ab0KODF7 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:59 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 0CF9424374;
+       Sun, 14 Nov 2010 19:04:27 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062291>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/ubifs/scan.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/fs/ubifs/scan.c b/fs/ubifs/scan.c
+index 3e1ee57..36216b4 100644
+--- a/fs/ubifs/scan.c
++++ b/fs/ubifs/scan.c
+@@ -328,7 +328,7 @@ struct ubifs_scan_leb *ubifs_scan(const struct ubifs_info *c, int lnum,
+               if (!quiet)
+                       ubifs_err("empty space starts at non-aligned offset %d",
+                                 offs);
+-              goto corrupted;;
++              goto corrupted;
+       }
+       ubifs_end_scan(c, sleb, lnum, offs);
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002273:2, b/test/corpora/lkml/cur/1382298793.002273:2,
new file mode 100644 (file)
index 0000000..d5eca3b
--- /dev/null
@@ -0,0 +1,64 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 39/44] include/net/caif/cfctrl.h: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:58 -0800
+Lines: 26
+Message-ID: <35914cfea1bd0ab3963e632d02b1fdd52a9d2bc8.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Sjur Braendeland <sjur.brandeland@stericsson.com>,
+       "David S. Miller" <davem@davemloft.net>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: netdev-owner@vger.kernel.org Mon Nov 15 04:08:21 2010
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PHpQW-0001fQ-TC
+       for linux-netdev-2@lo.gmane.org; Mon, 15 Nov 2010 04:08:17 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932988Ab0KODHd (ORCPT <rfc822;linux-netdev-2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:07:33 -0500
+Received: from mail.perches.com ([173.55.12.10]:1252 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932944Ab0KODGB (ORCPT <rfc822;netdev@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:06:01 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 4F8E124376;
+       Sun, 14 Nov 2010 19:04:28 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062292>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ include/net/caif/cfctrl.h |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/include/net/caif/cfctrl.h b/include/net/caif/cfctrl.h
+index 9402543..e54f639 100644
+--- a/include/net/caif/cfctrl.h
++++ b/include/net/caif/cfctrl.h
+@@ -51,7 +51,7 @@ struct cfctrl_rsp {
+       void (*restart_rsp)(void);
+       void (*radioset_rsp)(void);
+       void (*reject_rsp)(struct cflayer *layer, u8 linkid,
+-                              struct cflayer *client_layer);;
++                              struct cflayer *client_layer);
+ };
+ /* Link Setup Parameters for CAIF-Links. */
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002274:2, b/test/corpora/lkml/cur/1382298793.002274:2,
new file mode 100644 (file)
index 0000000..087ddf2
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 30/44] drivers/usb/gadget: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:49 -0800
+Lines: 21
+Message-ID: <cdc48b6ab9446585f304c801cca45a2a9d1e37ec.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: David Brownell <dbrownell@users.sourceforge.net>,
+       Greg Kroah-Hartman <gregkh@suse.de>, linux-usb@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:08:56 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpR9-0001sn-P6
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:08:56 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932917Ab0KODF7 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:59 -0500
+Received: from mail.perches.com ([173.55.12.10]:1216 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932842Ab0KODFz (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:55 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 6C4A92436C;
+       Sun, 14 Nov 2010 19:04:22 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062293>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/usb/gadget/f_fs.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c
+index 4a830df..38bb200 100644
+--- a/drivers/usb/gadget/f_fs.c
++++ b/drivers/usb/gadget/f_fs.c
+@@ -2096,7 +2096,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
+               ep = usb_ep_autoconfig(func->gadget, ds);
+               if (unlikely(!ep))
+                       return -ENOTSUPP;
+-              ep->driver_data = func->eps + idx;;
++              ep->driver_data = func->eps + idx;
+               req = usb_ep_alloc_request(ep, GFP_KERNEL);
+               if (unlikely(!req))
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002275:2, b/test/corpora/lkml/cur/1382298793.002275:2,
new file mode 100644 (file)
index 0000000..32fa526
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 36/44] fs/ocfs2: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:55 -0800
+Lines: 21
+Message-ID: <e32409b17aaa1a54fec85f3654583ef08fcf851c.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Mark Fasheh <mfasheh@suse.com>,
+       Joel Becker <joel.becker@oracle.com>,
+       ocfs2-devel@oss.oracle.com, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:08:57 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpRA-0001sn-Pm
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:08:57 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933007Ab0KODI2 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:08:28 -0500
+Received: from mail.perches.com ([173.55.12.10]:1239 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932884Ab0KODF7 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:59 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 54CEB24372;
+       Sun, 14 Nov 2010 19:04:26 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062294>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/ocfs2/refcounttree.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
+index b5f9160..da14a42 100644
+--- a/fs/ocfs2/refcounttree.c
++++ b/fs/ocfs2/refcounttree.c
+@@ -3704,7 +3704,7 @@ int ocfs2_refcount_cow_xattr(struct inode *inode,
+       context->cow_start = cow_start;
+       context->cow_len = cow_len;
+       context->ref_tree = ref_tree;
+-      context->ref_root_bh = ref_root_bh;;
++      context->ref_root_bh = ref_root_bh;
+       context->cow_object = xv;
+       context->cow_duplicate_clusters = ocfs2_duplicate_clusters_by_jbd;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002276:2, b/test/corpora/lkml/cur/1382298793.002276:2,
new file mode 100644 (file)
index 0000000..9e318aa
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 35/44] fs/nfs: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:54 -0800
+Lines: 21
+Message-ID: <ec29c2321578915d1d219f5ad00b876a3ff1a105.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Trond Myklebust <Trond.Myklebust@netapp.com>,
+       linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:09:03 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpRB-0001sn-9x
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:08:57 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933153Ab0KODIl (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:08:41 -0500
+Received: from mail.perches.com ([173.55.12.10]:1236 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932876Ab0KODF6 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:58 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 8DCD324371;
+       Sun, 14 Nov 2010 19:04:25 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062295>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/nfs/getroot.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c
+index ac7b814..e17f628 100644
+--- a/fs/nfs/getroot.c
++++ b/fs/nfs/getroot.c
+@@ -190,7 +190,7 @@ struct dentry *nfs4_get_root(struct super_block *sb, struct nfs_fh *mntfh)
+       fattr = nfs_alloc_fattr();
+       if (fattr == NULL)
+-              return ERR_PTR(-ENOMEM);;
++              return ERR_PTR(-ENOMEM);
+       /* get the actual root for this mount */
+       error = server->nfs_client->rpc_ops->getattr(server, mntfh, fattr);
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002277:2, b/test/corpora/lkml/cur/1382298793.002277:2,
new file mode 100644 (file)
index 0000000..7fb2d9a
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 34/44] fs/logfs: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:53 -0800
+Lines: 21
+Message-ID: <0c990bdacb2f9bf256acbb06ca59130585f600b7.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Joern Engel <joern@logfs.org>, logfs@logfs.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:09:34 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpRl-000297-1j
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:09:33 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933187Ab0KODI4 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:08:56 -0500
+Received: from mail.perches.com ([173.55.12.10]:1234 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932859Ab0KODF5 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:57 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 0339024370;
+       Sun, 14 Nov 2010 19:04:25 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062296>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/logfs/readwrite.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/fs/logfs/readwrite.c b/fs/logfs/readwrite.c
+index 6127baf..b4304f2 100644
+--- a/fs/logfs/readwrite.c
++++ b/fs/logfs/readwrite.c
+@@ -481,7 +481,7 @@ static int inode_write_alias(struct super_block *sb,
+                       val = inode_val0(inode);
+                       break;
+               case INODE_USED_OFS:
+-                      val = cpu_to_be64(li->li_used_bytes);;
++                      val = cpu_to_be64(li->li_used_bytes);
+                       break;
+               case INODE_SIZE_OFS:
+                       val = cpu_to_be64(i_size_read(inode));
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002278:2, b/test/corpora/lkml/cur/1382298793.002278:2,
new file mode 100644 (file)
index 0000000..3048c16
--- /dev/null
@@ -0,0 +1,63 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 33/44] fs/ceph: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:52 -0800
+Lines: 26
+Message-ID: <e01252afc842668a94fb0549e2d1833d77a6411f.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Sage Weil <sage@newdream.net>, ceph-devel@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: ceph-devel-owner@vger.kernel.org Mon Nov 15 04:09:34 2010
+Return-path: <ceph-devel-owner@vger.kernel.org>
+Envelope-to: gcfcd-ceph-devel3-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <ceph-devel-owner@vger.kernel.org>)
+       id 1PHpRl-000297-Hx
+       for gcfcd-ceph-devel3-2@lo.gmane.org; Mon, 15 Nov 2010 04:09:33 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932690Ab0KODJN (ORCPT <rfc822;gcfcd-ceph-devel3-2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:09:13 -0500
+Received: from mail.perches.com ([173.55.12.10]:1231 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932849Ab0KODF5 (ORCPT <rfc822;ceph-devel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:57 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 69DEF2436B;
+       Sun, 14 Nov 2010 19:04:24 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: ceph-devel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <ceph-devel.vger.kernel.org>
+X-Mailing-List: ceph-devel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062297>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ fs/ceph/mds_client.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
+index 3142b15..931124c 100644
+--- a/fs/ceph/mds_client.c
++++ b/fs/ceph/mds_client.c
+@@ -2023,7 +2023,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
+               } else  {
+                       struct ceph_inode_info *ci = ceph_inode(req->r_inode);
+                       struct ceph_cap *cap =
+-                              ceph_get_cap_for_mds(ci, req->r_mds);;
++                              ceph_get_cap_for_mds(ci, req->r_mds);
+                       dout("already using auth");
+                       if ((!cap || cap != ci->i_auth_cap) ||
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe ceph-devel" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002279:2, b/test/corpora/lkml/cur/1382298793.002279:2,
new file mode 100644 (file)
index 0000000..1dbe4c3
--- /dev/null
@@ -0,0 +1,88 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 26/44] drivers/scsi/qla2xxx: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:45 -0800
+Lines: 49
+Message-ID: <40854ce1b1e958e2c0bb9b79911d89b45b5e5f27.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Andrew Vasquez <andrew.vasquez@qlogic.com>,
+       linux-driver@qlogic.com,
+       "James E.J. Bottomley" <James.Bottomley@suse.de>,
+       linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-scsi-owner@vger.kernel.org Mon Nov 15 04:10:09 2010
+Return-path: <linux-scsi-owner@vger.kernel.org>
+Envelope-to: lnx-linux-scsi@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-scsi-owner@vger.kernel.org>)
+       id 1PHpSK-0002RT-EP
+       for lnx-linux-scsi@lo.gmane.org; Mon, 15 Nov 2010 04:10:08 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932860Ab0KODJt (ORCPT <rfc822;lnx-linux-scsi@m.gmane.org>);
+       Sun, 14 Nov 2010 22:09:49 -0500
+Received: from mail.perches.com ([173.55.12.10]:1202 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932797Ab0KODFo (ORCPT <rfc822;linux-scsi@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:44 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id E65A02436B;
+       Sun, 14 Nov 2010 19:04:11 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-scsi-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-scsi.vger.kernel.org>
+X-Mailing-List: linux-scsi@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062298>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/scsi/qla2xxx/qla_isr.c |    4 ++--
+ drivers/scsi/qla2xxx/qla_nx.c  |    2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c
+index 1f06ddd..59c4870 100644
+--- a/drivers/scsi/qla2xxx/qla_isr.c
++++ b/drivers/scsi/qla2xxx/qla_isr.c
+@@ -1049,7 +1049,7 @@ qla2x00_ct_entry(scsi_qla_host_t *vha, struct req_que *req,
+               }
+               DEBUG2(qla2x00_dump_buffer((uint8_t *)pkt, sizeof(*pkt)));
+       } else {
+-              bsg_job->reply->result =  DID_OK << 16;;
++              bsg_job->reply->result =  DID_OK << 16;
+               bsg_job->reply->reply_payload_rcv_len =
+                   bsg_job->reply_payload.payload_len;
+               bsg_job->reply_len = 0;
+@@ -1144,7 +1144,7 @@ qla24xx_els_ct_entry(scsi_qla_host_t *vha, struct req_que *req,
+               DEBUG2(qla2x00_dump_buffer((uint8_t *)pkt, sizeof(*pkt)));
+       }
+       else {
+-              bsg_job->reply->result =  DID_OK << 16;;
++              bsg_job->reply->result =  DID_OK << 16;
+               bsg_job->reply->reply_payload_rcv_len = bsg_job->reply_payload.payload_len;
+               bsg_job->reply_len = 0;
+       }
+diff --git a/drivers/scsi/qla2xxx/qla_nx.c b/drivers/scsi/qla2xxx/qla_nx.c
+index 8d9edfb..de2d1eb 100644
+--- a/drivers/scsi/qla2xxx/qla_nx.c
++++ b/drivers/scsi/qla2xxx/qla_nx.c
+@@ -1022,7 +1022,7 @@ ql82xx_rom_lock_d(struct qla_hw_data *ha)
+               qla_printk(KERN_WARNING, ha, "ROM lock failed\n");
+               return -1;
+       }
+-      return 0;;
++      return 0;
+ }
+ static int
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002280:2, b/test/corpora/lkml/cur/1382298793.002280:2,
new file mode 100644 (file)
index 0000000..f8961e7
--- /dev/null
@@ -0,0 +1,57 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 27/44] drivers/serial: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:46 -0800
+Lines: 21
+Message-ID: <57c2393ee99b62bca43fa1c510cae832795069ba.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:10:10 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpSJ-0002RT-DE
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:10:07 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932970Ab0KODJh (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:09:37 -0500
+Received: from mail.perches.com ([173.55.12.10]:1204 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932802Ab0KODFp (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:45 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 7FB8224371;
+       Sun, 14 Nov 2010 19:04:12 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062299>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/serial/mrst_max3110.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/serial/mrst_max3110.c b/drivers/serial/mrst_max3110.c
+index b62857b..00d284c 100644
+--- a/drivers/serial/mrst_max3110.c
++++ b/drivers/serial/mrst_max3110.c
+@@ -56,7 +56,7 @@ struct uart_max3110 {
+       wait_queue_head_t wq;
+       struct task_struct *main_thread;
+       struct task_struct *read_thread;
+-      struct mutex thread_mutex;;
++      struct mutex thread_mutex;
+       u32 baud;
+       u16 cur_conf;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002281:2, b/test/corpora/lkml/cur/1382298793.002281:2,
new file mode 100644 (file)
index 0000000..a1349a9
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 24/44] drivers/scsi/lpfc: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:43 -0800
+Lines: 21
+Message-ID: <1a0612305d3b141f85a6aeef6d91cd6829a80bae.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: James Smart <james.smart@emulex.com>,
+       "James E.J. Bottomley" <James.Bottomley@suse.de>,
+       linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:10:40 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpSn-0002jF-BC
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:10:37 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933110Ab0KODKE (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:10:04 -0500
+Received: from mail.perches.com ([173.55.12.10]:1195 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932782Ab0KODFn (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:43 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 86AD42436E;
+       Sun, 14 Nov 2010 19:04:10 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062300>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/scsi/lpfc/lpfc_bsg.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c
+index 7260c3a..9e2c652 100644
+--- a/drivers/scsi/lpfc/lpfc_bsg.c
++++ b/drivers/scsi/lpfc/lpfc_bsg.c
+@@ -617,7 +617,7 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job)
+       dd_data->context_un.iocb.cmdiocbq = cmdiocbq;
+       dd_data->context_un.iocb.rspiocbq = rspiocbq;
+       dd_data->context_un.iocb.set_job = job;
+-      dd_data->context_un.iocb.bmp = NULL;;
++      dd_data->context_un.iocb.bmp = NULL;
+       dd_data->context_un.iocb.ndlp = ndlp;
+       if (phba->cfg_poll & DISABLE_FCP_RING_INT) {
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002282:2, b/test/corpora/lkml/cur/1382298793.002282:2,
new file mode 100644 (file)
index 0000000..49db122
--- /dev/null
@@ -0,0 +1,59 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 23/44] drivers/scsi/bfa: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:42 -0800
+Lines: 21
+Message-ID: <71d0d7db4197f7b6f6b946a295648dc18bd559e0.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Jing Huang <huangj@brocade.com>,
+       "James E.J. Bottomley" <James.Bottomley@suse.de>,
+       linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:10:53 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpT0-0002p1-2I
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:10:50 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932981Ab0KODKh (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:10:37 -0500
+Received: from mail.perches.com ([173.55.12.10]:1192 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932746Ab0KODFm (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:42 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id EFB872436B;
+       Sun, 14 Nov 2010 19:04:09 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062301>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/scsi/bfa/bfa_fcs_lport.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/scsi/bfa/bfa_fcs_lport.c b/drivers/scsi/bfa/bfa_fcs_lport.c
+index 377cbff..55b3f74 100644
+--- a/drivers/scsi/bfa/bfa_fcs_lport.c
++++ b/drivers/scsi/bfa/bfa_fcs_lport.c
+@@ -1683,7 +1683,7 @@ bfa_fcs_lport_fdmi_build_rhba_pyld(struct bfa_fcs_lport_fdmi_s *fdmi, u8 *pyld)
+       memcpy(attr->value, fcs_hba_attr->driver_version, attr->len);
+       attr->len = fc_roundup(attr->len, sizeof(u32));
+       curr_ptr += sizeof(attr->type) + sizeof(attr->len) + attr->len;
+-      len += attr->len;;
++      len += attr->len;
+       count++;
+       attr->len = cpu_to_be16(attr->len + sizeof(attr->type) +
+                            sizeof(attr->len));
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002283:2, b/test/corpora/lkml/cur/1382298793.002283:2,
new file mode 100644 (file)
index 0000000..6b6c939
--- /dev/null
@@ -0,0 +1,68 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 22/44] drivers/scsi/be2iscsi: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:41 -0800
+Lines: 30
+Message-ID: <7d4fbb8d3ac34861808dac24efeebe05011f6b0c.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Jayamohan Kallickal <jayamohank@serverengines.com>,
+       "James E.J. Bottomley" <James.Bottomley@suse.de>,
+       linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:10:52 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpSz-0002p1-Hv
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:10:49 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932862Ab0KODKi (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:10:38 -0500
+Received: from mail.perches.com ([173.55.12.10]:1188 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932757Ab0KODFm (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:42 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 610FF24371;
+       Sun, 14 Nov 2010 19:04:09 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062302>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/scsi/be2iscsi/be_main.c |    4 ++--
+ 1 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/scsi/be2iscsi/be_main.c b/drivers/scsi/be2iscsi/be_main.c
+index 75a85aa..db60563 100644
+--- a/drivers/scsi/be2iscsi/be_main.c
++++ b/drivers/scsi/be2iscsi/be_main.c
+@@ -618,7 +618,7 @@ static void beiscsi_get_params(struct beiscsi_hba *phba)
+                                   + BE2_NOPOUT_REQ));
+       phba->params.cxns_per_ctrl = phba->fw_config.iscsi_cid_count;
+       phba->params.asyncpdus_per_ctrl = phba->fw_config.iscsi_cid_count * 2;
+-      phba->params.icds_per_ctrl = phba->fw_config.iscsi_icd_count;;
++      phba->params.icds_per_ctrl = phba->fw_config.iscsi_icd_count;
+       phba->params.num_sge_per_io = BE2_SGE;
+       phba->params.defpdu_hdr_sz = BE2_DEFPDU_HDR_SZ;
+       phba->params.defpdu_data_sz = BE2_DEFPDU_DATA_SZ;
+@@ -781,7 +781,7 @@ static irqreturn_t be_isr(int irq, void *dev_id)
+       int isr;
+       phba = dev_id;
+-      ctrl = &phba->ctrl;;
++      ctrl = &phba->ctrl;
+       isr = ioread32(ctrl->csr + CEV_ISR0_OFFSET +
+                      (PCI_FUNC(ctrl->pdev->devfn) * CEV_ISR_SIZE));
+       if (!isr)
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002284:2, b/test/corpora/lkml/cur/1382298793.002284:2,
new file mode 100644 (file)
index 0000000..315c1be
--- /dev/null
@@ -0,0 +1,61 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 15/44] drivers/net/vxge: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:34 -0800
+Lines: 21
+Message-ID: <e86e79a18106cc38715136bfb2e880b38f5ac764.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Ramkrishna Vepa <ramkrishna.vepa@exar.com>,
+       Sivakumar Subramani <sivakumar.subramani@exar.com>,
+       Sreenivasa Honnur <sreenivasa.honnur@exar.com>,
+       Jon Mason <jon.mason@exar.com>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:11:16 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpTP-00032R-QK
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:11:16 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932740Ab0KODFk (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:40 -0500
+Received: from mail.perches.com ([173.55.12.10]:1166 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932690Ab0KODFh (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:37 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id BAAD824377;
+       Sun, 14 Nov 2010 19:04:04 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062303>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/vxge/vxge-main.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/vxge/vxge-main.c b/drivers/net/vxge/vxge-main.c
+index 813829f..93e80c5 100644
+--- a/drivers/net/vxge/vxge-main.c
++++ b/drivers/net/vxge/vxge-main.c
+@@ -2062,7 +2062,7 @@ static irqreturn_t vxge_isr_napi(int irq, void *dev_id)
+       struct __vxge_hw_device *hldev;
+       u64 reason;
+       enum vxge_hw_status status;
+-      struct vxgedev *vdev = (struct vxgedev *) dev_id;;
++      struct vxgedev *vdev = (struct vxgedev *)dev_id;
+       vxge_debug_intr(VXGE_TRACE, "%s:%d", __func__, __LINE__);
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002285:2, b/test/corpora/lkml/cur/1382298793.002285:2,
new file mode 100644 (file)
index 0000000..80ec0f9
--- /dev/null
@@ -0,0 +1,61 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 21/44] drivers/s390/net: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:40 -0800
+Lines: 21
+Message-ID: <ea09773876fb36a52a9a750110b381d20767ac12.1289789605.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Ursula Braun <ursula.braun@de.ibm.com>,
+       Frank Blaschka <blaschka@linux.vnet.ibm.com>,
+       linux390@de.ibm.com, Martin Schwidefsky <schwidefsky@de.ibm.com>,
+       Heiko Carstens <heiko.carstens@de.ibm.com>,
+       linux-s390@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:11:19 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpTR-00032R-T3
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:11:18 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932825Ab0KODLK (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:11:10 -0500
+Received: from mail.perches.com ([173.55.12.10]:1186 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932749Ab0KODFl (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:41 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id C07A924370;
+       Sun, 14 Nov 2010 19:04:08 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062304>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/s390/net/qeth_core_sys.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/s390/net/qeth_core_sys.c b/drivers/s390/net/qeth_core_sys.c
+index 42fa783..b5e967c 100644
+--- a/drivers/s390/net/qeth_core_sys.c
++++ b/drivers/s390/net/qeth_core_sys.c
+@@ -372,7 +372,7 @@ static ssize_t qeth_dev_performance_stats_store(struct device *dev,
+       i = simple_strtoul(buf, &tmp, 16);
+       if ((i == 0) || (i == 1)) {
+               if (i == card->options.performance_stats)
+-                      goto out;;
++                      goto out;
+               card->options.performance_stats = i;
+               if (i == 0)
+                       memset(&card->perf_stats, 0,
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002286:2, b/test/corpora/lkml/cur/1382298793.002286:2,
new file mode 100644 (file)
index 0000000..9045476
--- /dev/null
@@ -0,0 +1,62 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 17/44] drivers/net/wireless/iwlwifi: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:36 -0800
+Lines: 21
+Message-ID: <6beaab935c2c511a5833e855db527976ef05e2dc.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Reinette Chatre <reinette.chatre@intel.com>,
+       Wey-Yi Guy <wey-yi.w.guy@intel.com>,
+       Intel Linux Wireless <ilw@linux.intel.com>,
+       "John W. Linville" <linville@tuxdriver.com>,
+       linux-wireless@vger.kernel.org, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:11:41 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpTn-0003Ei-3m
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:11:39 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933222Ab0KODLe (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:11:34 -0500
+Received: from mail.perches.com ([173.55.12.10]:1174 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932718Ab0KODFj (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:39 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 3C0E524379;
+       Sun, 14 Nov 2010 19:04:06 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062305>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/wireless/iwlwifi/iwl-agn.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/wireless/iwlwifi/iwl-agn.c b/drivers/net/wireless/iwlwifi/iwl-agn.c
+index c2636a7..f293fb6 100644
+--- a/drivers/net/wireless/iwlwifi/iwl-agn.c
++++ b/drivers/net/wireless/iwlwifi/iwl-agn.c
+@@ -2420,7 +2420,7 @@ static const char *desc_lookup(u32 num)
+       max = ARRAY_SIZE(advanced_lookup) - 1;
+       for (i = 0; i < max; i++) {
+               if (advanced_lookup[i].num == num)
+-                      break;;
++                      break;
+       }
+       return advanced_lookup[i].name;
+ }
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002287:2, b/test/corpora/lkml/cur/1382298793.002287:2,
new file mode 100644 (file)
index 0000000..4fa3cef
--- /dev/null
@@ -0,0 +1,57 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 18/44] drivers/net/cnic.c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:37 -0800
+Lines: 21
+Message-ID: <950331e47b16c2ad28d73502f30f5a0f017b5493.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:12:07 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpUF-0003Pl-09
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:12:07 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933052Ab0KODLd (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:11:33 -0500
+Received: from mail.perches.com ([173.55.12.10]:1176 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932652Ab0KODFj (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:39 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id D093A24378;
+       Sun, 14 Nov 2010 19:04:06 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062306>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/cnic.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/cnic.c b/drivers/net/cnic.c
+index 92bac19..594ca9c 100644
+--- a/drivers/net/cnic.c
++++ b/drivers/net/cnic.c
+@@ -1695,7 +1695,7 @@ static int cnic_bnx2x_iscsi_ofld1(struct cnic_dev *dev, struct kwqe *wqes[],
+               *work = num;
+               return -EINVAL;
+       }
+-      *work = 2 + req2->num_additional_wqes;;
++      *work = 2 + req2->num_additional_wqes;
+       l5_cid = req1->iscsi_conn_id;
+       if (l5_cid >= MAX_ISCSI_TBL_SZ)
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002288:2, b/test/corpora/lkml/cur/1382298793.002288:2,
new file mode 100644 (file)
index 0000000..5b1515a
--- /dev/null
@@ -0,0 +1,63 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 16/44] drivers/net/wireless/ath: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:35 -0800
+Lines: 21
+Message-ID: <c375cdc1175018f00066e2220f1d659ca70cde16.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: "Luis R. Rodriguez" <lrodriguez@atheros.com>,
+       Jouni Malinen <jmalinen@atheros.com>,
+       Vasanthakumar Thiagarajan <vasanth@atheros.com>,
+       Senthil Balasubramanian <senthilkumar@atheros.com>,
+       "John W. Linville" <linville@tuxdriver.com>,
+       linux-wireless@vger.kernel.org, ath9k-devel@lists.ath9k.org,
+       netdev@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:12:48 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpUt-0003ie-NF
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:12:48 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932786Ab0KODMQ (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:12:16 -0500
+Received: from mail.perches.com ([173.55.12.10]:1169 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932695Ab0KODFi (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:38 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 6AD192437A;
+       Sun, 14 Nov 2010 19:04:05 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062307>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/wireless/ath/ath9k/htc.h |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h
+index 75ecf6a..4c98b93 100644
+--- a/drivers/net/wireless/ath/ath9k/htc.h
++++ b/drivers/net/wireless/ath/ath9k/htc.h
+@@ -434,7 +434,7 @@ void ath9k_htc_beaconep(void *drv_priv, struct sk_buff *skb,
+ void ath9k_htc_station_work(struct work_struct *work);
+ void ath9k_htc_aggr_work(struct work_struct *work);
+-void ath9k_ani_work(struct work_struct *work);;
++void ath9k_ani_work(struct work_struct *work);
+ int ath9k_tx_init(struct ath9k_htc_priv *priv);
+ void ath9k_tx_tasklet(unsigned long data);
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002289:2, b/test/corpora/lkml/cur/1382298793.002289:2,
new file mode 100644 (file)
index 0000000..a6111d4
--- /dev/null
@@ -0,0 +1,86 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 09/44] drivers/media/video: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:28 -0800
+Lines: 49
+Message-ID: <d7cec5e05200050ee2c7f624eef8c571193b4d92.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Mauro Carvalho Chehab <mchehab@infradead.org>,
+       linux-media@vger.kernel.org, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-media-owner@vger.kernel.org Mon Nov 15 04:13:25 2010
+Return-path: <linux-media-owner@vger.kernel.org>
+Envelope-to: gldv-linux-media@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-media-owner@vger.kernel.org>)
+       id 1PHpVT-00042L-DI
+       for gldv-linux-media@lo.gmane.org; Mon, 15 Nov 2010 04:13:23 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932630Ab0KODFd (ORCPT <rfc822;gldv-linux-media@m.gmane.org>);
+       Sun, 14 Nov 2010 22:05:33 -0500
+Received: from mail.perches.com ([173.55.12.10]:1148 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932627Ab0KODFd (ORCPT <rfc822;linux-media@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:33 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 873E324371;
+       Sun, 14 Nov 2010 19:04:00 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-media-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-media.vger.kernel.org>
+X-Mailing-List: linux-media@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062308>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/media/video/cx88/cx88-blackbird.c  |    2 +-
+ drivers/media/video/davinci/vpfe_capture.c |    2 +-
+ drivers/media/video/em28xx/em28xx-cards.c  |    2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/media/video/cx88/cx88-blackbird.c b/drivers/media/video/cx88/cx88-blackbird.c
+index 417d1d5..14b2546 100644
+--- a/drivers/media/video/cx88/cx88-blackbird.c
++++ b/drivers/media/video/cx88/cx88-blackbird.c
+@@ -1065,7 +1065,7 @@ static int mpeg_open(struct file *file)
+               err = drv->request_acquire(drv);
+               if(err != 0) {
+                       dprintk(1,"%s: Unable to acquire hardware, %d\n", __func__, err);
+-                      mutex_unlock(&dev->core->lock);;
++                      mutex_unlock(&dev->core->lock);
+                       return err;
+               }
+       }
+diff --git a/drivers/media/video/davinci/vpfe_capture.c b/drivers/media/video/davinci/vpfe_capture.c
+index d8e38cc..14f3d54 100644
+--- a/drivers/media/video/davinci/vpfe_capture.c
++++ b/drivers/media/video/davinci/vpfe_capture.c
+@@ -1276,7 +1276,7 @@ static int vpfe_videobuf_prepare(struct videobuf_queue *vq,
+               vb->size = vpfe_dev->fmt.fmt.pix.sizeimage;
+               vb->field = field;
+-              ret = videobuf_iolock(vq, vb, NULL);;
++              ret = videobuf_iolock(vq, vb, NULL);
+               if (ret < 0)
+                       return ret;
+diff --git a/drivers/media/video/em28xx/em28xx-cards.c b/drivers/media/video/em28xx/em28xx-cards.c
+index 5485923..7aee7f0 100644
+--- a/drivers/media/video/em28xx/em28xx-cards.c
++++ b/drivers/media/video/em28xx/em28xx-cards.c
+@@ -2408,7 +2408,7 @@ void em28xx_register_i2c_ir(struct em28xx *dev)
+               dev->init_data.get_key = em28xx_get_key_em_haup;
+               dev->init_data.name = "i2c IR (EM2840 Hauppauge)";
+       case EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE:
+-              dev->init_data.ir_codes = RC_MAP_WINFAST_USBII_DELUXE;;
++              dev->init_data.ir_codes = RC_MAP_WINFAST_USBII_DELUXE;
+               dev->init_data.get_key = em28xx_get_key_winfast_usbii_deluxe;
+               dev->init_data.name = "i2c IR (EM2820 Winfast TV USBII Deluxe)";
+               break;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002290:2, b/test/corpora/lkml/cur/1382298793.002290:2,
new file mode 100644 (file)
index 0000000..bf05cb1
--- /dev/null
@@ -0,0 +1,81 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 12/44] drivers/net/bnx2x: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:31 -0800
+Lines: 44
+Message-ID: <2bfaf1f1fe5d503a8a386a433b5187997819d771.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Eilon Greenstein <eilong@broadcom.com>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:13:27 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpVW-00042L-1L
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:13:26 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932754Ab0KODMy (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:12:54 -0500
+Received: from mail.perches.com ([173.55.12.10]:1156 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932660Ab0KODFf (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:35 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id AB58824375;
+       Sun, 14 Nov 2010 19:04:02 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062309>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/net/bnx2x/bnx2x_link.c |    4 ++--
+ drivers/net/bnx2x/bnx2x_main.c |    2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/net/bnx2x/bnx2x_link.c b/drivers/net/bnx2x/bnx2x_link.c
+index 5809196..38aeffe 100644
+--- a/drivers/net/bnx2x/bnx2x_link.c
++++ b/drivers/net/bnx2x/bnx2x_link.c
+@@ -3904,7 +3904,7 @@ static u8 bnx2x_8726_read_sfp_module_eeprom(struct bnx2x_phy *phy,
+                             MDIO_PMA_REG_SFP_TWO_WIRE_CTRL, &val);
+               if ((val & MDIO_PMA_REG_SFP_TWO_WIRE_CTRL_STATUS_MASK) ==
+                   MDIO_PMA_REG_SFP_TWO_WIRE_STATUS_IDLE)
+-                      return 0;;
++                      return 0;
+               msleep(1);
+       }
+       return -EINVAL;
+@@ -3988,7 +3988,7 @@ static u8 bnx2x_8727_read_sfp_module_eeprom(struct bnx2x_phy *phy,
+                             MDIO_PMA_REG_SFP_TWO_WIRE_CTRL, &val);
+               if ((val & MDIO_PMA_REG_SFP_TWO_WIRE_CTRL_STATUS_MASK) ==
+                   MDIO_PMA_REG_SFP_TWO_WIRE_STATUS_IDLE)
+-                      return 0;;
++                      return 0;
+               msleep(1);
+       }
+diff --git a/drivers/net/bnx2x/bnx2x_main.c b/drivers/net/bnx2x/bnx2x_main.c
+index e9ad16f..7ffcb08 100644
+--- a/drivers/net/bnx2x/bnx2x_main.c
++++ b/drivers/net/bnx2x/bnx2x_main.c
+@@ -8078,7 +8078,7 @@ static void __devinit bnx2x_get_port_hwinfo(struct bnx2x *bp)
+       int port = BP_PORT(bp);
+       u32 val, val2;
+       u32 config;
+-      u32 ext_phy_type, ext_phy_config;;
++      u32 ext_phy_type, ext_phy_config;
+       bp->link_params.bp = bp;
+       bp->link_params.port = port;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002292:2, b/test/corpora/lkml/cur/1382298793.002292:2,
new file mode 100644 (file)
index 0000000..3aceed7
--- /dev/null
@@ -0,0 +1,71 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 10/44] drivers/misc: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:29 -0800
+Lines: 35
+Message-ID: <f1f1ff72045c075062d3fbe8d2bfcf67bdb1571d.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:14:21 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpWA-0004SF-ER
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:14:06 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932800Ab0KODNW (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:13:22 -0500
+Received: from mail.perches.com ([173.55.12.10]:1151 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932629Ab0KODFd (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:33 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 4E7E024372;
+       Sun, 14 Nov 2010 19:04:01 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062311>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/misc/bmp085.c   |    2 +-
+ drivers/misc/isl29020.c |    2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/misc/bmp085.c b/drivers/misc/bmp085.c
+index 63ee4c1..6b8a394 100644
+--- a/drivers/misc/bmp085.c
++++ b/drivers/misc/bmp085.c
+@@ -216,7 +216,7 @@ static s32 bmp085_get_temperature(struct bmp085_data *data, int *temperature)
+               *temperature = (x1+x2+8) >> 4;
+ exit:
+-      return status;;
++      return status;
+ }
+ /*
+diff --git a/drivers/misc/isl29020.c b/drivers/misc/isl29020.c
+index ca47e62..e6bbf13 100644
+--- a/drivers/misc/isl29020.c
++++ b/drivers/misc/isl29020.c
+@@ -158,7 +158,7 @@ static int als_set_default_config(struct i2c_client *client)
+               dev_err(&client->dev, "default write failed.");
+               return retval;
+       }
+-      return 0;;
++      return 0;
+ }
+ static int  isl29020_probe(struct i2c_client *client,
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002293:2, b/test/corpora/lkml/cur/1382298793.002293:2,
new file mode 100644 (file)
index 0000000..a547a9b
--- /dev/null
@@ -0,0 +1,57 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 08/44] drivers/leds: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:27 -0800
+Lines: 21
+Message-ID: <054f6857b7472d9f4c540c298cef0aa77bce6962.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Richard Purdie <rpurdie@rpsys.net>, linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:14:24 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpWB-0004SF-F9
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:14:07 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932958Ab0KODNw (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:13:52 -0500
+Received: from mail.perches.com ([173.55.12.10]:1145 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932602Ab0KODFc (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:32 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 92C3F24370;
+       Sun, 14 Nov 2010 19:03:59 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062312>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/leds/leds-mc13783.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c
+index f05bb08..f369e56 100644
+--- a/drivers/leds/leds-mc13783.c
++++ b/drivers/leds/leds-mc13783.c
+@@ -234,7 +234,7 @@ static int __devinit mc13783_leds_prepare(struct platform_device *pdev)
+                                                       MC13783_LED_Cx_PERIOD;
+       if (pdata->flags & MC13783_LED_TRIODE_TC3)
+-              reg |= MC13783_LED_Cx_TRIODE_TC_BIT;;
++              reg |= MC13783_LED_Cx_TRIODE_TC_BIT;
+       ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_5, reg);
+       if (ret)
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002294:2, b/test/corpora/lkml/cur/1382298793.002294:2,
new file mode 100644 (file)
index 0000000..aa5821b
--- /dev/null
@@ -0,0 +1,63 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 04/44] drivers/cpufreq: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:23 -0800
+Lines: 26
+Message-ID: <a4bef9c18ce34e80870a07c728ee25e8efac6d9d.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Dave Jones <davej@redhat.com>, cpufreq@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: cpufreq-owner@vger.kernel.org Mon Nov 15 04:16:07 2010
+Return-path: <cpufreq-owner@vger.kernel.org>
+Envelope-to: glkc-cpufreq2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <cpufreq-owner@vger.kernel.org>)
+       id 1PHpY5-0005lg-O4
+       for glkc-cpufreq2@lo.gmane.org; Mon, 15 Nov 2010 04:16:06 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932680Ab0KODO4 (ORCPT <rfc822;glkc-cpufreq2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:14:56 -0500
+Received: from mail.perches.com ([173.55.12.10]:1127 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1757376Ab0KODF3 (ORCPT <rfc822;cpufreq@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:29 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 1F49D2436E;
+       Sun, 14 Nov 2010 19:03:57 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: cpufreq-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <cpufreq.vger.kernel.org>
+X-Mailing-List: cpufreq@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062313>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/cpufreq/cpufreq_conservative.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/cpufreq/cpufreq_conservative.c b/drivers/cpufreq/cpufreq_conservative.c
+index 526bfbf..b0d8f3d 100644
+--- a/drivers/cpufreq/cpufreq_conservative.c
++++ b/drivers/cpufreq/cpufreq_conservative.c
+@@ -118,7 +118,7 @@ static inline cputime64_t get_cpu_idle_time_jiffy(unsigned int cpu,
+       if (wall)
+               *wall = (cputime64_t)jiffies_to_usecs(cur_wall_time);
+-      return (cputime64_t)jiffies_to_usecs(idle_time);;
++      return (cputime64_t)jiffies_to_usecs(idle_time);
+ }
+ static inline cputime64_t get_cpu_idle_time(unsigned int cpu, cputime64_t *wall)
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe cpufreq" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002296:2, b/test/corpora/lkml/cur/1382298793.002296:2,
new file mode 100644 (file)
index 0000000..4d337c7
--- /dev/null
@@ -0,0 +1,148 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 01/44] arch/arm: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:20 -0800
+Lines: 105
+Message-ID: <b6d517c8da3ca0d50c836736e76059c89d692b6e.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Russell King <linux@arm.linux.org.uk>,
+       Wan ZongShun <mcuos.com@gmail.com>,
+       Colin Cross <ccross@android.com>,
+       Erik Gilling <konkers@android.com>,
+       Olof Johansson <olof@lixom.net>,
+       Sascha Hauer <kernel@pengutronix.de>,
+       linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org,
+       linux-tegra@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:16:10 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpY9-0005lg-2R
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:16:09 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933240Ab0KODPt (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:15:49 -0500
+Received: from mail.perches.com ([173.55.12.10]:1125 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1757314Ab0KODF2 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:28 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 4C0C32436B;
+       Sun, 14 Nov 2010 19:03:55 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062315>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ arch/arm/mach-at91/at91cap9_devices.c    |    2 +-
+ arch/arm/mach-at91/at91sam9g45_devices.c |    2 +-
+ arch/arm/mach-at91/at91sam9rl_devices.c  |    2 +-
+ arch/arm/mach-nuc93x/time.c              |    2 +-
+ arch/arm/mach-tegra/tegra2_clocks.c      |    2 +-
+ arch/arm/mach-w90x900/cpu.c              |    2 +-
+ arch/arm/plat-mxc/irq.c                  |    2 +-
+ 7 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/arch/arm/mach-at91/at91cap9_devices.c b/arch/arm/mach-at91/at91cap9_devices.c
+index d1f775e..308ce7a 100644
+--- a/arch/arm/mach-at91/at91cap9_devices.c
++++ b/arch/arm/mach-at91/at91cap9_devices.c
+@@ -171,7 +171,7 @@ void __init at91_add_device_usba(struct usba_platform_data *data)
+        */
+       usba_udc_data.pdata.vbus_pin = -EINVAL;
+       usba_udc_data.pdata.num_ep = ARRAY_SIZE(usba_udc_ep);
+-      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));;
++      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));
+       if (data && data->vbus_pin > 0) {
+               at91_set_gpio_input(data->vbus_pin, 0);
+diff --git a/arch/arm/mach-at91/at91sam9g45_devices.c b/arch/arm/mach-at91/at91sam9g45_devices.c
+index 1e8f275..5e9f8a4 100644
+--- a/arch/arm/mach-at91/at91sam9g45_devices.c
++++ b/arch/arm/mach-at91/at91sam9g45_devices.c
+@@ -256,7 +256,7 @@ void __init at91_add_device_usba(struct usba_platform_data *data)
+ {
+       usba_udc_data.pdata.vbus_pin = -EINVAL;
+       usba_udc_data.pdata.num_ep = ARRAY_SIZE(usba_udc_ep);
+-      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));;
++      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));
+       if (data && data->vbus_pin > 0) {
+               at91_set_gpio_input(data->vbus_pin, 0);
+diff --git a/arch/arm/mach-at91/at91sam9rl_devices.c b/arch/arm/mach-at91/at91sam9rl_devices.c
+index 53aaa94..c49262b 100644
+--- a/arch/arm/mach-at91/at91sam9rl_devices.c
++++ b/arch/arm/mach-at91/at91sam9rl_devices.c
+@@ -145,7 +145,7 @@ void __init at91_add_device_usba(struct usba_platform_data *data)
+        */
+       usba_udc_data.pdata.vbus_pin = -EINVAL;
+       usba_udc_data.pdata.num_ep = ARRAY_SIZE(usba_udc_ep);
+-      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));;
++      memcpy(usba_udc_data.ep, usba_udc_ep, sizeof(usba_udc_ep));
+       if (data && data->vbus_pin > 0) {
+               at91_set_gpio_input(data->vbus_pin, 0);
+diff --git a/arch/arm/mach-nuc93x/time.c b/arch/arm/mach-nuc93x/time.c
+index 2f90f9d..f9807c0 100644
+--- a/arch/arm/mach-nuc93x/time.c
++++ b/arch/arm/mach-nuc93x/time.c
+@@ -82,7 +82,7 @@ static void nuc93x_timer_setup(void)
+       timer0_load = (rate / TICKS_PER_SEC);
+       __raw_writel(timer0_load, REG_TICR0);
+-      val |= (PERIOD | COUNTEN | INTEN | PRESCALE);;
++      val |= (PERIOD | COUNTEN | INTEN | PRESCALE);
+       __raw_writel(val, REG_TCSR0);
+ }
+diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c
+index ae3b308..7f9d2252 100644
+--- a/arch/arm/mach-tegra/tegra2_clocks.c
++++ b/arch/arm/mach-tegra/tegra2_clocks.c
+@@ -293,7 +293,7 @@ static int tegra2_super_clk_set_parent(struct clk *c, struct clk *p)
+       const struct clk_mux_sel *sel;
+       int shift;
+-      val = clk_readl(c->reg + SUPER_CLK_MUX);;
++      val = clk_readl(c->reg + SUPER_CLK_MUX);
+       BUG_ON(((val & SUPER_STATE_MASK) != SUPER_STATE_RUN) &&
+               ((val & SUPER_STATE_MASK) != SUPER_STATE_IDLE));
+       shift = ((val & SUPER_STATE_MASK) == SUPER_STATE_IDLE) ?
+diff --git a/arch/arm/mach-w90x900/cpu.c b/arch/arm/mach-w90x900/cpu.c
+index 83c5632..0a235e5 100644
+--- a/arch/arm/mach-w90x900/cpu.c
++++ b/arch/arm/mach-w90x900/cpu.c
+@@ -60,7 +60,7 @@ static DEFINE_CLK(emc, 7);
+ static DEFINE_SUBCLK(rmii, 2);
+ static DEFINE_CLK(usbd, 8);
+ static DEFINE_CLK(usbh, 9);
+-static DEFINE_CLK(g2d, 10);;
++static DEFINE_CLK(g2d, 10);
+ static DEFINE_CLK(pwm, 18);
+ static DEFINE_CLK(ps2, 24);
+ static DEFINE_CLK(kpi, 25);
+diff --git a/arch/arm/plat-mxc/irq.c b/arch/arm/plat-mxc/irq.c
+index 7331f2a..d7809d0 100644
+--- a/arch/arm/plat-mxc/irq.c
++++ b/arch/arm/plat-mxc/irq.c
+@@ -53,7 +53,7 @@ int imx_irq_set_priority(unsigned char irq, unsigned char prio)
+       unsigned int mask = 0x0F << irq % 8 * 4;
+       if (irq >= MXC_INTERNAL_IRQS)
+-              return -EINVAL;;
++              return -EINVAL;
+       temp = __raw_readl(avic_base + AVIC_NIPRIORITY(irq / 8));
+       temp &= ~mask;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002297:2, b/test/corpora/lkml/cur/1382298793.002297:2,
new file mode 100644 (file)
index 0000000..48a49c4
--- /dev/null
@@ -0,0 +1,58 @@
+From: Joe Perches <joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+Subject: [PATCH 06/44] drivers/i2c: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:25 -0800
+Lines: 21
+Message-ID: <04cfa2beee1ed9656e550bb13076b9c57899542e.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: "Jean Delvare (PC drivers, core)" <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>,
+       "Ben Dooks (embedded platforms)" <ben-linux-elnMNo+KYs3YtjvyW6yDsg@public.gmane.org>,
+       linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+To: Jiri Kosina <trivial-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
+X-From: linux-i2c-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mon Nov 15 04:15:07 2010
+Return-path: <linux-i2c-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: gldi-i2c-1-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-i2c-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1PHpX8-0005AO-Oy
+       for gldi-i2c-1-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Mon, 15 Nov 2010 04:15:07 +0100
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S932641Ab0KODOb (ORCPT <rfc822;gldi-i2c-1@m.gmane.org>);
+       Sun, 14 Nov 2010 22:14:31 -0500
+Received: from mail.perches.com ([173.55.12.10]:1138 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932562Ab0KODFa (ORCPT <rfc822;linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Sun, 14 Nov 2010 22:05:30 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 39B6A2436C;
+       Sun, 14 Nov 2010 19:03:58 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+Sender: linux-i2c-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-i2c.vger.kernel.org>
+X-Mailing-List: linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+
+Signed-off-by: Joe Perches <joe-6d6DIl74uiNBDgjK7y7TUQ@public.gmane.org>
+---
+ drivers/i2c/busses/i2c-designware.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/i2c/busses/i2c-designware.c b/drivers/i2c/busses/i2c-designware.c
+index b664ed8..a93922d 100644
+--- a/drivers/i2c/busses/i2c-designware.c
++++ b/drivers/i2c/busses/i2c-designware.c
+@@ -390,7 +390,7 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
+       int tx_limit, rx_limit;
+       u32 addr = msgs[dev->msg_write_idx].addr;
+       u32 buf_len = dev->tx_buf_len;
+-      u8 *buf = dev->tx_buf;;
++      u8 *buf = dev->tx_buf;
+       intr_mask = DW_IC_INTR_DEFAULT_MASK;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002298:2, b/test/corpora/lkml/cur/1382298793.002298:2,
new file mode 100644 (file)
index 0000000..4aa3f38
--- /dev/null
@@ -0,0 +1,85 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 07/44] drivers/isdn: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:26 -0800
+Lines: 49
+Message-ID: <c7a38f65340aafb208d50fc3a781602c07aebb0c.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: Karsten Keil <isdn@linux-pingi.de>, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: netdev-owner@vger.kernel.org Mon Nov 15 04:15:06 2010
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PHpX7-0005AO-Lc
+       for linux-netdev-2@lo.gmane.org; Mon, 15 Nov 2010 04:15:06 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932642Ab0KODOJ (ORCPT <rfc822;linux-netdev-2@m.gmane.org>);
+       Sun, 14 Nov 2010 22:14:09 -0500
+Received: from mail.perches.com ([173.55.12.10]:1142 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932587Ab0KODFb (ORCPT <rfc822;netdev@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:31 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 036B82436F;
+       Sun, 14 Nov 2010 19:03:59 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/isdn/hardware/mISDN/mISDNinfineon.c |    4 ++--
+ drivers/isdn/hardware/mISDN/mISDNisar.c     |    2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/isdn/hardware/mISDN/mISDNinfineon.c b/drivers/isdn/hardware/mISDN/mISDNinfineon.c
+index e90db88..bc0529a 100644
+--- a/drivers/isdn/hardware/mISDN/mISDNinfineon.c
++++ b/drivers/isdn/hardware/mISDN/mISDNinfineon.c
+@@ -420,7 +420,7 @@ enable_hwirq(struct inf_hw *hw)
+               break;
+       case INF_NICCY:
+               val = inl((u32)hw->cfg.start + NICCY_IRQ_CTRL_REG);
+-              val |= NICCY_IRQ_ENABLE;;
++              val |= NICCY_IRQ_ENABLE;
+               outl(val, (u32)hw->cfg.start + NICCY_IRQ_CTRL_REG);
+               break;
+       case INF_SCT_1:
+@@ -924,7 +924,7 @@ setup_instance(struct inf_hw *card)
+               mISDNipac_init(&card->ipac, card);
+       if (card->ipac.isac.dch.dev.Bprotocols == 0)
+-              goto error_setup;;
++              goto error_setup;
+       err = mISDN_register_device(&card->ipac.isac.dch.dev,
+               &card->pdev->dev, card->name);
+diff --git a/drivers/isdn/hardware/mISDN/mISDNisar.c b/drivers/isdn/hardware/mISDN/mISDNisar.c
+index 38eb314..d13fa5b 100644
+--- a/drivers/isdn/hardware/mISDN/mISDNisar.c
++++ b/drivers/isdn/hardware/mISDN/mISDNisar.c
+@@ -264,7 +264,7 @@ load_firmware(struct isar_hw *isar, const u8 *buf, int size)
+                       while (noc) {
+                               val = le16_to_cpu(*sp++);
+                               *mp++ = val >> 8;
+-                              *mp++ = val & 0xFF;;
++                              *mp++ = val & 0xFF;
+                               noc--;
+                       }
+                       spin_lock_irqsave(isar->hwlock, flags);
+-- 
+1.7.3.1.g432b3.dirty
+
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002299:2, b/test/corpora/lkml/cur/1382298793.002299:2,
new file mode 100644 (file)
index 0000000..dc35b93
--- /dev/null
@@ -0,0 +1,56 @@
+From: Joe Perches <joe@perches.com>
+Subject: [PATCH 05/44] drivers/gpio: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 19:04:24 -0800
+Lines: 21
+Message-ID: <a04f2c16a94e214f0a1828c4cea95f815a816853.1289789604.git.joe@perches.com>
+References: <cover.1289789604.git.joe@perches.com>
+Cc: linux-kernel@vger.kernel.org
+To: Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:15:13 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHpX9-0005AO-Pv
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:15:08 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933055Ab0KODOp (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:14:45 -0500
+Received: from mail.perches.com ([173.55.12.10]:1136 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932550Ab0KODFa (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:05:30 -0500
+Received: from Joe-Laptop.home (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id A81642436F;
+       Sun, 14 Nov 2010 19:03:57 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1.g432b3.dirty
+In-Reply-To: <cover.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+ drivers/gpio/langwell_gpio.c |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/gpio/langwell_gpio.c b/drivers/gpio/langwell_gpio.c
+index 64db9dc..ed05ecb 100644
+--- a/drivers/gpio/langwell_gpio.c
++++ b/drivers/gpio/langwell_gpio.c
+@@ -122,7 +122,7 @@ static int lnw_gpio_direction_output(struct gpio_chip *chip,
+       lnw_gpio_set(chip, offset, value);
+       spin_lock_irqsave(&lnw->lock, flags);
+       value = readl(gpdr);
+-      value |= BIT(offset % 32);;
++      value |= BIT(offset % 32);
+       writel(value, gpdr);
+       spin_unlock_irqrestore(&lnw->lock, flags);
+       return 0;
+-- 
+1.7.3.1.g432b3.dirty
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002302:2, b/test/corpora/lkml/cur/1382298793.002302:2,
new file mode 100644 (file)
index 0000000..7ee679f
--- /dev/null
@@ -0,0 +1,85 @@
+From: "Jack Wang" <jack_wang@usish.com>
+Subject: RE: [PATCH 25/44] drivers/scsi/pm8001: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:27:32 +0800
+Lines: 33
+Message-ID: <1671200DA80140558ED0D17FB55585AD@usish.com.cn>
+References: <cover.1289789604.git.joe@perches.com> <20b352f91642ca45ad730d8eeec0bbd323d26626.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=gb2312
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: <lindar_liu@usish.com>,
+       "'James E.J. Bottomley'" <James.Bottomley@suse.de>,
+       <linux-scsi@vger.kernel.org>, <linux-kernel@vger.kernel.org>
+To: "'Joe Perches'" <joe@perches.com>,
+       "'Jiri Kosina'" <trivial@kernel.org>
+X-From: linux-scsi-owner@vger.kernel.org Mon Nov 15 04:28:10 2010
+Return-path: <linux-scsi-owner@vger.kernel.org>
+Envelope-to: lnx-linux-scsi@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-scsi-owner@vger.kernel.org>)
+       id 1PHpjl-0002I8-68
+       for lnx-linux-scsi@lo.gmane.org; Mon, 15 Nov 2010 04:28:09 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932669Ab0KOD15 convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;lnx-linux-scsi@m.gmane.org>); Sun, 14 Nov 2010 22:27:57 -0500
+Received: from sr-smtp.usish.com ([210.5.144.203]:58240 "EHLO
+       sr-smtp.usish.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S932514Ab0KOD14 convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-scsi@vger.kernel.org>); Sun, 14 Nov 2010 22:27:56 -0500
+Received: from outbound.usish.com (unknown [192.168.40.103])
+       by sr-smtp.usish.com (Postfix) with ESMTP id 782BE778067;
+       Mon, 15 Nov 2010 11:20:06 +0800 (CST)
+Received: from outbound.usish.com (outbound.usish.com [127.0.0.1])
+       by postfix.imss70 (Postfix) with ESMTP id 8E538428070;
+       Mon, 15 Nov 2010 11:27:48 +0800 (CST)
+Received: from usishe7a1977d2 (unknown [192.168.58.33])
+       (using TLSv1 with cipher RC4-MD5 (128/128 bits))
+       (No client certificate requested)
+       by outbound.usish.com (Postfix) with ESMTP id 5437142807A;
+       Mon, 15 Nov 2010 11:27:48 +0800 (CST)
+X-Mailer: Microsoft Office Outlook 11
+X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.5994
+Thread-Index: AcuEcgXhz8cjF31qQU6VXNE8zpGc0wAAnu4g
+In-Reply-To: <20b352f91642ca45ad730d8eeec0bbd323d26626.1289789605.git.joe@perches.com>
+Sender: linux-scsi-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-scsi.vger.kernel.org>
+X-Mailing-List: linux-scsi@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062321>
+
+>=20
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  drivers/scsi/pm8001/pm8001_init.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+>=20
+> diff --git a/drivers/scsi/pm8001/pm8001_init.c
+> b/drivers/scsi/pm8001/pm8001_init.c
+> index f8c86b2..be210dd 100644
+> --- a/drivers/scsi/pm8001/pm8001_init.c
+> +++ b/drivers/scsi/pm8001/pm8001_init.c
+> @@ -160,7 +160,7 @@ static void pm8001_free(struct pm8001_hba_info
+*pm8001_ha)
+>  static void pm8001_tasklet(unsigned long opaque)
+>  {
+>      struct pm8001_hba_info *pm8001_ha;
+> -    pm8001_ha =3D (struct pm8001_hba_info *)opaque;;
+> +    pm8001_ha =3D (struct pm8001_hba_info *)opaque;
+>      if (unlikely(!pm8001_ha))
+>              BUG_ON(1);
+>      PM8001_CHIP_DISP->isr(pm8001_ha);
+> --
+> 1.7.3.1.g432b3.dirty
+[Jack Wang] Acked-by: Jack Wang <jack_wang@usish.com>
+Thanks=A3=A1
+
+
+--
+To unsubscribe from this list: send the line "unsubscribe linux-scsi" i=
+n
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002309:2, b/test/corpora/lkml/cur/1382298793.002309:2,
new file mode 100644 (file)
index 0000000..64153b5
--- /dev/null
@@ -0,0 +1,93 @@
+From: Grant Likely <grant.likely@secretlab.ca>
+Subject: Re: [PATCH 28/44] drivers/spi: Remove unnecessary semicolons
+Date: Sun, 14 Nov 2010 20:57:42 -0700
+Lines: 41
+Message-ID: <20101115035742.GA19965@angua.secretlab.ca>
+References: <cover.1289789604.git.joe@perches.com>
+ <fe5e5e0efbd97eaa32530eef5ed47efdc3252dad.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Jiri Kosina <trivial@kernel.org>,
+       David Brownell <dbrownell@users.sourceforge.net>,
+       Wan ZongShun <mcuos.com@gmail.com>,
+       spi-devel-general@lists.sourceforge.net,
+       linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org
+To: Joe Perches <joe@perches.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 04:58:26 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHqD3-0006P9-7p
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 04:58:25 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1757510Ab0KOD5r (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 14 Nov 2010 22:57:47 -0500
+Received: from mail-yx0-f174.google.com ([209.85.213.174]:43928 "EHLO
+       mail-yx0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1754294Ab0KOD5q (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 14 Nov 2010 22:57:46 -0500
+Received: by yxn35 with SMTP id 35so916234yxn.19
+        for <linux-kernel@vger.kernel.org>; Sun, 14 Nov 2010 19:57:46 -0800 (PST)
+Received: by 10.91.10.20 with SMTP id n20mr7068735agi.56.1289793465914;
+        Sun, 14 Nov 2010 19:57:45 -0800 (PST)
+Received: from angua (S01060002b3d79728.cg.shawcable.net [70.72.87.49])
+        by mx.google.com with ESMTPS id d15sm3276149ana.20.2010.11.14.19.57.43
+        (version=TLSv1/SSLv3 cipher=RC4-MD5);
+        Sun, 14 Nov 2010 19:57:45 -0800 (PST)
+Received: by angua (Postfix, from userid 1000)
+       id 238853C00E5; Sun, 14 Nov 2010 20:57:42 -0700 (MST)
+Content-Disposition: inline
+In-Reply-To: <fe5e5e0efbd97eaa32530eef5ed47efdc3252dad.1289789605.git.joe@perches.com>
+User-Agent: Mutt/1.5.20 (2009-06-14)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062328>
+
+On Sun, Nov 14, 2010 at 07:04:47PM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+applied, thanks.
+
+g.
+
+> ---
+>  drivers/spi/amba-pl022.c |    2 +-
+>  drivers/spi/spi_nuc900.c |    2 +-
+>  2 files changed, 2 insertions(+), 2 deletions(-)
+> 
+> diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c
+> index fb3d1b3..2e50631 100644
+> --- a/drivers/spi/amba-pl022.c
+> +++ b/drivers/spi/amba-pl022.c
+> @@ -956,7 +956,7 @@ static int configure_dma(struct pl022 *pl022)
+>              tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+>              break;
+>      case WRITING_U32:
+> -            tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;;
+> +            tx_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+>              break;
+>      }
+>  
+> diff --git a/drivers/spi/spi_nuc900.c b/drivers/spi/spi_nuc900.c
+> index dff63be..d5be18b 100644
+> --- a/drivers/spi/spi_nuc900.c
+> +++ b/drivers/spi/spi_nuc900.c
+> @@ -449,7 +449,7 @@ err_iomap:
+>      release_mem_region(hw->res->start, resource_size(hw->res));
+>      kfree(hw->ioarea);
+>  err_pdata:
+> -    spi_master_put(hw->master);;
+> +    spi_master_put(hw->master);
+>  
+>  err_nomem:
+>      return err;
+> -- 
+> 1.7.3.1.g432b3.dirty
+> 
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002329:2, b/test/corpora/lkml/cur/1382298793.002329:2,
new file mode 100644 (file)
index 0000000..d91006a
--- /dev/null
@@ -0,0 +1,79 @@
+From: Michal Simek <monstr@monstr.eu>
+Subject: Re: [PATCH 02/44] arch/microblaze: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 07:37:39 +0100
+Lines: 32
+Message-ID: <4CE0D533.1010407@monstr.eu>
+References: <cover.1289789604.git.joe@perches.com> <5d57b90b488b4338bcdc3f0fbf5f6996842bd44d.1289789604.git.joe@perches.com>
+Reply-To: monstr@monstr.eu
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       microblaze-uclinux@itee.uq.edu.au, linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 07:38:12 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHshf-0005Kt-RF
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 07:38:12 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755918Ab0KOGhs (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 01:37:48 -0500
+Received: from mail-fx0-f46.google.com ([209.85.161.46]:39130 "EHLO
+       mail-fx0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1755187Ab0KOGhp (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 01:37:45 -0500
+Received: by fxm6 with SMTP id 6so1494962fxm.19
+        for <linux-kernel@vger.kernel.org>; Sun, 14 Nov 2010 22:37:43 -0800 (PST)
+Received: by 10.223.70.131 with SMTP id d3mr4100646faj.73.1289803062970;
+        Sun, 14 Nov 2010 22:37:42 -0800 (PST)
+Received: from monstr.eu ([178.23.216.97])
+        by mx.google.com with ESMTPS id l14sm735429fan.33.2010.11.14.22.37.40
+        (version=SSLv3 cipher=RC4-MD5);
+        Sun, 14 Nov 2010 22:37:41 -0800 (PST)
+User-Agent: Thunderbird 2.0.0.22 (X11/20090625)
+In-Reply-To: <5d57b90b488b4338bcdc3f0fbf5f6996842bd44d.1289789604.git.joe@perches.com>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062348>
+
+Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  arch/microblaze/lib/memmove.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+
+Applied.
+
+Thanks,
+Michal
+
+> 
+> diff --git a/arch/microblaze/lib/memmove.c b/arch/microblaze/lib/memmove.c
+> index 123e361..810fd68 100644
+> --- a/arch/microblaze/lib/memmove.c
+> +++ b/arch/microblaze/lib/memmove.c
+> @@ -182,7 +182,7 @@ void *memmove(void *v_dst, const void *v_src, __kernel_size_t c)
+>                      for (; c >= 4; c -= 4) {
+>                              value = *--i_src;
+>                              *--i_dst = buf_hold | ((value & 0xFF000000)>> 24);
+> -                            buf_hold = (value & 0xFFFFFF) << 8;;
+> +                            buf_hold = (value & 0xFFFFFF) << 8;
+>                      }
+>  #endif
+>                      /* Realign the source */
+
+
+-- 
+Michal Simek, Ing. (M.Eng)
+w: www.monstr.eu p: +42-0-721842854
+Maintainer of Linux kernel 2.6 Microblaze Linux - http://www.monstr.eu/fdt/
+Microblaze U-BOOT custodian
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002340:2, b/test/corpora/lkml/cur/1382298793.002340:2,
new file mode 100644 (file)
index 0000000..cee8581
--- /dev/null
@@ -0,0 +1,71 @@
+From: Sjur BRENDELAND <sjur.brandeland@stericsson.com>
+Subject: RE: [PATCH 39/44] include/net/caif/cfctrl.h: Remove unnecessary
+ semicolons
+Date: Mon, 15 Nov 2010 08:12:02 +0100
+Lines: 7
+Message-ID: <81C3A93C17462B4BBD7E272753C105791945C0C9AA@EXDCVYMBSTM005.EQ1STM.local>
+References: <cover.1289789604.git.joe@perches.com>
+ <35914cfea1bd0ab3963e632d02b1fdd52a9d2bc8.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 8BIT
+Cc: "David S. Miller" <davem@davemloft.net>,
+       "netdev@vger.kernel.org" <netdev@vger.kernel.org>,
+       "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
+To: Joe Perches <joe@perches.com>, Jiri Kosina <trivial@kernel.org>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 08:12:47 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PHtF8-0003Ia-E9
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 08:12:46 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1757561Ab0KOHMn (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 02:12:43 -0500
+Received: from eu1sys200aog110.obsmtp.com ([207.126.144.129]:34651 "EHLO
+       eu1sys200aog110.obsmtp.com" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S1755276Ab0KOHMk convert rfc822-to-8bit
+       (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 02:12:40 -0500
+Received: from source ([138.198.100.35]) (using TLSv1) by eu1sys200aob110.postini.com ([207.126.147.11]) with SMTP
+       ID DSNKTODdXnF0LEMxKFzys6wWldAszZ/h5aGS@postini.com; Mon, 15 Nov 2010 07:12:40 UTC
+Received: from zeta.dmz-ap.st.com (ns6.st.com [138.198.234.13])
+       by beta.dmz-ap.st.com (STMicroelectronics) with ESMTP id B201FFC;
+       Mon, 15 Nov 2010 07:12:06 +0000 (GMT)
+Received: from relay1.stm.gmessaging.net (unknown [10.230.100.17])
+       by zeta.dmz-ap.st.com (STMicroelectronics) with ESMTP id C1093569;
+       Mon, 15 Nov 2010 07:12:05 +0000 (GMT)
+Received: from exdcvycastm022.EQ1STM.local (alteon-source-exch [10.230.100.61])
+       (using TLSv1 with cipher RC4-MD5 (128/128 bits))
+       (Client CN "exdcvycastm022", Issuer "exdcvycastm022" (not verified))
+       by relay1.stm.gmessaging.net (Postfix) with ESMTPS id 2017C24C080;
+       Mon, 15 Nov 2010 08:12:01 +0100 (CET)
+Received: from EXDCVYMBSTM005.EQ1STM.local ([10.230.100.3]) by
+ exdcvycastm022.EQ1STM.local ([10.230.100.30]) with mapi; Mon, 15 Nov 2010
+ 08:12:04 +0100
+Thread-Topic: [PATCH 39/44] include/net/caif/cfctrl.h: Remove unnecessary
+ semicolons
+Thread-Index: AcuEcgmvXg9N7FIzS1KHGoGQuVf68gAIhhTQ
+In-Reply-To: <35914cfea1bd0ab3963e632d02b1fdd52a9d2bc8.1289789605.git.joe@perches.com>
+Accept-Language: en-US
+Content-Language: en-US
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+acceptlanguage: en-US
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062359>
+
+> 
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+
+Looks good to me.
+Acked-by: Sjur Braendeland <sjur.brandeland@stericsson.com>
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002400:2, b/test/corpora/lkml/cur/1382298793.002400:2,
new file mode 100644 (file)
index 0000000..a2eab51
--- /dev/null
@@ -0,0 +1,102 @@
+From: Mel Gorman <mel@csn.ul.ie>
+Subject: Re: [PATCH 40/44] mm/hugetlb.c: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 09:52:44 +0000
+Lines: 44
+Message-ID: <20101115095244.GI27362@csn.ul.ie>
+References: <cover.1289789604.git.joe@perches.com> <59705f848d35b12ace640f92afcffea02cee0976.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15
+Cc: Jiri Kosina <trivial@kernel.org>, linux-mm@kvack.org,
+       linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: owner-linux-mm@kvack.org Mon Nov 15 10:53:02 2010
+Return-path: <owner-linux-mm@kvack.org>
+Envelope-to: glkm-linux-mm-2@m.gmane.org
+Received: from kanga.kvack.org ([205.233.56.17])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <owner-linux-mm@kvack.org>)
+       id 1PHvkD-0001DG-RE
+       for glkm-linux-mm-2@m.gmane.org; Mon, 15 Nov 2010 10:53:02 +0100
+Received: by kanga.kvack.org (Postfix)
+       id BA7DD8D0050; Mon, 15 Nov 2010 04:53:00 -0500 (EST)
+Delivered-To: linux-mm-outgoing@kvack.org
+Received: by kanga.kvack.org (Postfix, from userid 40)
+       id B3E4F8D0017; Mon, 15 Nov 2010 04:53:00 -0500 (EST)
+X-Original-To: int-list-linux-mm@kvack.org
+Delivered-To: int-list-linux-mm@kvack.org
+Received: by kanga.kvack.org (Postfix, from userid 63042)
+       id 985338D0050; Mon, 15 Nov 2010 04:53:00 -0500 (EST)
+X-Original-To: linux-mm@kvack.org
+Delivered-To: linux-mm@kvack.org
+Received: from mail144.messagelabs.com (mail144.messagelabs.com [216.82.254.51])
+       by kanga.kvack.org (Postfix) with ESMTP id 3FA8F8D0017
+       for <linux-mm@kvack.org>; Mon, 15 Nov 2010 04:53:00 -0500 (EST)
+X-VirusChecked: Checked
+X-Env-Sender: mel@csn.ul.ie
+X-Msg-Ref: server-6.tower-144.messagelabs.com!1289814777!96428158!1
+X-StarScan-Version: 6.2.9; banners=-,-,-
+X-Originating-IP: [193.1.99.77]
+X-SpamReason: No, hits=0.5 required=7.0 tests=BODY_RANDOM_LONG
+Received: (qmail 13284 invoked from network); 15 Nov 2010 09:52:59 -0000
+Received: from gir.skynet.ie (HELO gir.skynet.ie) (193.1.99.77)
+  by server-6.tower-144.messagelabs.com with DHE-RSA-AES256-SHA encrypted SMTP; 15 Nov 2010 09:52:59 -0000
+Received: from skynet.skynet.ie (skynet.skynet.ie [193.1.99.74])
+       by gir.skynet.ie (Postfix) with ESMTP id E3A5E1244B;
+       Mon, 15 Nov 2010 09:52:44 +0000 (GMT)
+Received: by skynet.skynet.ie (Postfix, from userid 2391)
+       id D7AF750911; Mon, 15 Nov 2010 09:52:44 +0000 (GMT)
+Content-Disposition: inline
+In-Reply-To: <59705f848d35b12ace640f92afcffea02cee0976.1289789605.git.joe@perches.com>
+User-Agent: Mutt/1.5.17+20080114 (2008-01-14)
+X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.2
+Sender: owner-linux-mm@kvack.org
+Precedence: bulk
+X-Loop: owner-majordomo@kvack.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062420>
+
+On Sun, Nov 14, 2010 at 07:04:59PM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  mm/hugetlb.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+
+Acked-by: Mel Gorman <mel@csn.ul.ie>
+
+> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
+> index c4a3558..8875242 100644
+> --- a/mm/hugetlb.c
+> +++ b/mm/hugetlb.c
+> @@ -540,7 +540,7 @@ static struct page *dequeue_huge_page_vma(struct hstate *h,
+>  
+>      /* If reserves cannot be used, ensure enough pages are in the pool */
+>      if (avoid_reserve && h->free_huge_pages - h->resv_huge_pages == 0)
+> -            goto err;;
+> +            goto err;
+>  
+>      for_each_zone_zonelist_nodemask(zone, z, zonelist,
+>                                              MAX_NR_ZONES - 1, nodemask) {
+> -- 
+> 1.7.3.1.g432b3.dirty
+> 
+> --
+> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
+> the body of a message to majordomo@vger.kernel.org
+> More majordomo info at  http://vger.kernel.org/majordomo-info.html
+> Please read the FAQ at  http://www.tux.org/lkml/
+> 
+
+-- 
+Mel Gorman
+Part-time Phd Student                          Linux Technology Center
+University of Limerick                         IBM Dublin Software Lab
+
+--
+To unsubscribe, send a message with 'unsubscribe linux-mm' in
+the body to majordomo@kvack.org.  For more info on Linux MM,
+see: http://www.linux-mm.org/ .
+Fight unfair telecom policy in Canada: sign http://dissolvethecrtc.ca/
+Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002432:2, b/test/corpora/lkml/cur/1382298793.002432:2,
new file mode 100644 (file)
index 0000000..f8b72b6
--- /dev/null
@@ -0,0 +1,85 @@
+From: Liam Girdwood <lrg@slimlogic.co.uk>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 11:09:20 +0000
+Lines: 14
+Message-ID: <1289819360.3377.15.camel@odin>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Takashi@alsa-project.org,
+       Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 12:09:44 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PHwwS-0002x6-6X
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 12:09:44 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id D0E09103835; Mon, 15 Nov 2010 12:09:43 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: *
+X-Spam-Status: No, score=1.0 required=5.0 tests=PRX_BODY_40 autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 0AE19103845;
+       Mon, 15 Nov 2010 12:09:35 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id EA398103845; Mon, 15 Nov 2010 12:09:33 +0100 (CET)
+Received: from mail-wy0-f179.google.com (mail-wy0-f179.google.com
+       [74.125.82.179])
+       by alsa0.perex.cz (Postfix) with ESMTP id 8FD12103849
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 12:09:26 +0100 (CET)
+Received: by mail-wy0-f179.google.com with SMTP id 36so3482678wyg.38
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 03:09:26 -0800 (PST)
+Received: by 10.216.64.139 with SMTP id c11mr5392190wed.81.1289819366153;
+       Mon, 15 Nov 2010 03:09:26 -0800 (PST)
+Received: from [192.168.1.6] (host81-136-218-57.in-addr.btopenworld.com
+       [81.136.218.57])
+       by mx.google.com with ESMTPS id 7sm3626925wet.24.2010.11.15.03.09.21
+       (version=SSLv3 cipher=RC4-MD5); Mon, 15 Nov 2010 03:09:24 -0800 (PST)
+In-Reply-To: <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+X-Mailer: Evolution 2.30.3 
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062453>
+
+On Sun, 2010-11-14 at 19:05 -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  sound/soc/codecs/wm8904.c  |    2 +-
+>  sound/soc/codecs/wm8940.c  |    1 -
+>  sound/soc/codecs/wm8993.c  |    2 +-
+>  sound/soc/codecs/wm_hubs.c |    2 +-
+>  4 files changed, 3 insertions(+), 4 deletions(-)
+
+Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
+-- 
+Freelance Developer, SlimLogic Ltd
+ASoC and Voltage Regulator Maintainer.
+http://www.slimlogic.co.uk
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002468:2, b/test/corpora/lkml/cur/1382298793.002468:2,
new file mode 100644 (file)
index 0000000..e06d389
--- /dev/null
@@ -0,0 +1,75 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 13:49:39 +0000
+Lines: 5
+Message-ID: <20101115134939.GC12986@rakim.wolfsonmicro.main>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 14:49:49 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PHzRN-0003qZ-C2
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 14:49:49 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id D24A2103851; Mon, 15 Nov 2010 14:49:48 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 53C91103853;
+       Mon, 15 Nov 2010 14:49:44 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 15C62103853; Mon, 15 Nov 2010 14:49:42 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 8DE08103851
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 14:49:41 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id E613E788028; 
+       Mon, 15 Nov 2010 13:49:40 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PHzRD-0004Lh-OM; Mon, 15 Nov 2010 13:49:39 +0000
+Content-Disposition: inline
+In-Reply-To: <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+X-Cookie: I like your SNOOPY POSTER!!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062489>
+
+On Sun, Nov 14, 2010 at 07:05:03PM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+This doesn't apply against current -next, could you please regenerate
+against that?
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002543:2, b/test/corpora/lkml/cur/1382298793.002543:2,
new file mode 100644 (file)
index 0000000..aebfe25
--- /dev/null
@@ -0,0 +1,132 @@
+From: "Rose, Gregory V" <gregory.v.rose@intel.com>
+Subject: Re: [PATCH 14/44] drivers/net/ixgbe: Remove
+       unnecessary semicolons
+Date: Mon, 15 Nov 2010 08:24:22 -0800
+Lines: 48
+Message-ID: <43F901BD926A4E43B106BF17856F0755013080DEFF@orsmsx508.amr.corp.intel.com>
+References: <cover.1289789604.git.joe@perches.com>
+       <7d2c334daa75c5221946a17d45c9de1901cf06e7.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: "e1000-devel@lists.sourceforge.net" <e1000-devel@lists.sourceforge.net>,
+       "Allan, Bruce W" <bruce.w.allan@intel.com>, "Brandeburg,
+       Jesse" <jesse.brandeburg@intel.com>,
+       "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
+       "Ronciak, John" <john.ronciak@intel.com>, "Kirsher,
+       Jeffrey T" <jeffrey.t.kirsher@intel.com>,
+       "netdev@vger.kernel.org" <netdev@vger.kernel.org>
+To: Joe Perches <joe@perches.com>, Jiri Kosina <trivial@kernel.org>
+X-From: e1000-devel-bounces@lists.sourceforge.net Mon Nov 15 17:25:50 2010
+Return-path: <e1000-devel-bounces@lists.sourceforge.net>
+Envelope-to: glded-e1000-devel@m.gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI1sL-0003p5-2h
+       for glded-e1000-devel@m.gmane.org; Mon, 15 Nov 2010 17:25:49 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI1sG-0006Jq-KT; Mon, 15 Nov 2010 16:25:44 +0000
+Received: from sog-mx-4.v43.ch3.sourceforge.com ([172.29.43.194]
+       helo=mx.sourceforge.net)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <gregory.v.rose@intel.com>) id 1PI1sF-0006Jk-IV
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 16:25:43 +0000
+X-ACL-Warn: 
+Received: from mga09.intel.com ([134.134.136.24])
+       by sog-mx-4.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PI1sA-0001hP-Vk
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 16:25:43 +0000
+Received: from orsmga001.jf.intel.com ([10.7.209.18])
+       by orsmga102.jf.intel.com with ESMTP; 15 Nov 2010 08:25:33 -0800
+X-ExtLoop1: 1
+X-IronPort-AV: E=Sophos;i="4.59,200,1288594800"; d="scan'208";a="677619042"
+Received: from orsmsx604.amr.corp.intel.com ([10.22.226.87])
+       by orsmga001.jf.intel.com with ESMTP; 15 Nov 2010 08:25:33 -0800
+Received: from orsmsx606.amr.corp.intel.com (10.22.226.128) by
+       orsmsx604.amr.corp.intel.com (10.22.226.87) with Microsoft SMTP Server
+       (TLS) id 8.2.254.0; Mon, 15 Nov 2010 08:24:25 -0800
+Received: from orsmsx508.amr.corp.intel.com ([10.22.226.46]) by
+       orsmsx606.amr.corp.intel.com ([10.22.226.128]) with mapi;
+       Mon, 15 Nov 2010 08:24:24 -0800
+Thread-Topic: [PATCH 14/44] drivers/net/ixgbe: Remove unnecessary semicolons
+Thread-Index: AcuEcftvdxmC6VgnRT2RlEslHutcHgAb4Qcg
+In-Reply-To: <7d2c334daa75c5221946a17d45c9de1901cf06e7.1289789604.git.joe@perches.com>
+Accept-Language: en-US
+Content-Language: en-US
+X-MS-Has-Attach: 
+X-MS-TNEF-Correlator: 
+acceptlanguage: en-US
+X-Spam-Score: -0.0 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay
+       domain
+X-Headers-End: 1PI1sA-0001hP-Vk
+X-BeenThere: e1000-devel@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "e100/e1000\(e\)/ixgb/igb/ixgbe development and discussion"
+       <e1000-devel.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>, 
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=e1000-devel>
+List-Post: <mailto:e1000-devel@lists.sourceforge.net>
+List-Help: <mailto:e1000-devel-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>,
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: e1000-devel-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062564>
+
+> -----Original Message-----
+> From: Joe Perches [mailto:joe@perches.com]
+> Sent: Sunday, November 14, 2010 7:05 PM
+> To: Jiri Kosina
+> Cc: Kirsher, Jeffrey T; Brandeburg, Jesse; Allan, Bruce W; Wyborny,
+> Carolyn; Skidmore, Donald C; Rose, Gregory V; Waskiewicz Jr, Peter P;
+> Duyck, Alexander H; Ronciak, John; e1000-devel@lists.sourceforge.net;
+> netdev@vger.kernel.org; linux-kernel@vger.kernel.org
+> Subject: [PATCH 14/44] drivers/net/ixgbe: Remove unnecessary semicolons
+> 
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  drivers/net/ixgbe/ixgbe_sriov.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+> diff --git a/drivers/net/ixgbe/ixgbe_sriov.c
+> b/drivers/net/ixgbe/ixgbe_sriov.c
+> index 5428153..93f40bc 100644
+> --- a/drivers/net/ixgbe/ixgbe_sriov.c
+> +++ b/drivers/net/ixgbe/ixgbe_sriov.c
+> @@ -68,7 +68,7 @@ static int ixgbe_set_vf_multicasts(struct ixgbe_adapter
+> *adapter,
+>       * addresses
+>       */
+>      for (i = 0; i < entries; i++) {
+> -            vfinfo->vf_mc_hashes[i] = hash_list[i];;
+> +            vfinfo->vf_mc_hashes[i] = hash_list[i];
+>      }
+> 
+>      for (i = 0; i < vfinfo->num_vf_mc_hashes; i++) {
+> --
+> 1.7.3.1.g432b3.dirty
+
+Acked By: Greg Rose <Gregory.v.rose@intel.com>
+
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+_______________________________________________
+E1000-devel mailing list
+E1000-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/e1000-devel
+To learn more about Intel&#174; Ethernet, visit http://communities.intel.com/community/wired
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002557:2, b/test/corpora/lkml/cur/1382298793.002557:2,
new file mode 100644 (file)
index 0000000..723f9d6
--- /dev/null
@@ -0,0 +1,109 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 09:09:17 -0800
+Lines: 63
+Message-ID: <1289840957.16461.138.camel@Joe-Laptop>
+References: <cover.1289789604.git.joe@perches.com>
+        <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+        <20101115134939.GC12986@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 18:09:51 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI2Yr-0005ly-Pc
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 18:09:46 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932795Ab0KORJV (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 12:09:21 -0500
+Received: from mail.perches.com ([173.55.12.10]:1293 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932132Ab0KORJU (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 12:09:20 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 93B872436B;
+       Mon, 15 Nov 2010 09:07:32 -0800 (PST)
+In-Reply-To: <20101115134939.GC12986@rakim.wolfsonmicro.main>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062578>
+
+Signed-off-by: Joe Perches <joe@perches.com>
+---
+V2: against -next
+
+ sound/soc/codecs/wm8904.c  |    2 +-
+ sound/soc/codecs/wm8940.c  |    1 -
+ sound/soc/codecs/wm8993.c  |    2 +-
+ sound/soc/codecs/wm_hubs.c |    2 +-
+ 4 files changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
+index be90399..5e57bd2 100644
+--- a/sound/soc/codecs/wm8904.c
++++ b/sound/soc/codecs/wm8904.c
+@@ -1591,7 +1591,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
+                      - wm8904->fs);
+       for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+               cur_val = abs((wm8904->sysclk_rate /
+-                             clk_sys_rates[i].ratio) - wm8904->fs);;
++                             clk_sys_rates[i].ratio) - wm8904->fs);
+               if (cur_val < best_val) {
+                       best = i;
+                       best_val = cur_val;
+diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c
+index c2def1b..caed084 100644
+--- a/sound/soc/codecs/wm8940.c
++++ b/sound/soc/codecs/wm8940.c
+@@ -736,7 +736,6 @@ static int wm8940_probe(struct snd_soc_codec *codec)
+               return ret;
+       return ret;
+-;
+ }
+ static int wm8940_remove(struct snd_soc_codec *codec)
+diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
+index bcc54be..991d90c 100644
+--- a/sound/soc/codecs/wm8993.c
++++ b/sound/soc/codecs/wm8993.c
+@@ -1227,7 +1227,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream,
+                      - wm8993->fs);
+       for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) {
+               cur_val = abs((wm8993->sysclk_rate /
+-                             clk_sys_rates[i].ratio) - wm8993->fs);;
++                             clk_sys_rates[i].ratio) - wm8993->fs);
+               if (cur_val < best_val) {
+                       best = i;
+                       best_val = cur_val;
+diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
+index 8aff0ef..422c7fb 100644
+--- a/sound/soc/codecs/wm_hubs.c
++++ b/sound/soc/codecs/wm_hubs.c
+@@ -119,7 +119,7 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
+       switch (hubs->dcs_readback_mode) {
+       case 0:
+               reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
+-                      & WM8993_DCS_INTEG_CHAN_0_MASK;;
++                      & WM8993_DCS_INTEG_CHAN_0_MASK;
+               reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
+                       & WM8993_DCS_INTEG_CHAN_1_MASK;
+               break;
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002575:2, b/test/corpora/lkml/cur/1382298793.002575:2,
new file mode 100644 (file)
index 0000000..981c1c9
--- /dev/null
@@ -0,0 +1,79 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 17:30:31 +0000
+Lines: 7
+Message-ID: <20101115173031.GI12986@rakim.wolfsonmicro.main>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+       <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 18:30:46 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PI2t9-0000lr-Er
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 18:30:43 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 5B6F8244F9; Mon, 15 Nov 2010 18:30:41 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id AE3D8244FB;
+       Mon, 15 Nov 2010 18:30:36 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id EF7B7244FC; Mon, 15 Nov 2010 18:30:34 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 8B247244F9
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 18:30:34 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id C748C788028; 
+       Mon, 15 Nov 2010 17:30:32 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PI2sx-0000C1-Jf; Mon, 15 Nov 2010 17:30:31 +0000
+Content-Disposition: inline
+In-Reply-To: <1289840957.16461.138.camel@Joe-Laptop>
+X-Cookie: I like your SNOOPY POSTER!!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062597>
+
+On Mon, Nov 15, 2010 at 09:09:17AM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied, thanks.  
+
+Please try to use changelog formats consistent with the code you're
+modifying.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002576:2, b/test/corpora/lkml/cur/1382298793.002576:2,
new file mode 100644 (file)
index 0000000..6963356
--- /dev/null
@@ -0,0 +1,63 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 09:34:04 -0800
+Lines: 15
+Message-ID: <1289842444.16461.140.camel@Joe-Laptop>
+References: <cover.1289789604.git.joe@perches.com>
+        <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+        <20101115134939.GC12986@rakim.wolfsonmicro.main>
+        <1289840957.16461.138.camel@Joe-Laptop>
+        <20101115173031.GI12986@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 18:34:20 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI2wd-0002wj-Br
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 18:34:19 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933091Ab0KOReI (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 12:34:08 -0500
+Received: from mail.perches.com ([173.55.12.10]:1304 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S933013Ab0KOReH (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 12:34:07 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 9F71A24368;
+       Mon, 15 Nov 2010 09:32:18 -0800 (PST)
+In-Reply-To: <20101115173031.GI12986@rakim.wolfsonmicro.main>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062599>
+
+On Mon, 2010-11-15 at 17:30 +0000, Mark Brown wrote:
+> On Mon, Nov 15, 2010 at 09:09:17AM -0800, Joe Perches wrote:
+> > Signed-off-by: Joe Perches <joe@perches.com>
+> Applied, thanks.
+> Please try to use changelog formats consistent with the code you're
+> modifying.
+
+I think it's more important to use consistent changelogs
+for a patch series.
+
+If you want your own subsystem changelog consistency, I
+think you should change the format to what you desire.
+
+cheers, Joe
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002639:2, b/test/corpora/lkml/cur/1382298793.002639:2,
new file mode 100644 (file)
index 0000000..450509d
--- /dev/null
@@ -0,0 +1,90 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 18:27:08 +0000
+Lines: 16
+Message-ID: <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+       <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 19:27:21 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PI3lw-0007AT-Pq
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 19:27:20 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 35136103873; Mon, 15 Nov 2010 19:27:17 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id B554724525;
+       Mon, 15 Nov 2010 19:27:11 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id D599A24527; Mon, 15 Nov 2010 19:27:10 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 5719224414
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 19:27:10 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id C97D2788028; 
+       Mon, 15 Nov 2010 18:27:09 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PI3lk-00053D-RZ; Mon, 15 Nov 2010 18:27:08 +0000
+Content-Disposition: inline
+In-Reply-To: <1289842444.16461.140.camel@Joe-Laptop>
+X-Cookie: I like your SNOOPY POSTER!!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062664>
+
+On Mon, Nov 15, 2010 at 09:34:04AM -0800, Joe Perches wrote:
+> On Mon, 2010-11-15 at 17:30 +0000, Mark Brown wrote:
+
+> > Please try to use changelog formats consistent with the code you're
+> > modifying.
+
+> I think it's more important to use consistent changelogs
+> for a patch series.
+
+...since...?
+
+> If you want your own subsystem changelog consistency, I
+> think you should change the format to what you desire.
+
+Which is what I'm doing but it's annoying to have to constantly hand
+edit changelogs.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002642:2, b/test/corpora/lkml/cur/1382298793.002642:2,
new file mode 100644 (file)
index 0000000..1bd4e32
--- /dev/null
@@ -0,0 +1,66 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 10:30:29 -0800
+Lines: 16
+Message-ID: <1289845830.16461.149.camel@Joe-Laptop>
+References: <cover.1289789604.git.joe@perches.com>
+        <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+        <20101115134939.GC12986@rakim.wolfsonmicro.main>
+        <1289840957.16461.138.camel@Joe-Laptop>
+        <20101115173031.GI12986@rakim.wolfsonmicro.main>
+        <1289842444.16461.140.camel@Joe-Laptop>
+        <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 19:31:55 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI3qN-0000mO-6u
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 19:31:55 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1758106Ab0KOSad (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 13:30:33 -0500
+Received: from mail.perches.com ([173.55.12.10]:1314 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1756843Ab0KOSac (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 13:30:32 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 2BC7724368;
+       Mon, 15 Nov 2010 10:28:43 -0800 (PST)
+In-Reply-To: <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062667>
+
+On Mon, 2010-11-15 at 18:27 +0000, Mark Brown wrote:
+> On Mon, Nov 15, 2010 at 09:34:04AM -0800, Joe Perches wrote:
+> > On Mon, 2010-11-15 at 17:30 +0000, Mark Brown wrote:
+> > > Please try to use changelog formats consistent with the code you're
+> > > modifying.
+> > I think it's more important to use consistent changelogs
+> > for a patch series.
+> ...since...?
+
+1995...
+
+Since there isn't a consistent standard for subsystems
+changelogs and automating scripts for the desires of
+individual subsystem maintainers is not feasible.
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002661:2, b/test/corpora/lkml/cur/1382298793.002661:2,
new file mode 100644 (file)
index 0000000..36f8752
--- /dev/null
@@ -0,0 +1,49 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 39/44] include/net/caif/cfctrl.h: Remove unnecessary
+ semicolons
+Date: Mon, 15 Nov 2010 11:07:32 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110732.27814339.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <35914cfea1bd0ab3963e632d02b1fdd52a9d2bc8.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, sjur.brandeland@stericsson.com,
+       netdev@vger.kernel.org, linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:07:42 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4Oy-0002G6-Lp
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:07:41 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933101Ab0KOTHJ (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:09 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51782
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S932513Ab0KOTHI (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:08 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 71B5924C088;
+       Mon, 15 Nov 2010 11:07:33 -0800 (PST)
+In-Reply-To: <35914cfea1bd0ab3963e632d02b1fdd52a9d2bc8.1289789605.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062686>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:58 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002662:2, b/test/corpora/lkml/cur/1382298793.002662:2,
new file mode 100644 (file)
index 0000000..2fbb7e7
--- /dev/null
@@ -0,0 +1,49 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 41/44] net/ipv6/mcast.c: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:07:39 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110739.191407854.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <1f3e1f7e454f3c62b66fc5f3e1e1ed90d62b7fb0.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, kuznet@ms2.inr.ac.ru, pekkas@netcore.fi,
+       jmorris@namei.org, yoshfuji@linux-ipv6.org, kaber@trash.net,
+       netdev@vger.kernel.org, linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:07:42 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4Oz-0002G6-63
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:07:41 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933351Ab0KOTHS (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:18 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51792
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933109Ab0KOTHP (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:15 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 53FFA24C088;
+       Mon, 15 Nov 2010 11:07:40 -0800 (PST)
+In-Reply-To: <1f3e1f7e454f3c62b66fc5f3e1e1ed90d62b7fb0.1289789605.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062687>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:05:00 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002663:2, b/test/corpora/lkml/cur/1382298793.002663:2,
new file mode 100644 (file)
index 0000000..c888922
--- /dev/null
@@ -0,0 +1,50 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 15/44] drivers/net/vxge: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:07:55 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110755.98889745.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <e86e79a18106cc38715136bfb2e880b38f5ac764.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, ramkrishna.vepa@exar.com,
+       sivakumar.subramani@exar.com, sreenivasa.honnur@exar.com,
+       jon.mason@exar.com, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:07:42 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4P0-0002G6-7S
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:07:42 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933487Ab0KOTHe (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:34 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51805
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933109Ab0KOTHb (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:31 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 185A124C08A;
+       Mon, 15 Nov 2010 11:07:56 -0800 (PST)
+In-Reply-To: <e86e79a18106cc38715136bfb2e880b38f5ac764.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062688>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:34 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Not applicable to net-next-2.6
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002664:2, b/test/corpora/lkml/cur/1382298793.002664:2,
new file mode 100644 (file)
index 0000000..ec584cb
--- /dev/null
@@ -0,0 +1,49 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 38/44] include/linux/if_macvlan.h: Remove unnecessary
+ semicolons
+Date: Mon, 15 Nov 2010 11:07:46 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110746.241931394.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <186ca914f887b2354ea3178696edc81cacbb28c6.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, kaber@trash.net, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:07:42 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4Oz-0002G6-MR
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:07:42 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933465Ab0KOTHZ (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:25 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51798
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933109Ab0KOTHV (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:21 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 665FB24C08A;
+       Mon, 15 Nov 2010 11:07:46 -0800 (PST)
+In-Reply-To: <186ca914f887b2354ea3178696edc81cacbb28c6.1289789605.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062689>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:57 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002665:2, b/test/corpora/lkml/cur/1382298793.002665:2,
new file mode 100644 (file)
index 0000000..7af81e8
--- /dev/null
@@ -0,0 +1,98 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 19:07:38 +0000
+Lines: 22
+Message-ID: <20101115190738.GF3338@sirena.org.uk>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+       <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 20:07:53 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PI4PA-0002MQ-AO
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 20:07:52 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 78DB02453D; Mon, 15 Nov 2010 20:07:51 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 07D122453A;
+       Mon, 15 Nov 2010 20:07:47 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 4AB082453B; Mon, 15 Nov 2010 20:07:45 +0100 (CET)
+Received: from cassiel.sirena.org.uk (cassiel.sirena.org.uk [80.68.93.111])
+       by alsa0.perex.cz (Postfix) with ESMTP id D864524538
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 20:07:44 +0100 (CET)
+Received: from broonie by cassiel.sirena.org.uk with local (Exim 4.69)
+       (envelope-from <broonie@sirena.org.uk>)
+       id 1PI4Ow-0007qS-V5; Mon, 15 Nov 2010 19:07:38 +0000
+Content-Disposition: inline
+In-Reply-To: <1289845830.16461.149.camel@Joe-Laptop>
+X-Cookie: Who messed with my anti-paranoia shot?
+User-Agent: Mutt/1.5.18 (2008-05-17)
+X-SA-Exim-Connect-IP: <locally generated>
+X-SA-Exim-Mail-From: broonie@sirena.org.uk
+X-SA-Exim-Scanned: No (on cassiel.sirena.org.uk);
+       SAEximRunCond expanded to false
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062690>
+
+On Mon, Nov 15, 2010 at 10:30:29AM -0800, Joe Perches wrote:
+> On Mon, 2010-11-15 at 18:27 +0000, Mark Brown wrote:
+> > On Mon, Nov 15, 2010 at 09:34:04AM -0800, Joe Perches wrote:
+
+> > > I think it's more important to use consistent changelogs
+> > > for a patch series.
+
+> > ...since...?
+
+> 1995...
+
+That's not really a reason.  It seems that...
+
+> Since there isn't a consistent standard for subsystems
+> changelogs and automating scripts for the desires of
+> individual subsystem maintainers is not feasible.
+
+...you mean that you wish to do this since it makes your life as a
+script author easier.  I'd suggest using pattern matching to look up the
+rules for generating the prefixes (it's pretty much entirely prefixes)
+in the same way you're handling figuring out who to mail - that'd
+probably cover it in an automatable fashion.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002666:2, b/test/corpora/lkml/cur/1382298793.002666:2,
new file mode 100644 (file)
index 0000000..8907c25
--- /dev/null
@@ -0,0 +1,87 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 13/44] drivers/net/e1000e: Remove
+       unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:27 -0800 (PST)
+Lines: 19
+Message-ID: <20101115.110827.58428696.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <e5cf92d50de7924930d660a5865c3d60d9cd9dc5.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, e1000-devel@lists.sourceforge.net,
+       bruce.w.allan@intel.com, jesse.brandeburg@intel.com,
+       linux-kernel@vger.kernel.org, gregory.v.rose@intel.com,
+       john.ronciak@intel.com, jeffrey.t.kirsher@intel.com, netdev@vger.kernel.org
+To: joe@perches.com
+X-From: e1000-devel-bounces@lists.sourceforge.net Mon Nov 15 20:08:15 2010
+Return-path: <e1000-devel-bounces@lists.sourceforge.net>
+Envelope-to: glded-e1000-devel@m.gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI4PW-0002W0-GR
+       for glded-e1000-devel@m.gmane.org; Mon, 15 Nov 2010 20:08:14 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com)
+       by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI4PV-0002fQ-W8; Mon, 15 Nov 2010 19:08:13 +0000
+Received: from sog-mx-3.v43.ch3.sourceforge.com ([172.29.43.193]
+       helo=mx.sourceforge.net)
+       by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <davem@davemloft.net>) id 1PI4PU-0002fJ-Ct
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 19:08:12 +0000
+X-ACL-Warn: 
+Received: from 74-93-104-97-washington.hfc.comcastbusiness.net ([74.93.104.97]
+       helo=sunset.davemloft.net)
+       by sog-mx-3.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PI4PQ-0006vK-9a
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 19:08:12 +0000
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 0BCD224C088;
+       Mon, 15 Nov 2010 11:08:28 -0800 (PST)
+In-Reply-To: <e5cf92d50de7924930d660a5865c3d60d9cd9dc5.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+X-Spam-Score: 0.8 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       1.0 RDNS_DYNAMIC           Delivered to internal network by host with
+       dynamic-looking rDNS
+       -0.2 AWL AWL: From: address is in the auto white-list
+X-Headers-End: 1PI4PQ-0006vK-9a
+X-BeenThere: e1000-devel@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "e100/e1000\(e\)/ixgb/igb/ixgbe development and discussion"
+       <e1000-devel.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>, 
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=e1000-devel>
+List-Post: <mailto:e1000-devel@lists.sourceforge.net>
+List-Help: <mailto:e1000-devel-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>,
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: e1000-devel-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062691>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:32 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+_______________________________________________
+E1000-devel mailing list
+E1000-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/e1000-devel
+To learn more about Intel&#174; Ethernet, visit http://communities.intel.com/community/wired
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002667:2, b/test/corpora/lkml/cur/1382298793.002667:2,
new file mode 100644 (file)
index 0000000..c85cbd5
--- /dev/null
@@ -0,0 +1,87 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 14/44] drivers/net/ixgbe: Remove
+       unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:21 -0800 (PST)
+Lines: 19
+Message-ID: <20101115.110821.13743893.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <7d2c334daa75c5221946a17d45c9de1901cf06e7.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, e1000-devel@lists.sourceforge.net,
+       bruce.w.allan@intel.com, jesse.brandeburg@intel.com,
+       linux-kernel@vger.kernel.org, gregory.v.rose@intel.com,
+       john.ronciak@intel.com, jeffrey.t.kirsher@intel.com, netdev@vger.kernel.org
+To: joe@perches.com
+X-From: e1000-devel-bounces@lists.sourceforge.net Mon Nov 15 20:08:15 2010
+Return-path: <e1000-devel-bounces@lists.sourceforge.net>
+Envelope-to: glded-e1000-devel@m.gmane.org
+Received: from lists.sourceforge.net ([216.34.181.88])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI4PW-0002Vz-9H
+       for glded-e1000-devel@m.gmane.org; Mon, 15 Nov 2010 20:08:14 +0100
+Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <e1000-devel-bounces@lists.sourceforge.net>)
+       id 1PI4PQ-0008VG-9t; Mon, 15 Nov 2010 19:08:08 +0000
+Received: from sog-mx-1.v43.ch3.sourceforge.com ([172.29.43.191]
+       helo=mx.sourceforge.net)
+       by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69)
+       (envelope-from <davem@davemloft.net>) id 1PI4PO-0008V9-I9
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 19:08:06 +0000
+X-ACL-Warn: 
+Received: from 74-93-104-97-washington.hfc.comcastbusiness.net ([74.93.104.97]
+       helo=sunset.davemloft.net)
+       by sog-mx-1.v43.ch3.sourceforge.com with esmtp (Exim 4.69)
+       id 1PI4PK-0004Ab-D0
+       for e1000-devel@lists.sourceforge.net; Mon, 15 Nov 2010 19:08:06 +0000
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id E0AE124C08A;
+       Mon, 15 Nov 2010 11:08:21 -0800 (PST)
+In-Reply-To: <7d2c334daa75c5221946a17d45c9de1901cf06e7.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+X-Spam-Score: 0.7 (/)
+X-Spam-Report: Spam Filtering performed by mx.sourceforge.net.
+       See http://spamassassin.org/tag/ for more details.
+       1.0 RDNS_DYNAMIC           Delivered to internal network by host with
+       dynamic-looking rDNS
+       -0.3 AWL AWL: From: address is in the auto white-list
+X-Headers-End: 1PI4PK-0004Ab-D0
+X-BeenThere: e1000-devel@lists.sourceforge.net
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "e100/e1000\(e\)/ixgb/igb/ixgbe development and discussion"
+       <e1000-devel.lists.sourceforge.net>
+List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>, 
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=unsubscribe>
+List-Archive: <http://sourceforge.net/mailarchive/forum.php?forum_name=e1000-devel>
+List-Post: <mailto:e1000-devel@lists.sourceforge.net>
+List-Help: <mailto:e1000-devel-request@lists.sourceforge.net?subject=help>
+List-Subscribe: <https://lists.sourceforge.net/lists/listinfo/e1000-devel>,
+       <mailto:e1000-devel-request@lists.sourceforge.net?subject=subscribe>
+Errors-To: e1000-devel-bounces@lists.sourceforge.net
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062692>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:33 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+------------------------------------------------------------------------------
+Centralized Desktop Delivery: Dell and VMware Reference Architecture
+Simplifying enterprise desktop deployment and management using
+Dell EqualLogic storage and VMware View: A highly scalable, end-to-end
+client virtualization framework. Read more!
+http://p.sf.net/sfu/dell-eql-dev2dev
+_______________________________________________
+E1000-devel mailing list
+E1000-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/e1000-devel
+To learn more about Intel&#174; Ethernet, visit http://communities.intel.com/community/wired
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002668:2, b/test/corpora/lkml/cur/1382298793.002668:2,
new file mode 100644 (file)
index 0000000..a453744
--- /dev/null
@@ -0,0 +1,50 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 21/44] drivers/s390/net: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:10 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110810.241442235.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <ea09773876fb36a52a9a750110b381d20767ac12.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, ursula.braun@de.ibm.com,
+       blaschka@linux.vnet.ibm.com, linux390@de.ibm.com,
+       schwidefsky@de.ibm.com, heiko.carstens@de.ibm.com,
+       linux-s390@vger.kernel.org, linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:09:11 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4QQ-00030Y-IU
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:09:10 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933525Ab0KOTHt (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:49 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51810
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933071Ab0KOTHp (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:45 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 79DC124C088;
+       Mon, 15 Nov 2010 11:08:10 -0800 (PST)
+In-Reply-To: <ea09773876fb36a52a9a750110b381d20767ac12.1289789605.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062693>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:40 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+I'll let the s390 folks take this one.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002669:2, b/test/corpora/lkml/cur/1382298793.002669:2,
new file mode 100644 (file)
index 0000000..f595d0a
--- /dev/null
@@ -0,0 +1,48 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 18/44] drivers/net/cnic.c: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:15 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110815.52192986.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <950331e47b16c2ad28d73502f30f5a0f017b5493.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:09:11 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4QR-00030Y-3C
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:09:11 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933539Ab0KOTHy (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:07:54 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51817
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933054Ab0KOTHv (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:07:51 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 0297924C08A;
+       Mon, 15 Nov 2010 11:08:16 -0800 (PST)
+In-Reply-To: <950331e47b16c2ad28d73502f30f5a0f017b5493.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062694>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:37 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002670:2, b/test/corpora/lkml/cur/1382298793.002670:2,
new file mode 100644 (file)
index 0000000..964ba85
--- /dev/null
@@ -0,0 +1,52 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 07/44] drivers/isdn: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:40 -0800 (PST)
+Lines: 11
+Message-ID: <20101115.110840.45901337.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <c7a38f65340aafb208d50fc3a781602c07aebb0c.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, isdn@linux-pingi.de, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: netdev-owner@vger.kernel.org Mon Nov 15 20:09:14 2010
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PI4QU-00030Y-5P
+       for linux-netdev-2@lo.gmane.org; Mon, 15 Nov 2010 20:09:14 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933584Ab0KOTIR (ORCPT <rfc822;linux-netdev-2@m.gmane.org>);
+       Mon, 15 Nov 2010 14:08:17 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51842
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933494Ab0KOTIP (ORCPT
+       <rfc822;netdev@vger.kernel.org>); Mon, 15 Nov 2010 14:08:15 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 88D8924C088;
+       Mon, 15 Nov 2010 11:08:40 -0800 (PST)
+In-Reply-To: <c7a38f65340aafb208d50fc3a781602c07aebb0c.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062695>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:26 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002671:2, b/test/corpora/lkml/cur/1382298793.002671:2,
new file mode 100644 (file)
index 0000000..1fe0cc5
--- /dev/null
@@ -0,0 +1,48 @@
+From: David Miller <davem@davemloft.net>
+Subject: Re: [PATCH 12/44] drivers/net/bnx2x: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:08:34 -0800 (PST)
+Lines: 6
+Message-ID: <20101115.110834.91340564.davem@davemloft.net>
+References: <cover.1289789604.git.joe@perches.com>
+       <2bfaf1f1fe5d503a8a386a433b5187997819d771.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: Text/Plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+Cc: trivial@kernel.org, eilong@broadcom.com, netdev@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: joe@perches.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:09:14 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4QT-00030Y-LK
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:09:13 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933575Ab0KOTIN (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:08:13 -0500
+Received: from 74-93-104-97-Washington.hfc.comcastbusiness.net ([74.93.104.97]:51836
+       "EHLO sunset.davemloft.net" rhost-flags-OK-OK-OK-OK)
+       by vger.kernel.org with ESMTP id S933449Ab0KOTIJ (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:08:09 -0500
+Received: from localhost (localhost [127.0.0.1])
+       by sunset.davemloft.net (Postfix) with ESMTP id 8FAC824C08A;
+       Mon, 15 Nov 2010 11:08:34 -0800 (PST)
+In-Reply-To: <2bfaf1f1fe5d503a8a386a433b5187997819d771.1289789604.git.joe@perches.com>
+X-Mailer: Mew version 6.3 on Emacs 23.1 / Mule 6.0 (HANACHIRUSATO)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062696>
+
+From: Joe Perches <joe@perches.com>
+Date: Sun, 14 Nov 2010 19:04:31 -0800
+
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002679:2, b/test/corpora/lkml/cur/1382298793.002679:2,
new file mode 100644 (file)
index 0000000..8e1234b
--- /dev/null
@@ -0,0 +1,61 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:14:18 -0800
+Lines: 9
+Message-ID: <1289848458.16461.150.camel@Joe-Laptop>
+References: <cover.1289789604.git.joe@perches.com>
+        <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+        <20101115134939.GC12986@rakim.wolfsonmicro.main>
+        <1289840957.16461.138.camel@Joe-Laptop>
+        <20101115173031.GI12986@rakim.wolfsonmicro.main>
+        <1289842444.16461.140.camel@Joe-Laptop>
+        <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+        <1289845830.16461.149.camel@Joe-Laptop>
+        <20101115190738.GF3338@sirena.org.uk>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:14:49 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI4Vp-0006HI-NH
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:14:46 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933566Ab0KOTOW (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:14:22 -0500
+Received: from mail.perches.com ([173.55.12.10]:1319 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S933505Ab0KOTOV (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:14:21 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 4087C24368;
+       Mon, 15 Nov 2010 11:12:31 -0800 (PST)
+In-Reply-To: <20101115190738.GF3338@sirena.org.uk>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062704>
+
+On Mon, 2010-11-15 at 19:07 +0000, Mark Brown wrote:
+> I'd suggest using pattern matching to look up the
+> rules for generating the prefixes (it's pretty much entirely prefixes)
+> in the same way you're handling figuring out who to mail - that'd
+> probably cover it in an automatable fashion.
+
+Publish a tool that works and I'll use it.
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002688:2, b/test/corpora/lkml/cur/1382298793.002688:2,
new file mode 100644 (file)
index 0000000..7d573a0
--- /dev/null
@@ -0,0 +1,94 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Mon, 15 Nov 2010 19:34:07 +0000
+Lines: 16
+Message-ID: <20101115193407.GK12986@rakim.wolfsonmicro.main>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+       <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 15 20:34:23 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PI4oo-0008LE-En
+       for glad-alsa-devel-2@m.gmane.org; Mon, 15 Nov 2010 20:34:22 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 9B9BF24551; Mon, 15 Nov 2010 20:34:21 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 7F6F424547;
+       Mon, 15 Nov 2010 20:34:16 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 4D4F724548; Mon, 15 Nov 2010 20:34:14 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 9C6502453F
+       for <alsa-devel@alsa-project.org>; Mon, 15 Nov 2010 20:34:09 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id 992AA788028; 
+       Mon, 15 Nov 2010 19:34:08 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PI4oZ-0005Qk-Q4; Mon, 15 Nov 2010 19:34:07 +0000
+Content-Disposition: inline
+In-Reply-To: <1289848458.16461.150.camel@Joe-Laptop>
+X-Cookie: I like your SNOOPY POSTER!!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062713>
+
+On Mon, Nov 15, 2010 at 11:14:18AM -0800, Joe Perches wrote:
+> On Mon, 2010-11-15 at 19:07 +0000, Mark Brown wrote:
+
+> > I'd suggest using pattern matching to look up the
+> > rules for generating the prefixes (it's pretty much entirely prefixes)
+> > in the same way you're handling figuring out who to mail - that'd
+> > probably cover it in an automatable fashion.
+
+> Publish a tool that works and I'll use it.
+
+It appears your scripts are already hooked into get_maintainers.pl which
+would seem the obvious place to do this?  Sadly I don't do perl, though
+it looks like you're doing pretty much all the work on that anyway.
+
+The main thing here is to avoid your patches sticking out - as well as
+the hassle applying them stuff like this is also a red flag on review.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.002699:2, b/test/corpora/lkml/cur/1382298793.002699:2,
new file mode 100644 (file)
index 0000000..3fdfaf1
--- /dev/null
@@ -0,0 +1,106 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary semicolons
+Date: Mon, 15 Nov 2010 11:52:53 -0800
+Lines: 52
+Message-ID: <1289850773.16461.166.camel@Joe-Laptop>
+References: <cover.1289789604.git.joe@perches.com>
+        <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+        <20101115134939.GC12986@rakim.wolfsonmicro.main>
+        <1289840957.16461.138.camel@Joe-Laptop>
+        <20101115173031.GI12986@rakim.wolfsonmicro.main>
+        <1289842444.16461.140.camel@Joe-Laptop>
+        <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+        <1289845830.16461.149.camel@Joe-Laptop>
+        <20101115190738.GF3338@sirena.org.uk>
+        <1289848458.16461.150.camel@Joe-Laptop>
+        <20101115193407.GK12986@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>,
+       Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Nov 15 20:53:21 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PI57A-0001v9-CG
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 15 Nov 2010 20:53:20 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932871Ab0KOTw5 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 15 Nov 2010 14:52:57 -0500
+Received: from mail.perches.com ([173.55.12.10]:1328 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1758222Ab0KOTw4 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 15 Nov 2010 14:52:56 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id CE13524368;
+       Mon, 15 Nov 2010 11:51:05 -0800 (PST)
+In-Reply-To: <20101115193407.GK12986@rakim.wolfsonmicro.main>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1062724>
+
+On Mon, 2010-11-15 at 19:34 +0000, Mark Brown wrote:
+> On Mon, Nov 15, 2010 at 11:14:18AM -0800, Joe Perches wrote:
+> > On Mon, 2010-11-15 at 19:07 +0000, Mark Brown wrote:
+> > > I'd suggest using pattern matching to look up the
+> > > rules for generating the prefixes (it's pretty much entirely prefixes)
+> > > in the same way you're handling figuring out who to mail - that'd
+> > > probably cover it in an automatable fashion.
+> > Publish a tool that works and I'll use it.
+> It appears your scripts are already hooked into get_maintainers.pl which
+> would seem the obvious place to do this?  Sadly I don't do perl, though
+> it looks like you're doing pretty much all the work on that anyway.
+
+Sadly, no it's not the right place.
+
+That script just generates cc email addresses
+for pre-formatted commit patches.
+
+It'd have to be a script that modifies the git commit subject line
+to the taste of the subsystem maintainer.
+
+Right now, I use a commit script that's something like:
+
+#!/bin/bash
+echo "$1: Remove unnecessary semicolons" > msg
+echo >> msg
+#cat >> msg <<EOF
+#Unnecessary semicolons should not exist.
+#EOF
+git commit -s -F msg $1
+
+There could be a modification to $1 (path)
+or some such.
+
+Maybe a script like
+./scripts/convert_commit_subject_to_subsystem_maintainer_taste
+or something.
+
+Care to write one in sh/bash/perl/python/c/ocaml/c#?
+
+As far as I know, the only subsystem pedants^H^H^H^H^Hople
+that care much about the commit subject style are
+arch/x86 and sound.
+
+I can understand the desire of these subsystem maintainers
+to have a consistent style.  I think though that requiring
+a subject header style without providing more than a
+general guideline is a but much.
+
+I'd use any other automated tool you want to provide.
+
+cheers, Joe
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003013:2, b/test/corpora/lkml/cur/1382298793.003013:2,
new file mode 100644 (file)
index 0000000..945ce7c
--- /dev/null
@@ -0,0 +1,128 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Tue, 16 Nov 2010 10:49:22 +0000
+Lines: 50
+Message-ID: <20101116104921.GL12986@rakim.wolfsonmicro.main>
+References: <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Tue Nov 16 11:49:29 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIJ6O-0003Hl-Gx
+       for glad-alsa-devel-2@m.gmane.org; Tue, 16 Nov 2010 11:49:28 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id C3B89243EB; Tue, 16 Nov 2010 11:49:27 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 5204E243EB;
+       Tue, 16 Nov 2010 11:49:26 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id D1E2E243EC; Tue, 16 Nov 2010 11:49:24 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 16268243EA
+       for <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 11:49:24 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id 4AC4E3B438A; 
+       Tue, 16 Nov 2010 10:49:23 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PIJ6I-0001Kz-Cf; Tue, 16 Nov 2010 10:49:22 +0000
+Content-Disposition: inline
+In-Reply-To: <1289850773.16461.166.camel@Joe-Laptop>
+X-Cookie: I like your SNOOPY POSTER!!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063040>
+
+On Mon, Nov 15, 2010 at 11:52:53AM -0800, Joe Perches wrote:
+> On Mon, 2010-11-15 at 19:34 +0000, Mark Brown wrote:
+
+> > It appears your scripts are already hooked into get_maintainers.pl which
+> > would seem the obvious place to do this?  Sadly I don't do perl, though
+> > it looks like you're doing pretty much all the work on that anyway.
+
+> Sadly, no it's not the right place.
+
+To query MAINTAINERS?  I'd assume that's where you'd want to put that
+stuff?
+
+> There could be a modification to $1 (path)
+> or some such.
+> 
+> Maybe a script like
+> ./scripts/convert_commit_subject_to_subsystem_maintainer_taste
+> or something.
+
+> Care to write one in sh/bash/perl/python/c/ocaml/c#?
+
+Like I say I'd expect this to be a get_maintainers based lookup to dump
+some data out?
+
+> As far as I know, the only subsystem pedants^H^H^H^H^Hople
+> that care much about the commit subject style are
+> arch/x86 and sound.
+
+If you look at the kernel you'll see quite a few subsystems which have
+some sort of standard practice which they do try to enforce, you
+shouldn't take silence as people being happy here - it's taken me some
+considerable time to get round to mentioning this, for example, and I
+might not have bothered if the patch had applied first time around.
+Like working against -next it's one of these things that would make your
+patches easier to deal with.
+
+> I can understand the desire of these subsystem maintainers
+> to have a consistent style.  I think though that requiring
+> a subject header style without providing more than a
+> general guideline is a but much.
+
+The general guideline I tend to go with is that if what you're doing
+looks odd for the code you're submitting against for some reason you're
+doing something wrong unless you understand why you're doing something
+different and there's a good reason.
+
+> I'd use any other automated tool you want to provide.
+
+Like I say, I'd expect the lookup from the database to be handled by
+get_maintainers.pl.  Having a separate database would seem odd.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003145:2, b/test/corpora/lkml/cur/1382298793.003145:2,
new file mode 100644 (file)
index 0000000..7418171
--- /dev/null
@@ -0,0 +1,89 @@
+From: Joe Perches <joe@perches.com>
+Subject: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 06:51:17 -0800
+Lines: 36
+Message-ID: <1289919077.28741.50.camel@Joe-Laptop>
+References: <20101115134939.GC12986@rakim.wolfsonmicro.main>
+        <1289840957.16461.138.camel@Joe-Laptop>
+        <20101115173031.GI12986@rakim.wolfsonmicro.main>
+        <1289842444.16461.140.camel@Joe-Laptop>
+        <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+        <1289845830.16461.149.camel@Joe-Laptop>
+        <20101115190738.GF3338@sirena.org.uk>
+        <1289848458.16461.150.camel@Joe-Laptop>
+        <20101115193407.GK12986@rakim.wolfsonmicro.main>
+        <1289850773.16461.166.camel@Joe-Laptop>
+        <20101116104921.GL12986@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       Florian Mickler <florian@mickler.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Nov 16 15:51:41 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIMsn-0003tR-Ee
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 16 Nov 2010 15:51:41 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1758690Ab0KPOvV (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 09:51:21 -0500
+Received: from mail.perches.com ([173.55.12.10]:1433 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1755918Ab0KPOvU (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 09:51:20 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 38D5C24368;
+       Tue, 16 Nov 2010 06:49:10 -0800 (PST)
+In-Reply-To: <20101116104921.GL12986@rakim.wolfsonmicro.main>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063175>
+
+On Tue, 2010-11-16 at 10:49 +0000, Mark Brown wrote:
+> On Mon, Nov 15, 2010 at 11:52:53AM -0800, Joe Perches wrote:
+> > On Mon, 2010-11-15 at 19:34 +0000, Mark Brown wrote:
+> > > It appears your scripts are already hooked into get_maintainers.pl which
+> > > would seem the obvious place to do this?  Sadly I don't do perl, though
+> > > it looks like you're doing pretty much all the work on that anyway.
+> > Sadly, no it's not the right place.
+> To query MAINTAINERS?  I'd assume that's where you'd want to put that
+> stuff?
+
+I trimmed cc's and added Andrew Morton and Florian Mickler.
+First thread link for them: http://lkml.org/lkml/2010/11/15/262
+
+I use get_maintainer to find email addresses with
+"git send-email --cc-cmd=" but sure it could be extended
+to find some other new information in the MAINTAINERS file.
+
+Anyway, I think that get_maintainers isn't the proper tool
+to rewrite commit subject lines, though it could certainly
+do the lookup of a key in the MAINTAINERS file.
+
+Maybe add a new MAINTAINERS section line something like:
+       "C:     CommitSubjectGrammarStyle"
+where CommitSubjectGrammarStyle is something more
+information rich than "style 1", "style 2".
+
+Perhaps you'll propose a grammar to convert path to header
+and go through and add these "C:" style entries to the
+sections you maintain.
+
+Also, what would you expect the output to be when a single
+patch modified files from 2 subsystems that use different
+styles?
+
+cheers, Joe
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003148:2, b/test/corpora/lkml/cur/1382298793.003148:2,
new file mode 100644 (file)
index 0000000..bd56e87
--- /dev/null
@@ -0,0 +1,92 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 15:04:51 +0000
+Lines: 15
+Message-ID: <20101116150451.GA26239@rakim.wolfsonmicro.main>
+References: <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Florian Mickler <florian@mickler.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       Jiri Kosina <trivial@kernel.org>, alsa-devel@alsa-project.org,
+       linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Tue Nov 16 16:05:00 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIN5f-0004ME-Rp
+       for glad-alsa-devel-2@m.gmane.org; Tue, 16 Nov 2010 16:04:59 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id E869810380D; Tue, 16 Nov 2010 16:04:57 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 159A924457;
+       Tue, 16 Nov 2010 16:04:57 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 43E4E24458; Tue, 16 Nov 2010 16:04:55 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id CED8D243CD
+       for <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 16:04:54 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id A251D3B445E; 
+       Tue, 16 Nov 2010 15:04:52 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PIN5X-0007x2-Qm; Tue, 16 Nov 2010 15:04:51 +0000
+Content-Disposition: inline
+In-Reply-To: <1289919077.28741.50.camel@Joe-Laptop>
+X-Cookie: Onward through the fog.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063178>
+
+On Tue, Nov 16, 2010 at 06:51:17AM -0800, Joe Perches wrote:
+
+> Maybe add a new MAINTAINERS section line something like:
+>      "C:     CommitSubjectGrammarStyle"
+> where CommitSubjectGrammarStyle is something more
+> information rich than "style 1", "style 2".
+
+Something printfish would seem reasonable?
+
+> Also, what would you expect the output to be when a single
+> patch modified files from 2 subsystems that use different
+> styles?
+
+The traditional thing is "ThingX/ThingY: blah" but as with anything else
+you need to be sensible.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003216:2, b/test/corpora/lkml/cur/1382298793.003216:2,
new file mode 100644 (file)
index 0000000..655f450
--- /dev/null
@@ -0,0 +1,113 @@
+From: Florian Mickler <florian@mickler.org>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 18:37:07 +0100
+Lines: 59
+Message-ID: <20101116183707.179964dd@schatten.dmk.lab>
+References: <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Nov 16 18:37:57 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIPTh-0007Ey-5Q
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 16 Nov 2010 18:37:57 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756054Ab0KPRhi (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 12:37:38 -0500
+Received: from ist.d-labs.de ([213.239.218.44]:44291 "EHLO mx01.d-labs.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754101Ab0KPRhh (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 12:37:37 -0500
+Received: from schatten.dmk.lab (f053209081.adsl.alicedsl.de [78.53.209.81])
+       by mx01.d-labs.de (Postfix) with ESMTPSA id 1EB9E7FFD4;
+       Tue, 16 Nov 2010 18:36:55 +0100 (CET)
+In-Reply-To: <1289919077.28741.50.camel@Joe-Laptop>
+X-Mailer: Claws Mail 3.7.6cvs31 (GTK+ 2.20.1; x86_64-unknown-linux-gnu)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063246>
+
+On Tue, 16 Nov 2010 06:51:17 -0800
+Joe Perches <joe@perches.com> wrote:
+
+> On Tue, 2010-11-16 at 10:49 +0000, Mark Brown wrote:
+> > On Mon, Nov 15, 2010 at 11:52:53AM -0800, Joe Perches wrote:
+> > > On Mon, 2010-11-15 at 19:34 +0000, Mark Brown wrote:
+> > > > It appears your scripts are already hooked into get_maintainers.pl which
+> > > > would seem the obvious place to do this?  Sadly I don't do perl, though
+> > > > it looks like you're doing pretty much all the work on that anyway.
+> > > Sadly, no it's not the right place.
+> > To query MAINTAINERS?  I'd assume that's where you'd want to put that
+> > stuff?
+> 
+> I trimmed cc's and added Andrew Morton and Florian Mickler.
+> First thread link for them: http://lkml.org/lkml/2010/11/15/262
+> 
+> I use get_maintainer to find email addresses with
+> "git send-email --cc-cmd=" but sure it could be extended
+> to find some other new information in the MAINTAINERS file.
+> 
+> Anyway, I think that get_maintainers isn't the proper tool
+> to rewrite commit subject lines, though it could certainly
+> do the lookup of a key in the MAINTAINERS file.
+> 
+> Maybe add a new MAINTAINERS section line something like:
+>      "C:     CommitSubjectGrammarStyle"
+> where CommitSubjectGrammarStyle is something more
+> information rich than "style 1", "style 2".
+> 
+> Perhaps you'll propose a grammar to convert path to header
+> and go through and add these "C:" style entries to the
+> sections you maintain.
+> 
+> Also, what would you expect the output to be when a single
+> patch modified files from 2 subsystems that use different
+> styles?
+> 
+> cheers, Joe
+> 
+
+My first reaction to this is, it's silly. Certainly a
+subsystem-maintainer is capable of hacking something together that
+suits his needs or may just use a good editor to get the job done.
+After all, he might want to edit the commit message anyway. Also he has
+to have his act together for all non-conforming submitters anyway,
+because shurely, telling people to re-edit their patches subject line
+is not what one would consider "welcoming to newbies",  or whatever it
+is kernel subsystem maintainers have to be nowadays *g*... 
+
+On second thought, if that facility existed, i think nobody would mind
+it either. So, why not. I don't see a way to specify what to do with
+cross-subsystem patches though. 
+
+(MAINTAINERS seems to be the logical place to put this
+information.)
+
+Regards,
+Flo
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003231:2, b/test/corpora/lkml/cur/1382298793.003231:2,
new file mode 100644 (file)
index 0000000..0f4a123
--- /dev/null
@@ -0,0 +1,82 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 18:12:27 +0000
+Lines: 26
+Message-ID: <20101116181226.GB26239@rakim.wolfsonmicro.main>
+References: <1289842444.16461.140.camel@Joe-Laptop>
+ <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+ <1289845830.16461.149.camel@Joe-Laptop>
+ <20101115190738.GF3338@sirena.org.uk>
+ <1289848458.16461.150.camel@Joe-Laptop>
+ <20101115193407.GK12986@rakim.wolfsonmicro.main>
+ <1289850773.16461.166.camel@Joe-Laptop>
+ <20101116104921.GL12986@rakim.wolfsonmicro.main>
+ <1289919077.28741.50.camel@Joe-Laptop>
+ <20101116183707.179964dd@schatten.dmk.lab>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Joe Perches <joe@perches.com>, Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Florian Mickler <florian@mickler.org>
+X-From: linux-kernel-owner@vger.kernel.org Tue Nov 16 19:12:51 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIQ1Q-0006KJ-Uw
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 16 Nov 2010 19:12:49 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1756467Ab0KPSMa (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 13:12:30 -0500
+Received: from opensource.wolfsonmicro.com ([80.75.67.52]:42692 "EHLO
+       opensource2.wolfsonmicro.com" rhost-flags-OK-OK-OK-FAIL)
+       by vger.kernel.org with ESMTP id S1755686Ab0KPSM3 (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 13:12:29 -0500
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id 3E8603B44D5;
+       Tue, 16 Nov 2010 18:12:28 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PIQ15-0000oJ-5s; Tue, 16 Nov 2010 18:12:27 +0000
+Content-Disposition: inline
+In-Reply-To: <20101116183707.179964dd@schatten.dmk.lab>
+X-Cookie: Onward through the fog.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063261>
+
+On Tue, Nov 16, 2010 at 06:37:07PM +0100, Florian Mickler wrote:
+
+> My first reaction to this is, it's silly. Certainly a
+> subsystem-maintainer is capable of hacking something together that
+> suits his needs or may just use a good editor to get the job done.
+> After all, he might want to edit the commit message anyway. Also he has
+> to have his act together for all non-conforming submitters anyway,
+> because shurely, telling people to re-edit their patches subject line
+> is not what one would consider "welcoming to newbies",  or whatever it
+> is kernel subsystem maintainers have to be nowadays *g*... 
+
+So, my general policy on this is that I tend to push back on patches
+which don't just work with the toolset (subject lines are just one part
+of it) to a variable extent depending on who's submitting and what
+they're submitting.  One of the factors is that the more patches are
+coming from someone the easier I expect their patches to be to work
+with.
+
+The reason this came up is that this is one of the issues with Joe's
+patches (which are rather frequent) but he is only willing to do things
+that he can automate.
+
+> (MAINTAINERS seems to be the logical place to put this
+> information.)
+
+Indeed.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003278:2, b/test/corpora/lkml/cur/1382298793.003278:2,
new file mode 100644 (file)
index 0000000..b3b0f0c
--- /dev/null
@@ -0,0 +1,89 @@
+From: Florian Mickler <florian@mickler.org>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 20:35:22 +0100
+Lines: 37
+Message-ID: <20101116203522.65240b18@schatten.dmk.lab>
+References: <1289842444.16461.140.camel@Joe-Laptop>
+       <20101115182708.GJ12986@rakim.wolfsonmicro.main>
+       <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Joe Perches <joe@perches.com>, Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Tue Nov 16 20:36:24 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIRKK-0004cK-An
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 16 Nov 2010 20:36:24 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S932105Ab0KPTfy (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 14:35:54 -0500
+Received: from ist.d-labs.de ([213.239.218.44]:46199 "EHLO mx01.d-labs.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1756324Ab0KPTfw (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 14:35:52 -0500
+Received: from schatten.dmk.lab (f053209081.adsl.alicedsl.de [78.53.209.81])
+       by mx01.d-labs.de (Postfix) with ESMTPSA id 8CEAA7FAFE;
+       Tue, 16 Nov 2010 20:35:09 +0100 (CET)
+In-Reply-To: <20101116181226.GB26239@rakim.wolfsonmicro.main>
+X-Mailer: Claws Mail 3.7.6cvs31 (GTK+ 2.20.1; x86_64-unknown-linux-gnu)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063309>
+
+On Tue, 16 Nov 2010 18:12:27 +0000
+Mark Brown <broonie@opensource.wolfsonmicro.com> wrote:
+
+> On Tue, Nov 16, 2010 at 06:37:07PM +0100, Florian Mickler wrote:
+> 
+> > My first reaction to this is, it's silly. Certainly a
+> > subsystem-maintainer is capable of hacking something together that
+> > suits his needs or may just use a good editor to get the job done.
+> > After all, he might want to edit the commit message anyway. Also he has
+> > to have his act together for all non-conforming submitters anyway,
+> > because shurely, telling people to re-edit their patches subject line
+> > is not what one would consider "welcoming to newbies",  or whatever it
+> > is kernel subsystem maintainers have to be nowadays *g*... 
+> 
+> So, my general policy on this is that I tend to push back on patches
+> which don't just work with the toolset (subject lines are just one part
+> of it) to a variable extent depending on who's submitting and what
+> they're submitting.  One of the factors is that the more patches are
+> coming from someone the easier I expect their patches to be to work
+> with.
+> 
+> The reason this came up is that this is one of the issues with Joe's
+> patches (which are rather frequent) but he is only willing to do things
+> that he can automate.
+
+Hehe, I know that I wouldn't want to hand edit every autogenerated patch
+people throw at me... What about just dropping everything before the
+last "]" or ":" and putting an autogenerated prefix before it in a
+pre-commit hook on your side?  
+
+That should work most of the time... don't know... maybe other
+subsystem maintainers have some more suggestions on reducing the
+workload... this could even be an interesting topic for some summit...
+
+Regards,
+Flo
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003295:2, b/test/corpora/lkml/cur/1382298793.003295:2,
new file mode 100644 (file)
index 0000000..802eca7
--- /dev/null
@@ -0,0 +1,92 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 19:55:31 +0000
+Lines: 16
+Message-ID: <20101116195530.GA7523@rakim.wolfsonmicro.main>
+References: <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>,
+       Jiri Kosina <trivial@kernel.org>, alsa-devel@alsa-project.org,
+       linux-kernel@vger.kernel.org
+To: Florian Mickler <florian@mickler.org>
+X-From: alsa-devel-bounces@alsa-project.org Tue Nov 16 20:55:48 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIRd2-0000Zr-GV
+       for glad-alsa-devel-2@m.gmane.org; Tue, 16 Nov 2010 20:55:44 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 02BEA2417E; Tue, 16 Nov 2010 20:55:36 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 8AFD424159;
+       Tue, 16 Nov 2010 20:55:35 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 32ADC24179; Tue, 16 Nov 2010 20:55:34 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 913CA24158
+       for <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 20:55:33 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id C4CDE3B4538; 
+       Tue, 16 Nov 2010 19:55:31 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PIRcp-0004nx-60; Tue, 16 Nov 2010 19:55:31 +0000
+Content-Disposition: inline
+In-Reply-To: <20101116203522.65240b18@schatten.dmk.lab>
+X-Cookie: Killing turkeys causes winter.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063326>
+
+On Tue, Nov 16, 2010 at 08:35:22PM +0100, Florian Mickler wrote:
+
+> Hehe, I know that I wouldn't want to hand edit every autogenerated patch
+> people throw at me... What about just dropping everything before the
+> last "]" or ":" and putting an autogenerated prefix before it in a
+> pre-commit hook on your side?  
+
+> That should work most of the time... don't know... maybe other
+
+It's the most of the time bit that worries me, I'm generally reluctant
+to script things like this when the scripts aren't very widely used and
+it's a pain to get hooks distributed over all my systems and working for
+all the things I need to apply patches for.
+
+From my point of view my current approach is actually working pretty
+well with most submitters, even people doing similar janitorial stuff.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003316:2, b/test/corpora/lkml/cur/1382298793.003316:2,
new file mode 100644 (file)
index 0000000..7b09d35
--- /dev/null
@@ -0,0 +1,105 @@
+From: Randy Dunlap <rdunlap@xenotime.net>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 12:21:02 -0800
+Organization: YPO4
+Lines: 34
+Message-ID: <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+References: <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: alsa-devel-bounces@alsa-project.org Tue Nov 16 21:21:20 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIS1m-0001Kj-Rd
+       for glad-alsa-devel-2@m.gmane.org; Tue, 16 Nov 2010 21:21:18 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 2FC2024371; Tue, 16 Nov 2010 21:21:18 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id ACBEB24368;
+       Tue, 16 Nov 2010 21:21:16 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 6AFBB24369; Tue, 16 Nov 2010 21:21:15 +0100 (CET)
+Received: from xenotime.net (xenotime.net [72.52.115.56])
+       by alsa0.perex.cz (Postfix) with SMTP id 6FDFD24363
+       for <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 21:21:14 +0100 (CET)
+Received: from chimera.site ([173.50.240.230]) by xenotime.net for
+       <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 12:21:06 -0800
+In-Reply-To: <20101116195530.GA7523@rakim.wolfsonmicro.main>
+X-Mailer: Sylpheed 2.7.1 (GTK+ 2.16.6; x86_64-unknown-linux-gnu)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063347>
+
+On Tue, 16 Nov 2010 19:55:31 +0000 Mark Brown wrote:
+
+> On Tue, Nov 16, 2010 at 08:35:22PM +0100, Florian Mickler wrote:
+> 
+> > Hehe, I know that I wouldn't want to hand edit every autogenerated patch
+> > people throw at me... What about just dropping everything before the
+> > last "]" or ":" and putting an autogenerated prefix before it in a
+> > pre-commit hook on your side?  
+> 
+> > That should work most of the time... don't know... maybe other
+> 
+> It's the most of the time bit that worries me, I'm generally reluctant
+> to script things like this when the scripts aren't very widely used and
+> it's a pain to get hooks distributed over all my systems and working for
+> all the things I need to apply patches for.
+> 
+> From my point of view my current approach is actually working pretty
+> well with most submitters, even people doing similar janitorial stuff.
+
+I don't know what you asked Joe to change, but asking someone to use
+the documented canonical patch format:
+
+<quote>
+The canonical patch subject line is:
+
+    Subject: [PATCH 001/123] subsystem: summary phrase
+</quote>
+
+should be fine.  And there is no need for printf-ish templates
+for this in MAINTAINERS either.
+
+---
+~Randy
+*** Remember to use Documentation/SubmitChecklist when testing your code ***
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003334:2, b/test/corpora/lkml/cur/1382298793.003334:2,
new file mode 100644 (file)
index 0000000..9a58840
--- /dev/null
@@ -0,0 +1,106 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 12:42:36 -0800
+Lines: 51
+Message-ID: <1289940156.28741.207.camel@Joe-Laptop>
+References: <1289845830.16461.149.camel@Joe-Laptop>
+        <20101115190738.GF3338@sirena.org.uk>
+        <1289848458.16461.150.camel@Joe-Laptop>
+        <20101115193407.GK12986@rakim.wolfsonmicro.main>
+        <1289850773.16461.166.camel@Joe-Laptop>
+        <20101116104921.GL12986@rakim.wolfsonmicro.main>
+        <1289919077.28741.50.camel@Joe-Laptop>
+        <20101116183707.179964dd@schatten.dmk.lab>
+        <20101116181226.GB26239@rakim.wolfsonmicro.main>
+        <20101116203522.65240b18@schatten.dmk.lab>
+        <20101116195530.GA7523@rakim.wolfsonmicro.main>
+        <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Florian Mickler <florian@mickler.org>,
+       Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Randy Dunlap <rdunlap@xenotime.net>
+X-From: linux-kernel-owner@vger.kernel.org Tue Nov 16 21:43:01 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PISMm-00074k-9X
+       for glk-linux-kernel-3@lo.gmane.org; Tue, 16 Nov 2010 21:43:00 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1757174Ab0KPUmj (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 15:42:39 -0500
+Received: from mail.perches.com ([173.55.12.10]:1476 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1754409Ab0KPUmi (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 15:42:38 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 17CC824368;
+       Tue, 16 Nov 2010 12:40:23 -0800 (PST)
+In-Reply-To: <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063365>
+
+On Tue, 2010-11-16 at 12:21 -0800, Randy Dunlap wrote:
+> On Tue, 16 Nov 2010 19:55:31 +0000 Mark Brown wrote:
+> I don't know what you asked Joe to change, but asking someone to use
+> the documented canonical patch format:
+> <quote>
+> The canonical patch subject line is:
+>     Subject: [PATCH 001/123] subsystem: summary phrase
+> </quote>
+> should be fine.  And there is no need for printf-ish templates
+> for this in MAINTAINERS either.
+
+I've never read that before.  Learn something new etc...
+It seems path prefixes aren't good nor even commonly used.
+
+A review of kernel patch subjects:
+
+$ git log --no-merges --pretty=oneline | \
+       cut -f2- -d" " | cut -f1 -d: | sort | uniq -c | sort -rn
+
+is interesting.  Here's the head:
+   5007 x86
+   3943 Staging
+   3220 USB
+   2790 sh
+   2707 KVM
+   2624 ARM
+   2449 ALSA
+   1571 Input
+   1549 ASoC
+   1470 iwlwifi
+   1423 ACPI
+   1397 mac80211
+   1384 V4L/DVB
+   1226 sched
+   1200 Btrfs
+   1184 powerpc
+   1106 [NETFILTER]
+   1080 MIPS
+   1049 net
+   1047 ide
+   1014 drm/i915
+    993 staging
+    921 ath9k
+
+Some subsystem maintainers like upper case, some mixed, some lower.
+Some aren't consistent.  (Staging/staging)
+
+It doesn't seem a rule can be pregenerated so maybe adding these
+"C:" lines to MAINTAINERS has some value.
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003340:2, b/test/corpora/lkml/cur/1382298793.003340:2,
new file mode 100644 (file)
index 0000000..ff520cb
--- /dev/null
@@ -0,0 +1,138 @@
+From: Randy Dunlap <rdunlap@xenotime.net>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 12:46:09 -0800
+Organization: YPO4
+Lines: 64
+Message-ID: <20101116124609.382e42fb.rdunlap@xenotime.net>
+References: <1289845830.16461.149.camel@Joe-Laptop>
+       <20101115190738.GF3338@sirena.org.uk>
+       <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <1289940156.28741.207.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Andrew Morton <akpm@linux-foundation.org>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Tue Nov 16 21:46:23 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PISQ1-0000rd-2s
+       for glad-alsa-devel-2@m.gmane.org; Tue, 16 Nov 2010 21:46:21 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 5421B2438C; Tue, 16 Nov 2010 21:46:20 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: *
+X-Spam-Status: No, score=1.0 required=5.0 tests=PRX_BODY_29 autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 774FE24390;
+       Tue, 16 Nov 2010 21:46:19 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 6051924391; Tue, 16 Nov 2010 21:46:17 +0100 (CET)
+Received: from xenotime.net (xenotime.net [72.52.115.56])
+       by alsa0.perex.cz (Postfix) with SMTP id 4F17D2438C
+       for <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 21:46:15 +0100 (CET)
+Received: from chimera.site ([173.50.240.230]) by xenotime.net for
+       <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 12:46:10 -0800
+In-Reply-To: <1289940156.28741.207.camel@Joe-Laptop>
+X-Mailer: Sylpheed 2.7.1 (GTK+ 2.16.6; x86_64-unknown-linux-gnu)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063371>
+
+On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+
+> On Tue, 2010-11-16 at 12:21 -0800, Randy Dunlap wrote:
+> > On Tue, 16 Nov 2010 19:55:31 +0000 Mark Brown wrote:
+> > I don't know what you asked Joe to change, but asking someone to use
+> > the documented canonical patch format:
+> > <quote>
+> > The canonical patch subject line is:
+> >     Subject: [PATCH 001/123] subsystem: summary phrase
+> > </quote>
+> > should be fine.  And there is no need for printf-ish templates
+> > for this in MAINTAINERS either.
+> 
+> I've never read that before.  Learn something new etc...
+> It seems path prefixes aren't good nor even commonly used.
+> 
+> A review of kernel patch subjects:
+> 
+> $ git log --no-merges --pretty=oneline | \
+>      cut -f2- -d" " | cut -f1 -d: | sort | uniq -c | sort -rn
+> 
+> is interesting.  Here's the head:
+>    5007 x86
+>    3943 Staging
+>    3220 USB
+>    2790 sh
+>    2707 KVM
+>    2624 ARM
+>    2449 ALSA
+>    1571 Input
+>    1549 ASoC
+>    1470 iwlwifi
+>    1423 ACPI
+>    1397 mac80211
+>    1384 V4L/DVB
+>    1226 sched
+>    1200 Btrfs
+>    1184 powerpc
+>    1106 [NETFILTER]
+>    1080 MIPS
+>    1049 net
+>    1047 ide
+>    1014 drm/i915
+>     993 staging
+>     921 ath9k
+
+$ARCH is a commonly accepted substitute for subsystem.
+
+And yes, lots of people use <drivername> there as well.
+
+
+> Some subsystem maintainers like upper case, some mixed, some lower.
+> Some aren't consistent.  (Staging/staging)
+
+Case usually doesn't matter to most of us.
+
+> It doesn't seem a rule can be pregenerated so maybe adding these
+> "C:" lines to MAINTAINERS has some value.
+
+Hopefully it won't go that far.
+
+---
+~Randy
+*** Remember to use Documentation/SubmitChecklist when testing your code ***
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003448:2, b/test/corpora/lkml/cur/1382298793.003448:2,
new file mode 100644 (file)
index 0000000..d9ecd6a
--- /dev/null
@@ -0,0 +1,100 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 23:01:26 +0000
+Lines: 24
+Message-ID: <20101116230126.GB24623@opensource.wolfsonmicro.com>
+References: <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>
+To: Randy Dunlap <rdunlap@xenotime.net>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 00:01:43 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIUWr-0004yP-6J
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 00:01:33 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id A434B103882; Wed, 17 Nov 2010 00:01:26 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 9B9A81037FB;
+       Wed, 17 Nov 2010 00:01:23 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 47D7B1037FB; Wed, 17 Nov 2010 00:01:22 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id F24201037E3
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 00:01:20 +0100 (CET)
+Received: from finisterre.wolfsonmicro.main
+       (cpc3-sgyl4-0-0-cust125.sgyl.cable.virginmedia.com [82.41.240.126])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id F2D407881C9; 
+       Tue, 16 Nov 2010 23:01:18 +0000 (GMT)
+Received: from broonie by finisterre.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@opensource.wolfsonmicro.com>)
+       id 1PIUWk-0007m9-B8; Tue, 16 Nov 2010 23:01:26 +0000
+Content-Disposition: inline
+In-Reply-To: <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+X-Cookie: Beware of Bigfoot!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063481>
+
+On Tue, Nov 16, 2010 at 12:21:02PM -0800, Randy Dunlap wrote:
+
+> I don't know what you asked Joe to change, but asking someone to use
+> the documented canonical patch format:
+
+> <quote>
+> The canonical patch subject line is:
+
+>     Subject: [PATCH 001/123] subsystem: summary phrase
+> </quote>
+
+> should be fine.  And there is no need for printf-ish templates
+> for this in MAINTAINERS either.
+
+That's exactly what I asked him to do.  He said he's not willing to use
+anything for "subsystem" which can't be automatically generated.
+
+The formats I mentioned because some subsystems have their own things
+within this format like "subsystem: driver:" or whatever.  While it's
+probably not an issue for the sort of patch Joe generates if we do have
+a tool for this I'd expect it'll go the same way that checkpatch does
+and get used by people doing more specific work.  It'd be good to try to
+head off the friction that may cause by at least having an idea how we
+might cope with that.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003459:2, b/test/corpora/lkml/cur/1382298793.003459:2,
new file mode 100644 (file)
index 0000000..8fddae8
--- /dev/null
@@ -0,0 +1,88 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 23:22:58 +0000
+Lines: 12
+Message-ID: <20101116232258.GC24623@opensource.wolfsonmicro.com>
+References: <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <1289940156.28741.207.camel@Joe-Laptop>
+       <20101116124609.382e42fb.rdunlap@xenotime.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>
+To: Randy Dunlap <rdunlap@xenotime.net>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 00:22:58 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIUrZ-0001Km-F5
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 00:22:57 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 28F9610381A; Wed, 17 Nov 2010 00:22:55 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 1CC8D10381B;
+       Wed, 17 Nov 2010 00:22:55 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id B1B8B10381B; Wed, 17 Nov 2010 00:22:52 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id CB03810381A
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 00:22:51 +0100 (CET)
+Received: from finisterre.wolfsonmicro.main
+       (cpc3-sgyl4-0-0-cust125.sgyl.cable.virginmedia.com [82.41.240.126])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id 39A957881C9; 
+       Tue, 16 Nov 2010 23:22:51 +0000 (GMT)
+Received: from broonie by finisterre.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@opensource.wolfsonmicro.com>)
+       id 1PIUra-0001BL-NS; Tue, 16 Nov 2010 23:22:58 +0000
+Content-Disposition: inline
+In-Reply-To: <20101116124609.382e42fb.rdunlap@xenotime.net>
+X-Cookie: Beware of Bigfoot!
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063492>
+
+On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+> On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+
+> > Some subsystem maintainers like upper case, some mixed, some lower.
+> > Some aren't consistent.  (Staging/staging)
+
+> Case usually doesn't matter to most of us.
+
+Given that we're working in case sensitive languages here it's probably
+safe to assume that a reasonable proportion of people will care; being
+reasonably consistent with existing practice for the subsystem seems
+sensible.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003462:2, b/test/corpora/lkml/cur/1382298793.003462:2,
new file mode 100644 (file)
index 0000000..4f6ba5a
--- /dev/null
@@ -0,0 +1,97 @@
+From: Randy Dunlap <rdunlap@xenotime.net>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 15:28:35 -0800
+Organization: YPO4
+Lines: 26
+Message-ID: <20101116152835.b0ab571c.rdunlap@xenotime.net>
+References: <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <1289940156.28741.207.camel@Joe-Laptop>
+       <20101116124609.382e42fb.rdunlap@xenotime.net>
+       <20101116232258.GC24623@opensource.wolfsonmicro.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 00:29:00 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIUxP-0004iI-Hq
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 00:28:59 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id AD65410388C; Wed, 17 Nov 2010 00:28:58 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 21296103822;
+       Wed, 17 Nov 2010 00:28:57 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 58AE7103822; Wed, 17 Nov 2010 00:28:54 +0100 (CET)
+Received: from xenotime.net (xenotime.net [72.52.115.56])
+       by alsa0.perex.cz (Postfix) with SMTP id 1947B10381B
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 00:28:52 +0100 (CET)
+Received: from chimera.site ([173.50.240.230]) by xenotime.net for
+       <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 15:28:36 -0800
+In-Reply-To: <20101116232258.GC24623@opensource.wolfsonmicro.com>
+X-Mailer: Sylpheed 2.7.1 (GTK+ 2.16.6; x86_64-unknown-linux-gnu)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063495>
+
+On Tue, 16 Nov 2010 23:22:58 +0000 Mark Brown wrote:
+
+> On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+> > On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+> 
+> > > Some subsystem maintainers like upper case, some mixed, some lower.
+> > > Some aren't consistent.  (Staging/staging)
+> 
+> > Case usually doesn't matter to most of us.
+> 
+> Given that we're working in case sensitive languages here it's probably
+> safe to assume that a reasonable proportion of people will care; being
+> reasonably consistent with existing practice for the subsystem seems
+> sensible.
+
+Greg takes patches that say STAGING or Staging or staging.
+
+DaveM takes patches that say net: or netdev: or network: or NET:
+
+The sound maintainers take patches that say sound: or alsa: or ALSA:
+
+etc.
+
+---
+~Randy
+*** Remember to use Documentation/SubmitChecklist when testing your code ***
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003468:2, b/test/corpora/lkml/cur/1382298793.003468:2,
new file mode 100644 (file)
index 0000000..a26128d
--- /dev/null
@@ -0,0 +1,99 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 23:50:26 +0000
+Lines: 23
+Message-ID: <20101116235025.GA7256@opensource.wolfsonmicro.com>
+References: <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <1289940156.28741.207.camel@Joe-Laptop>
+       <20101116124609.382e42fb.rdunlap@xenotime.net>
+       <20101116232258.GC24623@opensource.wolfsonmicro.com>
+       <20101116152835.b0ab571c.rdunlap@xenotime.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>, Andrew Morton <akpm@linux-foundation.org>
+To: Randy Dunlap <rdunlap@xenotime.net>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 00:50:24 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIVI7-00006s-O3
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 00:50:23 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id F007910388B; Wed, 17 Nov 2010 00:50:22 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 9E0B9103822;
+       Wed, 17 Nov 2010 00:50:21 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id AD918103822; Wed, 17 Nov 2010 00:50:19 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id 39C5A10381B
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 00:50:19 +0100 (CET)
+Received: from finisterre.wolfsonmicro.main
+       (cpc3-sgyl4-0-0-cust125.sgyl.cable.virginmedia.com [82.41.240.126])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id B19503B4628; 
+       Tue, 16 Nov 2010 23:50:18 +0000 (GMT)
+Received: from broonie by finisterre.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@opensource.wolfsonmicro.com>)
+       id 1PIVIA-0003UP-7d; Tue, 16 Nov 2010 23:50:26 +0000
+Content-Disposition: inline
+In-Reply-To: <20101116152835.b0ab571c.rdunlap@xenotime.net>
+X-Cookie: You enjoy the company of other people.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063501>
+
+On Tue, Nov 16, 2010 at 03:28:35PM -0800, Randy Dunlap wrote:
+> On Tue, 16 Nov 2010 23:22:58 +0000 Mark Brown wrote:
+> > On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+
+> > > Case usually doesn't matter to most of us.
+
+> > Given that we're working in case sensitive languages here it's probably
+> > safe to assume that a reasonable proportion of people will care; being
+> > reasonably consistent with existing practice for the subsystem seems
+> > sensible.
+
+> Greg takes patches that say STAGING or Staging or staging.
+
+> DaveM takes patches that say net: or netdev: or network: or NET:
+
+> The sound maintainers take patches that say sound: or alsa: or ALSA:
+
+> etc.
+
+...and best practice would be to pay attention to what the standard
+thing is for the subsystem and follow that.  We shouldn't be suggesting
+that people just ignore the case, though obviously if it's not clear
+then it's not worth worrying too much about it.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003471:2, b/test/corpora/lkml/cur/1382298793.003471:2,
new file mode 100644 (file)
index 0000000..1e1fc4a
--- /dev/null
@@ -0,0 +1,79 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 15:57:57 -0800
+Lines: 25
+Message-ID: <1289951877.28741.262.camel@Joe-Laptop>
+References: <1289850773.16461.166.camel@Joe-Laptop>
+        <20101116104921.GL12986@rakim.wolfsonmicro.main>
+        <1289919077.28741.50.camel@Joe-Laptop>
+        <20101116183707.179964dd@schatten.dmk.lab>
+        <20101116181226.GB26239@rakim.wolfsonmicro.main>
+        <20101116203522.65240b18@schatten.dmk.lab>
+        <20101116195530.GA7523@rakim.wolfsonmicro.main>
+        <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+        <1289940156.28741.207.camel@Joe-Laptop>
+        <20101116124609.382e42fb.rdunlap@xenotime.net>
+        <20101116232258.GC24623@opensource.wolfsonmicro.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Randy Dunlap <rdunlap@xenotime.net>,
+       Florian Mickler <florian@mickler.org>,
+       Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Nov 17 00:58:25 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIVPr-0004pn-RQ
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 17 Nov 2010 00:58:24 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1757881Ab0KPX6A (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 18:58:00 -0500
+Received: from mail.perches.com ([173.55.12.10]:1493 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1757143Ab0KPX57 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 18:57:59 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id B87C82436B;
+       Tue, 16 Nov 2010 15:55:40 -0800 (PST)
+In-Reply-To: <20101116232258.GC24623@opensource.wolfsonmicro.com>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063504>
+
+On Tue, 2010-11-16 at 23:22 +0000, Mark Brown wrote:
+> On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+> > On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+> > > Some subsystem maintainers like upper case, some mixed, some lower.
+> > > Some aren't consistent.  (Staging/staging)
+> > Case usually doesn't matter to most of us.
+> Given that we're working in case sensitive languages here it's probably
+> safe to assume that a reasonable proportion of people will care; being
+> reasonably consistent with existing practice for the subsystem seems
+> sensible.
+
+Presumably the tool would also have to traverse up the tree
+to find the appropriate style so every MAINTAINERS section
+would not need a C entry.
+
+ie: sound/soc/codecs/foo could use the C: entry for sound/soc/
+
+Perhaps something like:
+       C:      ASoC basename:
+
+and for arch/x86/:
+       C:      x86, dirname:
+
+etc.
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003472:2, b/test/corpora/lkml/cur/1382298793.003472:2,
new file mode 100644 (file)
index 0000000..0e360fd
--- /dev/null
@@ -0,0 +1,84 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Tue, 16 Nov 2010 15:57:55 -0800
+Lines: 29
+Message-ID: <1289951875.28741.261.camel@Joe-Laptop>
+References: <1289850773.16461.166.camel@Joe-Laptop>
+        <20101116104921.GL12986@rakim.wolfsonmicro.main>
+        <1289919077.28741.50.camel@Joe-Laptop>
+        <20101116183707.179964dd@schatten.dmk.lab>
+        <20101116181226.GB26239@rakim.wolfsonmicro.main>
+        <20101116203522.65240b18@schatten.dmk.lab>
+        <20101116195530.GA7523@rakim.wolfsonmicro.main>
+        <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+        <1289940156.28741.207.camel@Joe-Laptop>
+        <20101116124609.382e42fb.rdunlap@xenotime.net>
+        <20101116232258.GC24623@opensource.wolfsonmicro.com>
+        <20101116152835.b0ab571c.rdunlap@xenotime.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Florian Mickler <florian@mickler.org>,
+       Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Randy Dunlap <rdunlap@xenotime.net>
+X-From: linux-kernel-owner@vger.kernel.org Wed Nov 17 00:58:26 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIVPr-0004pn-AW
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 17 Nov 2010 00:58:23 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1757099Ab0KPX56 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 18:57:58 -0500
+Received: from mail.perches.com ([173.55.12.10]:1485 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751238Ab0KPX55 (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 18:57:57 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 18B3A24368;
+       Tue, 16 Nov 2010 15:55:38 -0800 (PST)
+In-Reply-To: <20101116152835.b0ab571c.rdunlap@xenotime.net>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063505>
+
+On Tue, 2010-11-16 at 15:28 -0800, Randy Dunlap wrote:
+> On Tue, 16 Nov 2010 23:22:58 +0000 Mark Brown wrote:
+> > On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+> > > On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+> > > > Some subsystem maintainers like upper case, some mixed, some lower.
+> > > > Some aren't consistent.  (Staging/staging)
+> > > Case usually doesn't matter to most of us.
+> > Given that we're working in case sensitive languages here it's probably
+> > safe to assume that a reasonable proportion of people will care; being
+> > reasonably consistent with existing practice for the subsystem seems
+> > sensible.
+> Greg takes patches that say STAGING or Staging or staging.
+
+Greg seems to rewrite patch subjects and is inconsistent
+about case, so he might be doing that by hand.
+
+> DaveM takes patches that say net: or netdev: or network: or NET:
+
+DaveM doesn't appear to be choosy about patch subject lines.
+He seems to take any sensible patch and as far as I know he
+doesn't edit the subject lines.  He does reject inferior
+patches outright.
+
+> The sound maintainers take patches that say sound: or alsa: or ALSA:
+
+The sound maintainers seem to rewrite patch subjects on an
+ad-hoc basis.
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003478:2, b/test/corpora/lkml/cur/1382298793.003478:2,
new file mode 100644 (file)
index 0000000..c95e3f8
--- /dev/null
@@ -0,0 +1,113 @@
+From: Joel Becker <Joel.Becker@oracle.com>
+Subject: Re: [PATCH 36/44] fs/ocfs2: Remove unnecessary
+       semicolons
+Date: Tue, 16 Nov 2010 16:10:47 -0800
+Lines: 38
+Message-ID: <20101117001046.GE10237@mail.oracle.com>
+References: <cover.1289789604.git.joe@perches.com>
+       <e32409b17aaa1a54fec85f3654583ef08fcf851c.1289789605.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Mark Fasheh <mfasheh@suse.com>, Jiri Kosina <trivial@kernel.org>,
+        linux-kernel@vger.kernel.org, ocfs2-devel@oss.oracle.com
+To: Joe Perches <joe@perches.com>
+X-From: ocfs2-devel-bounces@oss.oracle.com Wed Nov 17 01:11:36 2010
+Return-path: <ocfs2-devel-bounces@oss.oracle.com>
+Envelope-to: gcfod-ocfs2-devel@gmane.org
+Received: from rcsinet10.oracle.com ([148.87.113.121])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <ocfs2-devel-bounces@oss.oracle.com>)
+       id 1PIVcY-0003qc-VC
+       for gcfod-ocfs2-devel@gmane.org; Wed, 17 Nov 2010 01:11:31 +0100
+Received: from rcsinet15.oracle.com (rcsinet15.oracle.com [148.87.113.117])
+       by rcsinet10.oracle.com (Switch-3.4.2/Switch-3.4.2) with ESMTP id oAH0B96Y007820
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK);
+       Wed, 17 Nov 2010 00:11:10 GMT
+Received: from oss.oracle.com (oss.oracle.com [141.146.12.120])
+       by rcsinet15.oracle.com (Switch-3.4.2/Switch-3.4.1) with ESMTP id oAH0B6rM032434
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO);
+       Wed, 17 Nov 2010 00:11:07 GMT
+Received: from localhost ([127.0.0.1] helo=oss.oracle.com)
+       by oss.oracle.com with esmtp (Exim 4.63)
+       (envelope-from <ocfs2-devel-bounces@oss.oracle.com>)
+       id 1PIVcA-0001bs-K0; Tue, 16 Nov 2010 16:11:06 -0800
+Received: from rcsinet15.oracle.com ([148.87.113.117])
+       by oss.oracle.com with esmtp (Exim 4.63)
+       (envelope-from <joel.becker@oracle.com>) id 1PIVc7-0001bl-Td
+       for ocfs2-devel@oss.oracle.com; Tue, 16 Nov 2010 16:11:04 -0800
+Received: from acsmt354.oracle.com (acsmt354.oracle.com [141.146.40.154])
+       by rcsinet15.oracle.com (Switch-3.4.2/Switch-3.4.1) with ESMTP id
+       oAH0B1gV032165; Wed, 17 Nov 2010 00:11:01 GMT
+Received: from ca-server1.us.oracle.com by acsmt353.oracle.com
+       with ESMTP id 784380701289952654; Tue, 16 Nov 2010 16:10:54 -0800
+Received: from jlbec by ca-server1.us.oracle.com with local (Exim 4.69)
+       (envelope-from <joel.becker@oracle.com>)
+       id 1PIVbx-0007gG-4G; Tue, 16 Nov 2010 16:10:53 -0800
+Mail-Followup-To: Joe Perches <joe@perches.com>,
+       Jiri Kosina <trivial@kernel.org>, Mark Fasheh <mfasheh@suse.com>,
+       ocfs2-devel@oss.oracle.com, linux-kernel@vger.kernel.org
+Content-Disposition: inline
+In-Reply-To: <e32409b17aaa1a54fec85f3654583ef08fcf851c.1289789605.git.joe@perches.com>
+X-Burt-Line: Trees are cool.
+X-Red-Smith: Ninety feet between bases is perhaps as close as man has ever
+       come to perfection.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: ocfs2-devel@oss.oracle.com
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: <ocfs2-devel.oss.oracle.com>
+List-Unsubscribe: <http://oss.oracle.com/mailman/listinfo/ocfs2-devel>,
+       <mailto:ocfs2-devel-request@oss.oracle.com?subject=unsubscribe>
+List-Archive: <http://oss.oracle.com/pipermail/ocfs2-devel>
+List-Post: <mailto:ocfs2-devel@oss.oracle.com>
+List-Help: <mailto:ocfs2-devel-request@oss.oracle.com?subject=help>
+List-Subscribe: <http://oss.oracle.com/mailman/listinfo/ocfs2-devel>,
+       <mailto:ocfs2-devel-request@oss.oracle.com?subject=subscribe>
+Sender: ocfs2-devel-bounces@oss.oracle.com
+Errors-To: ocfs2-devel-bounces@oss.oracle.com
+X-Source-IP: oss.oracle.com [141.146.12.120]
+X-Auth-Type: Internal IP
+X-CT-RefId: str=0001.0A090207.4CE31D9C.007A:SCFSTAT3865452,ss=1,fgs=0
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063511>
+
+On Sun, Nov 14, 2010 at 07:04:55PM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Acked-by: Joel Becker <joel.becker@oracle.com>
+
+
+> ---
+>  fs/ocfs2/refcounttree.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+> diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
+> index b5f9160..da14a42 100644
+> --- a/fs/ocfs2/refcounttree.c
+> +++ b/fs/ocfs2/refcounttree.c
+> @@ -3704,7 +3704,7 @@ int ocfs2_refcount_cow_xattr(struct inode *inode,
+>      context->cow_start = cow_start;
+>      context->cow_len = cow_len;
+>      context->ref_tree = ref_tree;
+> -    context->ref_root_bh = ref_root_bh;;
+> +    context->ref_root_bh = ref_root_bh;
+>      context->cow_object = xv;
+>  
+>      context->cow_duplicate_clusters = ocfs2_duplicate_clusters_by_jbd;
+> -- 
+> 1.7.3.1.g432b3.dirty
+> 
+
+-- 
+
+Life's Little Instruction Book #237
+
+       "Seek out the good in people."
+
+Joel Becker
+Senior Development Manager
+Oracle
+E-mail: joel.becker@oracle.com
+Phone: (650) 506-8127
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003497:2, b/test/corpora/lkml/cur/1382298793.003497:2,
new file mode 100644 (file)
index 0000000..ab47886
--- /dev/null
@@ -0,0 +1,94 @@
+From: Stefan Richter <stefanr@s5r6.in-berlin.de>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Wed, 17 Nov 2010 01:44:27 +0100
+Lines: 34
+Message-ID: <20101117014427.41d85b13@stein>
+References: <1289848458.16461.150.camel@Joe-Laptop>
+       <20101115193407.GK12986@rakim.wolfsonmicro.main>
+       <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <20101116230126.GB24623@opensource.wolfsonmicro.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Randy Dunlap <rdunlap@xenotime.net>,
+       Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>,
+       Jiri Kosina <trivial@kernel.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Nov 17 01:45:44 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIW9d-0005et-Ft
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 17 Nov 2010 01:45:41 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933190Ab0KQApU (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 19:45:20 -0500
+Received: from einhorn.in-berlin.de ([192.109.42.8]:40608 "EHLO
+       einhorn.in-berlin.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S932265Ab0KQApS (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 19:45:18 -0500
+X-Envelope-From: stefanr@s5r6.in-berlin.de
+Received: from stein ([83.221.231.7])
+       (authenticated bits=0)
+       by einhorn.in-berlin.de (8.13.6/8.13.6/Debian-1) with ESMTP id oAH0iRjV025917
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT);
+       Wed, 17 Nov 2010 01:44:28 +0100
+In-Reply-To: <20101116230126.GB24623@opensource.wolfsonmicro.com>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-pc-linux-gnu)
+X-Scanned-By: MIMEDefang_at_IN-Berlin_e.V. on 192.109.42.8
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063530>
+
+On Nov 16 Mark Brown wrote:
+> On Tue, Nov 16, 2010 at 12:21:02PM -0800, Randy Dunlap wrote:
+> 
+> > I don't know what you asked Joe to change, but asking someone to use
+> > the documented canonical patch format:
+> 
+> > <quote>
+> > The canonical patch subject line is:
+> 
+> >     Subject: [PATCH 001/123] subsystem: summary phrase
+> > </quote>
+> 
+> > should be fine.  And there is no need for printf-ish templates
+> > for this in MAINTAINERS either.
+> 
+> That's exactly what I asked him to do.  He said he's not willing to use
+> anything for "subsystem" which can't be automatically generated.
+
+Why should we codify our conventions in MAINTAINERS to accommodate the
+specific problem of virtually a _single_ patch author?
+
+Conventions are living and are being adjusted all the time, as code
+organization changes, people join and go, projects start and cease.
+
+Said author please looks the conventions up in the git history.  If he
+finds that this decelerates his patch generation rate, he can surely
+code a script that looks into git for him and suggests plausible
+prefixes for his patch titles to him.  Or he can collect a kind of
+database (a config file) locally for his own use in which he records
+conventional prefixes on the go.
+-- 
+Stefan Richter
+-=====-==-=- =-== =---=
+http://arcgraph.de/sr/
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003501:2, b/test/corpora/lkml/cur/1382298793.003501:2,
new file mode 100644 (file)
index 0000000..67668d1
--- /dev/null
@@ -0,0 +1,89 @@
+From: Jiri Kosina <jkosina@suse.cz>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Wed, 17 Nov 2010 01:53:35 +0100 (CET)
+Lines: 43
+Message-ID: <alpine.LNX.2.00.1011170150060.7420@pobox.suse.cz>
+References: <1289848458.16461.150.camel@Joe-Laptop> <20101115193407.GK12986@rakim.wolfsonmicro.main> <1289850773.16461.166.camel@Joe-Laptop> <20101116104921.GL12986@rakim.wolfsonmicro.main> <1289919077.28741.50.camel@Joe-Laptop> <20101116183707.179964dd@schatten.dmk.lab>
+ <20101116181226.GB26239@rakim.wolfsonmicro.main> <20101116203522.65240b18@schatten.dmk.lab> <20101116195530.GA7523@rakim.wolfsonmicro.main> <20101116122102.86e7e0b9.rdunlap@xenotime.net> <20101116230126.GB24623@opensource.wolfsonmicro.com>
+ <20101117014427.41d85b13@stein>
+Mime-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Randy Dunlap <rdunlap@xenotime.net>,
+       Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Stefan Richter <stefanr@s5r6.in-berlin.de>
+X-From: linux-kernel-owner@vger.kernel.org Wed Nov 17 01:53:55 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIWHa-0001VG-H4
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 17 Nov 2010 01:53:54 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S933245Ab0KQAxi (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Tue, 16 Nov 2010 19:53:38 -0500
+Received: from cantor2.suse.de ([195.135.220.15]:45188 "EHLO mx2.suse.de"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S932265Ab0KQAxh (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Tue, 16 Nov 2010 19:53:37 -0500
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       by mx2.suse.de (Postfix) with ESMTP id 1C9DD867E2;
+       Wed, 17 Nov 2010 01:53:36 +0100 (CET)
+In-Reply-To: <20101117014427.41d85b13@stein>
+User-Agent: Alpine 2.00 (LNX 1167 2008-08-23)
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063534>
+
+On Wed, 17 Nov 2010, Stefan Richter wrote:
+
+> > > I don't know what you asked Joe to change, but asking someone to use
+> > > the documented canonical patch format:
+> > 
+> > > <quote>
+> > > The canonical patch subject line is:
+> > 
+> > >     Subject: [PATCH 001/123] subsystem: summary phrase
+> > > </quote>
+> > 
+> > > should be fine.  And there is no need for printf-ish templates
+> > > for this in MAINTAINERS either.
+> > 
+> > That's exactly what I asked him to do.  He said he's not willing to use
+> > anything for "subsystem" which can't be automatically generated.
+> 
+> Why should we codify our conventions in MAINTAINERS to accommodate the
+> specific problem of virtually a _single_ patch author?
+> 
+> Conventions are living and are being adjusted all the time, as code
+> organization changes, people join and go, projects start and cease.
+> 
+> Said author please looks the conventions up in the git history.  If he
+> finds that this decelerates his patch generation rate, he can surely
+> code a script that looks into git for him and suggests plausible
+> prefixes for his patch titles to him.  Or he can collect a kind of
+> database (a config file) locally for his own use in which he records
+> conventional prefixes on the go.
+
+Come on guys, this debate is really horribly boring.
+
+Either the maintainer wants the patch. Then he is certainly able to apply 
+it no matter the subject line (I personally am getting a lot of patches 
+which don't follow the format I am using in my tree ... converting 
+Subject: lines is so trivial that I have never felt like bothering anyone 
+about it ... it's basically single condition in a shellscript). Or the 
+maintainer doesn't feel like the patch is worth it, and then the 
+subject-line format really doesn't matter.
+
+-- 
+Jiri Kosina
+SUSE Labs, Novell Inc.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003503:2, b/test/corpora/lkml/cur/1382298793.003503:2,
new file mode 100644 (file)
index 0000000..0ea87ec
--- /dev/null
@@ -0,0 +1,110 @@
+From: Randy Dunlap <rdunlap@xenotime.net>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Tue, 16 Nov 2010 16:55:56 -0800
+Organization: YPO4
+Lines: 36
+Message-ID: <20101116165556.3ee8e236.rdunlap@xenotime.net>
+References: <1289850773.16461.166.camel@Joe-Laptop>
+       <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <1289940156.28741.207.camel@Joe-Laptop>
+       <20101116124609.382e42fb.rdunlap@xenotime.net>
+       <20101116232258.GC24623@opensource.wolfsonmicro.com>
+       <20101116152835.b0ab571c.rdunlap@xenotime.net>
+       <1289951875.28741.261.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       linux-kernel@vger.kernel.org, Florian Mickler <florian@mickler.org>,
+       Andrew Morton <akpm@linux-foundation.org>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 01:56:19 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIWJv-0002k1-9C
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 01:56:19 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 9953B1038A4; Wed, 17 Nov 2010 01:56:17 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id C25DC24439;
+       Wed, 17 Nov 2010 01:56:09 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 316CC24439; Wed, 17 Nov 2010 01:56:08 +0100 (CET)
+Received: from xenotime.net (xenotime.net [72.52.115.56])
+       by alsa0.perex.cz (Postfix) with SMTP id 043F124436
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 01:56:06 +0100 (CET)
+Received: from chimera.site ([173.50.240.230]) by xenotime.net for
+       <alsa-devel@alsa-project.org>; Tue, 16 Nov 2010 16:55:57 -0800
+In-Reply-To: <1289951875.28741.261.camel@Joe-Laptop>
+X-Mailer: Sylpheed 2.7.1 (GTK+ 2.16.6; x86_64-unknown-linux-gnu)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1063536>
+
+On Tue, 16 Nov 2010 15:57:55 -0800 Joe Perches wrote:
+
+> On Tue, 2010-11-16 at 15:28 -0800, Randy Dunlap wrote:
+> > On Tue, 16 Nov 2010 23:22:58 +0000 Mark Brown wrote:
+> > > On Tue, Nov 16, 2010 at 12:46:09PM -0800, Randy Dunlap wrote:
+> > > > On Tue, 16 Nov 2010 12:42:36 -0800 Joe Perches wrote:
+> > > > > Some subsystem maintainers like upper case, some mixed, some lower.
+> > > > > Some aren't consistent.  (Staging/staging)
+> > > > Case usually doesn't matter to most of us.
+> > > Given that we're working in case sensitive languages here it's probably
+> > > safe to assume that a reasonable proportion of people will care; being
+> > > reasonably consistent with existing practice for the subsystem seems
+> > > sensible.
+> > Greg takes patches that say STAGING or Staging or staging.
+> 
+> Greg seems to rewrite patch subjects and is inconsistent
+> about case, so he might be doing that by hand.
+> 
+> > DaveM takes patches that say net: or netdev: or network: or NET:
+> 
+> DaveM doesn't appear to be choosy about patch subject lines.
+> He seems to take any sensible patch and as far as I know he
+> doesn't edit the subject lines.  He does reject inferior
+> patches outright.
+> 
+> > The sound maintainers take patches that say sound: or alsa: or ALSA:
+> 
+> The sound maintainers seem to rewrite patch subjects on an
+> ad-hoc basis.
+
+OK, I can accept your summary.
+However, I can't tell that we are making any progress.
+
+---
+~Randy
+*** Remember to use Documentation/SubmitChecklist when testing your code ***
+
+
diff --git a/test/corpora/lkml/cur/1382298793.003971:2, b/test/corpora/lkml/cur/1382298793.003971:2,
new file mode 100644 (file)
index 0000000..4b510ad
--- /dev/null
@@ -0,0 +1,106 @@
+From: Mark Brown <broonie@opensource.wolfsonmicro.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem
+ maintainer preference tool
+Date: Wed, 17 Nov 2010 17:07:47 +0000
+Lines: 29
+Message-ID: <20101117170746.GB19488@rakim.wolfsonmicro.main>
+References: <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <20101116230126.GB24623@opensource.wolfsonmicro.com>
+       <20101117014427.41d85b13@stein>
+       <alpine.LNX.2.00.1011170150060.7420@pobox.suse.cz>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org,
+       Florian Mickler <florian@mickler.org>, Randy Dunlap <rdunlap@xenotime.net>,
+       Stefan Richter <stefanr@s5r6.in-berlin.de>, Joe Perches <joe@perches.com>,
+       Andrew Morton <akpm@linux-foundation.org>
+To: Jiri Kosina <jkosina@suse.cz>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 18:07:59 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PIlUD-000436-I4
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 18:07:57 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id B1000245CE; Wed, 17 Nov 2010 18:07:54 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id C35471037EC;
+       Wed, 17 Nov 2010 18:07:52 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 4EE8F1037EC; Wed, 17 Nov 2010 18:07:51 +0100 (CET)
+Received: from opensource2.wolfsonmicro.com (opensource.wolfsonmicro.com
+       [80.75.67.52]) by alsa0.perex.cz (Postfix) with ESMTP id A8A16245CE
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 18:07:50 +0100 (CET)
+Received: from rakim.wolfsonmicro.main (lumison.wolfsonmicro.com
+       [87.246.78.27])
+       by opensource2.wolfsonmicro.com (Postfix) with ESMTPSA id 9E3591147AC; 
+       Wed, 17 Nov 2010 17:07:48 +0000 (GMT)
+Received: from broonie by rakim.wolfsonmicro.main with local (Exim 4.72)
+       (envelope-from <broonie@rakim.wolfsonmicro.main>)
+       id 1PIlU3-00061C-Bl; Wed, 17 Nov 2010 17:07:47 +0000
+Content-Disposition: inline
+In-Reply-To: <alpine.LNX.2.00.1011170150060.7420@pobox.suse.cz>
+X-Cookie: Killing turkeys causes winter.
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1064007>
+
+On Wed, Nov 17, 2010 at 01:53:35AM +0100, Jiri Kosina wrote:
+> On Wed, 17 Nov 2010, Stefan Richter wrote:
+
+> > Why should we codify our conventions in MAINTAINERS to accommodate the
+> > specific problem of virtually a _single_ patch author?
+
+It seems to be the way we're heading in general - look at all the recent
+work on MAINTAINERS and get_maintainer.pl.  There seems to be a genral
+push to make all this stuff automatable.
+
+> Either the maintainer wants the patch. Then he is certainly able to apply 
+> it no matter the subject line (I personally am getting a lot of patches 
+> which don't follow the format I am using in my tree ... converting 
+> Subject: lines is so trivial that I have never felt like bothering anyone 
+> about it ... it's basically single condition in a shellscript). Or the 
+
+It's slightly more than that if you're dealing with more than one area,
+and I also find this sort of stuff is a good flag for scrubbing the
+patch in greater detail - when patches stand out from a 1000ft visual
+overview there's a fair chance that there's other issues so if people
+regularly submit good patches that have only cosmetic issues I find it's
+worth guiding them away from that.
+
+> maintainer doesn't feel like the patch is worth it, and then the 
+> subject-line format really doesn't matter.
+
+In this case if I don't apply it it's likely to end up going in via your
+tree and then I'll still have to deal with the merge conflicts which are
+more annoying.
+
+
diff --git a/test/corpora/lkml/cur/1382298793.004059:2, b/test/corpora/lkml/cur/1382298793.004059:2,
new file mode 100644 (file)
index 0000000..646ac83
--- /dev/null
@@ -0,0 +1,88 @@
+From: Pavel Machek <pavel@ucw.cz>
+Subject: Re: [PATCH 44/44] sound/soc/codecs: Remove unnecessary
+       semicolons
+Date: Wed, 17 Nov 2010 20:32:56 +0100
+Lines: 19
+Message-ID: <20101117193256.GA28010@ucw.cz>
+References: <cover.1289789604.git.joe@perches.com>
+       <97fd199b7dac50613f6843156687223928cce44a.1289789605.git.joe@perches.com>
+       <20101115134939.GC12986@rakim.wolfsonmicro.main>
+       <1289840957.16461.138.camel@Joe-Laptop>
+       <20101115173031.GI12986@rakim.wolfsonmicro.main>
+       <1289842444.16461.140.camel@Joe-Laptop>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: Dimitris Papastamos <dp@opensource.wolfsonmicro.com>,
+       alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       Takashi Iwai <tiwai@suse.de>, linux-kernel@vger.kernel.org,
+       Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Ian Lartey <ian@opensource.wolfsonmicro.com>,
+       Liam Girdwood <lrg@slimlogic.co.uk>
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Wed Nov 17 20:33:19 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PInkr-0005bs-LY
+       for glad-alsa-devel-2@m.gmane.org; Wed, 17 Nov 2010 20:33:17 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id D7C501038BF; Wed, 17 Nov 2010 20:33:14 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=disabled
+       version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 193061038BD;
+       Wed, 17 Nov 2010 20:33:08 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 664E91038BD; Wed, 17 Nov 2010 20:33:06 +0100 (CET)
+Received: from atrey.karlin.mff.cuni.cz (ksp.mff.cuni.cz [195.113.26.206])
+       by alsa0.perex.cz (Postfix) with ESMTP id 9E5621038BA
+       for <alsa-devel@alsa-project.org>; Wed, 17 Nov 2010 20:33:05 +0100 (CET)
+Received: by atrey.karlin.mff.cuni.cz (Postfix, from userid 512)
+       id AC6A1F23E1; Wed, 17 Nov 2010 20:33:04 +0100 (CET)
+Content-Disposition: inline
+In-Reply-To: <1289842444.16461.140.camel@Joe-Laptop>
+User-Agent: Mutt/1.5.20 (2009-06-14)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1064096>
+
+On Mon 2010-11-15 09:34:04, Joe Perches wrote:
+> On Mon, 2010-11-15 at 17:30 +0000, Mark Brown wrote:
+> > On Mon, Nov 15, 2010 at 09:09:17AM -0800, Joe Perches wrote:
+> > > Signed-off-by: Joe Perches <joe@perches.com>
+> > Applied, thanks.
+> > Please try to use changelog formats consistent with the code you're
+> > modifying.
+> 
+> I think it's more important to use consistent changelogs
+> for a patch series.
+
+And I agree here. Having to learn code-style quirks for patches is
+bad, having to learn new changelog style for each subsystem is very
+bad.
+                                                               Pavel
+
+-- 
+(english) http://www.livejournal.com/~pavelmachek
+(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
+
+
diff --git a/test/corpora/lkml/cur/1382298793.004091:2, b/test/corpora/lkml/cur/1382298793.004091:2,
new file mode 100644 (file)
index 0000000..1abc297
--- /dev/null
@@ -0,0 +1,133 @@
+From: Stefan Richter <stefanr@s5r6.in-berlin.de>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Wed, 17 Nov 2010 22:07:37 +0100
+Lines: 74
+Message-ID: <20101117220737.2d3d7356@stein>
+References: <20101116104921.GL12986@rakim.wolfsonmicro.main>
+       <1289919077.28741.50.camel@Joe-Laptop>
+       <20101116183707.179964dd@schatten.dmk.lab>
+       <20101116181226.GB26239@rakim.wolfsonmicro.main>
+       <20101116203522.65240b18@schatten.dmk.lab>
+       <20101116195530.GA7523@rakim.wolfsonmicro.main>
+       <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+       <20101116230126.GB24623@opensource.wolfsonmicro.com>
+       <20101117014427.41d85b13@stein>
+       <alpine.LNX.2.00.1011170150060.7420@pobox.suse.cz>
+       <20101117170746.GB19488@rakim.wolfsonmicro.main>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Cc: Jiri Kosina <jkosina@suse.cz>, Randy Dunlap <rdunlap@xenotime.net>,
+       Florian Mickler <florian@mickler.org>,
+       Joe Perches <joe@perches.com>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Mark Brown <broonie@opensource.wolfsonmicro.com>
+X-From: linux-kernel-owner@vger.kernel.org Wed Nov 17 22:08:15 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIpEk-0006ge-Hz
+       for glk-linux-kernel-3@lo.gmane.org; Wed, 17 Nov 2010 22:08:14 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1758632Ab0KQVHz (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 17 Nov 2010 16:07:55 -0500
+Received: from einhorn.in-berlin.de ([192.109.42.8]:48630 "EHLO
+       einhorn.in-berlin.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751563Ab0KQVHy (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 17 Nov 2010 16:07:54 -0500
+X-Envelope-From: stefanr@s5r6.in-berlin.de
+Received: from stein ([83.221.231.7])
+       (authenticated bits=0)
+       by einhorn.in-berlin.de (8.13.6/8.13.6/Debian-1) with ESMTP id oAHL7dhi014114
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT);
+       Wed, 17 Nov 2010 22:07:39 +0100
+In-Reply-To: <20101117170746.GB19488@rakim.wolfsonmicro.main>
+X-Mailer: Claws Mail 3.7.6 (GTK+ 2.20.1; x86_64-pc-linux-gnu)
+X-Scanned-By: MIMEDefang_at_IN-Berlin_e.V. on 192.109.42.8
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1064128>
+
+On Nov 17 Mark Brown wrote:
+> On Wed, Nov 17, 2010 at 01:53:35AM +0100, Jiri Kosina wrote:
+> > On Wed, 17 Nov 2010, Stefan Richter wrote:
+> 
+> > > Why should we codify our conventions in MAINTAINERS to accommodate the
+> > > specific problem of virtually a _single_ patch author?
+> 
+> It seems to be the way we're heading in general - look at all the recent
+> work on MAINTAINERS and get_maintainer.pl.  There seems to be a genral
+> push to make all this stuff automatable.
+
+get_maintainer.pl, used with judgment and together with "gitk
+the/patched/source.c" is nice not only for people like Joe who
+regularly work tree-wide but also for ones like me who only rarely want
+to submit a bug report or patch for a subsystem with they are
+unfamiliar with.
+
+But the thought of a database of "how to start a good patch title" is
+far-fetched.  Really, as a patch author, just look how other people
+write patch titles and judge whether this is good for your work too or
+not.
+
+> > Either the maintainer wants the patch. Then he is certainly able to apply 
+> > it no matter the subject line (I personally am getting a lot of patches 
+> > which don't follow the format I am using in my tree ... converting 
+> > Subject: lines is so trivial that I have never felt like bothering anyone 
+> > about it ... it's basically single condition in a shellscript). Or the 
+> 
+> It's slightly more than that if you're dealing with more than one area,
+> and I also find this sort of stuff is a good flag for scrubbing the
+> patch in greater detail - when patches stand out from a 1000ft visual
+> overview there's a fair chance that there's other issues so if people
+> regularly submit good patches that have only cosmetic issues I find it's
+> worth guiding them away from that.
+
+On one hand Jiri is right that maintainers can adjust title prefixes ad
+hoc.  (Downside:  Weaker connection to mailinglist archives.)  On the
+other hand, in the case of long-term prolific authors like Joe it is
+more optimal if there is a good patch title right from the outset.
+
+So, if this boring thread does at least yield the conclusion that
+${path}/${filename}: is a bad title prefix, at least something was
+won. :-)
+
+Another thought:  Whether a typical part of a mass conversion, e.g. to
+use a new helper macro without change of functionality, is named
+
+       [PATCH] [subsystem] driver: use foo_bar helper
+or
+       [PATCH] use foo_bar helper in subsystem, driver
+
+does not really matter, does it?  This change is more about the helper
+than about the driver.  It is really a different kind of changeset than
+a functional change that we want to be called
+
+       [PATCH] [subsystem] driver: fix crash at disconnection
+
+or so.  This is something that those who look for release notes of
+that driver or subsystem want to grep in the changelog.
+
+Or in other words:  If you as patch author wonder what would be a good
+title for your patch, then ask yourself:  How should this change show up
+in kernel release notes that are constructed from the git shortlog?
+Sometimes the answer to this question includes among else a prefix with
+a canonical subsystem name (even case sensitive, with brackets or
+colon), whereas other times such formalities are utterly pointless.
+
+[Sorry for the spent electrons.  But OTOH, issues like (1.) optimum
+use of reviewer bandwidth, (2.) kernel changelog alias release
+notes /do/ matter.]
+-- 
+Stefan Richter
+-=====-==-=- =-== =---=
+http://arcgraph.de/sr/
+
+
diff --git a/test/corpora/lkml/cur/1382298793.004190:2, b/test/corpora/lkml/cur/1382298793.004190:2,
new file mode 100644 (file)
index 0000000..10a54af
--- /dev/null
@@ -0,0 +1,67 @@
+From: Joe Perches <joe@perches.com>
+Subject: Re: rfc: rewrite commit subject line for subsystem maintainer
+ preference tool
+Date: Wed, 17 Nov 2010 15:49:19 -0800
+Lines: 11
+Message-ID: <1290037759.28741.313.camel@Joe-Laptop>
+References: <20101116104921.GL12986@rakim.wolfsonmicro.main>
+        <1289919077.28741.50.camel@Joe-Laptop>
+        <20101116183707.179964dd@schatten.dmk.lab>
+        <20101116181226.GB26239@rakim.wolfsonmicro.main>
+        <20101116203522.65240b18@schatten.dmk.lab>
+        <20101116195530.GA7523@rakim.wolfsonmicro.main>
+        <20101116122102.86e7e0b9.rdunlap@xenotime.net>
+        <20101116230126.GB24623@opensource.wolfsonmicro.com>
+        <20101117014427.41d85b13@stein>
+        <alpine.LNX.2.00.1011170150060.7420@pobox.suse.cz>
+        <20101117170746.GB19488@rakim.wolfsonmicro.main>
+        <20101117220737.2d3d7356@stein>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>,
+       Jiri Kosina <jkosina@suse.cz>,
+       Randy Dunlap <rdunlap@xenotime.net>,
+       Florian Mickler <florian@mickler.org>,
+       Andrew Morton <akpm@linux-foundation.org>,
+       alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org
+To: Stefan Richter <stefanr@s5r6.in-berlin.de>
+X-From: linux-kernel-owner@vger.kernel.org Thu Nov 18 00:49:42 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PIrkz-0000bE-Ro
+       for glk-linux-kernel-3@lo.gmane.org; Thu, 18 Nov 2010 00:49:42 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752426Ab0KQXtX (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Wed, 17 Nov 2010 18:49:23 -0500
+Received: from mail.perches.com ([173.55.12.10]:1507 "EHLO mail.perches.com"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1750850Ab0KQXtW (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Wed, 17 Nov 2010 18:49:22 -0500
+Received: from [192.168.1.162] (unknown [192.168.1.162])
+       by mail.perches.com (Postfix) with ESMTP id 7D75724368;
+       Wed, 17 Nov 2010 15:49:17 -0800 (PST)
+In-Reply-To: <20101117220737.2d3d7356@stein>
+X-Mailer: Evolution 2.30.3 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1064228>
+
+On Wed, 2010-11-17 at 22:07 +0100, Stefan Richter wrote:
+> So, if this boring thread does at least yield the conclusion that
+> ${path}/${filename}: is a bad title prefix, at least something was
+> won. :-)
+
+I've changed my scripts to use this style:
+
+Subject: [PATCH] $(basename $(dirname $file)): commit desc... 
+
+until a better tool is available.
+
+
+
diff --git a/test/corpora/lkml/cur/1382298795.000299:2, b/test/corpora/lkml/cur/1382298795.000299:2,
new file mode 100644 (file)
index 0000000..ea8522f
--- /dev/null
@@ -0,0 +1,79 @@
+From: Artem Bityutskiy <dedekind1@gmail.com>
+Subject: Re: [PATCH 37/44] fs/ubifs: Remove unnecessary semicolons
+Date: Fri, 19 Nov 2010 15:21:08 +0200
+Lines: 13
+Message-ID: <1290172868.4768.2.camel@localhost>
+References: <cover.1289789604.git.joe@perches.com>
+        <902d76520da2f65e5dc44339dccb07159947f23d.1289789605.git.joe@perches.com>
+Reply-To: dedekind1@gmail.com
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Adrian Hunter <adrian.hunter@nokia.com>,
+       linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: linux-kernel-owner@vger.kernel.org Fri Nov 19 14:22:08 2010
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PJQuj-00008T-0Z
+       for glk-linux-kernel-3@lo.gmane.org; Fri, 19 Nov 2010 14:22:05 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754099Ab0KSNVg convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Fri, 19 Nov 2010 08:21:36 -0500
+Received: from mail-bw0-f46.google.com ([209.85.214.46]:60443 "EHLO
+       mail-bw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753270Ab0KSNVf (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Fri, 19 Nov 2010 08:21:35 -0500
+Received: by bwz15 with SMTP id 15so3864554bwz.19
+        for <linux-kernel@vger.kernel.org>; Fri, 19 Nov 2010 05:21:33 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:received:received:subject:from:reply-to:to:cc
+         :in-reply-to:references:content-type:date:message-id:mime-version
+         :x-mailer:content-transfer-encoding;
+        bh=I+JFeMB9svh3ZIIGdVlHav4mOQmOa+QTh4VZGVL0a/0=;
+        b=hLukn/U4YkodFZ8CEkuJJmYvpTDXhavKiL1YZ12QApXyCb9xBeYORheXEIQUygjUpL
+         Fy9zyFIWzw3YAiLEa4WUJnC4L+VWq4Nhtua9a1XBQBCK8HZuDITUcmtYcobib1kBg4KE
+         0nBOF7IL6d17HN8QGC+Nn+YTu7JxHSq4cHy8Y=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=subject:from:reply-to:to:cc:in-reply-to:references:content-type
+         :date:message-id:mime-version:x-mailer:content-transfer-encoding;
+        b=WiSerhUAnUL/tGRi8PqwwkXymz8N1Uf58rludUDWzSk+L3KSJqGtAvdv8xYqW8x4PQ
+         Vla/qoLPjcqQQmaLgLKtDvGIL9BFQ86dXrgokJhcrT1qSs2xN3VKGdhm49MOd/HrYZ/F
+         v4wespzjVohrMt2tOP38nOKZpPeu4t1wUJYOQ=
+Received: by 10.204.79.142 with SMTP id p14mr1988895bkk.175.1290172893650;
+        Fri, 19 Nov 2010 05:21:33 -0800 (PST)
+Received: from ?IPv6:::1? (shutemov.name [188.40.19.243])
+        by mx.google.com with ESMTPS id v25sm850549bkt.6.2010.11.19.05.21.30
+        (version=SSLv3 cipher=RC4-MD5);
+        Fri, 19 Nov 2010 05:21:31 -0800 (PST)
+In-Reply-To: <902d76520da2f65e5dc44339dccb07159947f23d.1289789605.git.joe@perches.com>
+X-Mailer: Evolution 2.32.0 (2.32.0-2.fc14) 
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1065301>
+
+On Sun, 2010-11-14 at 19:04 -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  fs/ubifs/scan.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+
+Thanks, I'll pick this up.
+
+--=20
+Best Regards,
+Artem Bityutskiy (=D0=90=D1=80=D1=82=D1=91=D0=BC =D0=91=D0=B8=D1=82=D1=8E=
+=D1=86=D0=BA=D0=B8=D0=B9)
+
+
+
diff --git a/test/corpora/lkml/cur/1382298795.001362:2, b/test/corpora/lkml/cur/1382298795.001362:2,
new file mode 100644 (file)
index 0000000..a723d58
--- /dev/null
@@ -0,0 +1,96 @@
+From: Takashi Iwai <tiwai@suse.de>
+Subject: Re: [PATCH 43/44] sound/core/pcm_lib.c: Remove
+       unnecessary semicolons
+Date: Mon, 22 Nov 2010 07:44:21 +0100
+Lines: 31
+Message-ID: <s5h4ob9svqy.wl%tiwai@suse.de>
+References: <cover.1289789604.git.joe@perches.com>
+       <9fa8e193ce125ef4fd19a952792629c5ee84953f.1289789605.git.joe@perches.com>
+Mime-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka")
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+Cc: alsa-devel@alsa-project.org, Jiri Kosina <trivial@kernel.org>,
+       linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: alsa-devel-bounces@alsa-project.org Mon Nov 22 07:44:31 2010
+Return-path: <alsa-devel-bounces@alsa-project.org>
+Envelope-to: glad-alsa-devel-2@m.gmane.org
+Received: from alsa0.perex.cz ([212.20.107.51])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <alsa-devel-bounces@alsa-project.org>)
+       id 1PKQ8Y-0002qf-F0
+       for glad-alsa-devel-2@m.gmane.org; Mon, 22 Nov 2010 07:44:26 +0100
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 26BFF103863; Mon, 22 Nov 2010 07:44:26 +0100 (CET)
+X-Spam-Checker-Version: SpamAssassin 3.2.4 (2008-01-01) on mail1.perex.cz
+X-Spam-Level: 
+X-Spam-Status: No, score=-8.0 required=5.0 tests=RCVD_IN_DNSWL_HI
+       autolearn=disabled version=3.2.4
+Received: from alsa0.perex.cz (localhost [127.0.0.1])
+       by alsa0.perex.cz (Postfix) with ESMTP id 1E4EF103848;
+       Mon, 22 Nov 2010 07:44:24 +0100 (CET)
+X-Original-To: alsa-devel@alsa-project.org
+Delivered-To: alsa-devel@alsa-project.org
+Received: by alsa0.perex.cz (Postfix, from userid 1000)
+       id 0861E103851; Mon, 22 Nov 2010 07:44:22 +0100 (CET)
+Received: from mx1.suse.de (cantor.suse.de [195.135.220.2])
+       by alsa0.perex.cz (Postfix) with ESMTP id 7007710383C
+       for <alsa-devel@alsa-project.org>; Mon, 22 Nov 2010 07:44:21 +0100 (CET)
+Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.221.2])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by mx1.suse.de (Postfix) with ESMTP id 53C1B947B6;
+       Mon, 22 Nov 2010 07:44:21 +0100 (CET)
+In-Reply-To: <9fa8e193ce125ef4fd19a952792629c5ee84953f.1289789605.git.joe@perches.com>
+User-Agent: Wanderlust/2.15.6 (Almost Unreal) SEMI/1.14.6 (Maruoka)
+       FLIM/1.14.9 (=?UTF-8?B?R29qxY0=?=) APEL/10.7 Emacs/23.1
+       (x86_64-suse-linux-gnu) MULE/6.0 (HANACHIRUSATO)
+X-BeenThere: alsa-devel@alsa-project.org
+X-Mailman-Version: 2.1.9
+Precedence: list
+List-Id: "Alsa-devel mailing list for ALSA developers -
+       http://www.alsa-project.org" <alsa-devel.alsa-project.org>
+List-Unsubscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=unsubscribe>
+List-Archive: <http://mailman.alsa-project.org/pipermail/alsa-devel>
+List-Post: <mailto:alsa-devel@alsa-project.org>
+List-Help: <mailto:alsa-devel-request@alsa-project.org?subject=help>
+List-Subscribe: <http://mailman.alsa-project.org/mailman/listinfo/alsa-devel>, 
+       <mailto:alsa-devel-request@alsa-project.org?subject=subscribe>
+Sender: alsa-devel-bounces@alsa-project.org
+Errors-To: alsa-devel-bounces@alsa-project.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1066369>
+
+At Sun, 14 Nov 2010 19:05:02 -0800,
+Joe Perches wrote:
+> 
+> Signed-off-by: Joe Perches <joe@perches.com>
+
+Applied now.  Thanks.
+
+
+Takashi
+
+
+> ---
+>  sound/core/pcm_lib.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+> diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
+> index a1707cc..b75db8e 100644
+> --- a/sound/core/pcm_lib.c
+> +++ b/sound/core/pcm_lib.c
+> @@ -223,7 +223,7 @@ static void xrun_log(struct snd_pcm_substream *substream,
+>      entry->jiffies = jiffies;
+>      entry->pos = pos;
+>      entry->period_size = runtime->period_size;
+> -    entry->buffer_size = runtime->buffer_size;;
+> +    entry->buffer_size = runtime->buffer_size;
+>      entry->old_hw_ptr = runtime->status->hw_ptr;
+>      entry->hw_ptr_base = runtime->hw_ptr_base;
+>      log->idx = (log->idx + 1) % XRUN_LOG_CNT;
+> -- 
+> 1.7.3.1.g432b3.dirty
+> 
+
+
diff --git a/test/corpora/lkml/cur/1382298795.002635:2, b/test/corpora/lkml/cur/1382298795.002635:2,
new file mode 100644 (file)
index 0000000..3c04b86
--- /dev/null
@@ -0,0 +1,54 @@
+From: Matthew Garrett <mjg59@srcf.ucam.org>
+Subject: Re: [PATCH 19/44] drivers/platform/x86: Remove unnecessary
+       semicolons
+Date: Wed, 24 Nov 2010 16:52:46 +0000
+Lines: 4
+Message-ID: <20101124165246.GH2150@srcf.ucam.org>
+References: <cover.1289789604.git.joe@perches.com> <eda82bcfaad265fc5cd3901bc4f41bfcfac2403b.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Jiri Kosina <trivial@kernel.org>,
+       Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>,
+       Daniel Oliveira Nascimento <don@syst.com.br>,
+       Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>,
+       platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org,
+       ibm-acpi-devel@lists.sourceforge.net
+To: Joe Perches <joe@perches.com>
+X-From: platform-driver-x86-owner@vger.kernel.org Wed Nov 24 17:53:05 2010
+Return-path: <platform-driver-x86-owner@vger.kernel.org>
+Envelope-to: gldpxd-platform-driver-x86@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <platform-driver-x86-owner@vger.kernel.org>)
+       id 1PLIae-0001zN-Vf
+       for gldpxd-platform-driver-x86@lo.gmane.org; Wed, 24 Nov 2010 17:53:05 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754012Ab0KXQxE (ORCPT
+       <rfc822;gldpxd-platform-driver-x86@m.gmane.org>);
+       Wed, 24 Nov 2010 11:53:04 -0500
+Received: from cavan.codon.org.uk ([93.93.128.6]:37338 "EHLO
+       cavan.codon.org.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753985Ab0KXQxD (ORCPT
+       <rfc822;platform-driver-x86@vger.kernel.org>);
+       Wed, 24 Nov 2010 11:53:03 -0500
+Received: from mjg59 by cavan.codon.org.uk with local (Exim 4.69)
+       (envelope-from <mjg59@cavan.codon.org.uk>)
+       id 1PLIaM-0000fC-HX; Wed, 24 Nov 2010 16:52:46 +0000
+Content-Disposition: inline
+In-Reply-To: <eda82bcfaad265fc5cd3901bc4f41bfcfac2403b.1289789604.git.joe@perches.com>
+User-Agent: Mutt/1.5.18 (2008-05-17)
+X-SA-Exim-Connect-IP: <locally generated>
+X-SA-Exim-Mail-From: mjg59@cavan.codon.org.uk
+X-SA-Exim-Scanned: No (on cavan.codon.org.uk); SAEximRunCond expanded to false
+Sender: platform-driver-x86-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <platform-driver-x86.vger.kernel.org>
+X-Mailing-List: platform-driver-x86@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1067655>
+
+Applied, thanks.
+
+-- 
+Matthew Garrett | mjg59@srcf.ucam.org
+
+
diff --git a/test/corpora/lkml/cur/1382298796.001941:2, b/test/corpora/lkml/cur/1382298796.001941:2,
new file mode 100644 (file)
index 0000000..c65a72f
--- /dev/null
@@ -0,0 +1,73 @@
+From: Chris Ball <cjb@laptop.org>
+Subject: Re: [PATCH 11/44] drivers/mmc: Remove unnecessary semicolons
+Date: Sun, 5 Dec 2010 03:32:32 +0000
+Lines: 33
+Message-ID: <20101205033232.GD24000@void.printf.net>
+References: <cover.1289789604.git.joe@perches.com> <6391af02ba7ec4a76c5c5f462d8013fc1f52f999.1289789604.git.joe@perches.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: Jiri Kosina <trivial@kernel.org>, linux-mmc@vger.kernel.org,
+       linux-kernel@vger.kernel.org
+To: Joe Perches <joe@perches.com>
+X-From: linux-mmc-owner@vger.kernel.org Sun Dec 05 04:32:38 2010
+Return-path: <linux-mmc-owner@vger.kernel.org>
+Envelope-to: glkm-linux-mmc@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-mmc-owner@vger.kernel.org>)
+       id 1PP5L3-0005AB-4b
+       for glkm-linux-mmc@lo.gmane.org; Sun, 05 Dec 2010 04:32:37 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752522Ab0LEDcg (ORCPT <rfc822;glkm-linux-mmc@m.gmane.org>);
+       Sat, 4 Dec 2010 22:32:36 -0500
+Received: from void.printf.net ([89.145.121.20]:42897 "EHLO void.printf.net"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752473Ab0LEDcf (ORCPT <rfc822;linux-mmc@vger.kernel.org>);
+       Sat, 4 Dec 2010 22:32:35 -0500
+Received: from chris by void.printf.net with local (Exim 4.69)
+       (envelope-from <chris@void.printf.net>)
+       id 1PP5Ky-0006Ly-KC; Sun, 05 Dec 2010 03:32:32 +0000
+Content-Disposition: inline
+In-Reply-To: <6391af02ba7ec4a76c5c5f462d8013fc1f52f999.1289789604.git.joe@perches.com>
+User-Agent: Mutt/1.5.18 (2008-05-17)
+Sender: linux-mmc-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-mmc.vger.kernel.org>
+X-Mailing-List: linux-mmc@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1071959>
+
+Hi Joe,
+
+On Sun, Nov 14, 2010 at 07:04:30PM -0800, Joe Perches wrote:
+> Signed-off-by: Joe Perches <joe@perches.com>
+> ---
+>  drivers/mmc/host/davinci_mmc.c |    2 +-
+>  1 files changed, 1 insertions(+), 1 deletions(-)
+> 
+> diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c
+> index e15547c..b643dde 100644
+> --- a/drivers/mmc/host/davinci_mmc.c
+> +++ b/drivers/mmc/host/davinci_mmc.c
+> @@ -480,7 +480,7 @@ static void mmc_davinci_send_dma_request(struct mmc_davinci_host *host,
+>      struct scatterlist      *sg;
+>      unsigned                sg_len;
+>      unsigned                bytes_left = host->bytes_left;
+> -    const unsigned          shift = ffs(rw_threshold) - 1;;
+> +    const unsigned          shift = ffs(rw_threshold) - 1;
+>  
+>      if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) {
+>              template = &host->tx_template;
+> -- 
+
+Pushed to mmc-next for .38, thanks.
+
+-- 
+Chris Ball   <cjb@laptop.org>   <http://printf.net/>
+One Laptop Per Child
+--
+To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004526:2, b/test/corpora/lkml/cur/1382298805.004526:2,
new file mode 100644 (file)
index 0000000..05dbb9d
--- /dev/null
@@ -0,0 +1,89 @@
+From: Colin Cross <ccross@android.com>
+Subject: [PATCH] ARM: vfp: Always save VFP state in vfp_pm_suspend
+Date: Sun, 13 Feb 2011 15:13:33 -0800
+Lines: 45
+Message-ID: <1297638813-1315-1-git-send-email-ccross@android.com>
+Cc: Colin Cross <ccross@android.com>,
+       Catalin Marinas <catalin.marinas@arm.com>,
+       Russell King <linux@arm.linux.org.uk>,
+       linux-kernel@vger.kernel.org
+To: linux-arm-kernel@lists.infradead.org
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 00:14:16 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1Pol8x-0007RZ-Hw
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 00:14:15 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1755242Ab1BMXOA (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Sun, 13 Feb 2011 18:14:00 -0500
+Received: from smtp-out.google.com ([74.125.121.67]:10204 "EHLO
+       smtp-out.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753669Ab1BMXN5 (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Sun, 13 Feb 2011 18:13:57 -0500
+Received: from hpaq1.eem.corp.google.com (hpaq1.eem.corp.google.com [172.25.149.1])
+       by smtp-out.google.com with ESMTP id p1DNDjFc030645;
+       Sun, 13 Feb 2011 15:13:45 -0800
+Received: from walnut.mtv.corp.google.com (walnut.mtv.corp.google.com [172.18.102.62])
+       by hpaq1.eem.corp.google.com with ESMTP id p1DNDdKg016468;
+       Sun, 13 Feb 2011 15:13:39 -0800
+Received: by walnut.mtv.corp.google.com (Postfix, from userid 99897)
+       id 1F65025772D; Sun, 13 Feb 2011 15:13:39 -0800 (PST)
+X-Mailer: git-send-email 1.7.3.1
+X-System-Of-Record: true
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099558>
+
+vfp_pm_suspend should save the VFP state any time there is
+a last_VFP_context.  If it only saves when the VFP is enabled,
+the state can get lost when, on a UP system:
+   Thread 1 uses the VFP
+   Context switch occurs to thread 2, VFP is disabled but the
+      VFP context is not saved to allow lazy save and restore
+   Thread 2 initiates suspend
+   vfp_pm_suspend is called with the VFP disabled, but the
+      context has not been saved.
+
+Modify vfp_pm_suspend to save the VFP context whenever
+last_VFP_context is set.
+
+Cc: Catalin Marinas <catalin.marinas@arm.com>
+Signed-off-by: Colin Cross <ccross@android.com>
+---
+ arch/arm/vfp/vfpmodule.c |   11 +++++------
+ 1 files changed, 5 insertions(+), 6 deletions(-)
+
+diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c
+index 66bf8d1..7aea616 100644
+--- a/arch/arm/vfp/vfpmodule.c
++++ b/arch/arm/vfp/vfpmodule.c
+@@ -415,13 +415,12 @@ static int vfp_pm_suspend(struct sys_device *dev, pm_message_t state)
+       struct thread_info *ti = current_thread_info();
+       u32 fpexc = fmrx(FPEXC);
+-      /* if vfp is on, then save state for resumption */
+-      if (fpexc & FPEXC_EN) {
++      /* save state for resume */
++      if (last_VFP_context[ti->cpu]) {
+               printk(KERN_DEBUG "%s: saving vfp state\n", __func__);
+-              vfp_save_state(&ti->vfpstate, fpexc);
+-
+-              /* disable, just in case */
+-              fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
++              fmxr(FPEXC, fpexc | FPEXC_EN);
++              vfp_save_state(last_VFP_context[ti->cpu], fpexc);
++              fmxr(FPEXC, fpexc & ~FPEXC_EN);
+       }
+       /* clear any information we had about last context state */
+-- 
+1.7.3.1
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004551:2, b/test/corpora/lkml/cur/1382298805.004551:2,
new file mode 100644 (file)
index 0000000..0e21b16
--- /dev/null
@@ -0,0 +1,90 @@
+From: Justin Mattock <justinmattock-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
+Subject: bluetooth disabled with current 2.6.38-rc4
+Date: Sun, 13 Feb 2011 17:30:04 -0800
+Lines: 30
+Message-ID: <1A8743E5-65EA-4625-82FD-658C9722629F@gmail.com>
+Mime-Version: 1.0 (Apple Message framework v936)
+Content-Type: text/plain; charset=US-ASCII; format=flowed; delsp=yes
+Content-Transfer-Encoding: 7bit
+Cc: "linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mailing List" 
+       <linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+To: linux-bluetooth-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+X-From: linux-bluetooth-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Mon Feb 14 02:30:20 2011
+Return-path: <linux-bluetooth-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
+Envelope-to: glbk-linux-bluetooth-1dZseelyfdZg9hUCZPvPmw@public.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-bluetooth-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>)
+       id 1PonGb-0000BP-Ns
+       for glbk-linux-bluetooth-1dZseelyfdZg9hUCZPvPmw@public.gmane.org; Mon, 14 Feb 2011 02:30:18 +0100
+Received: (majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org) by vger.kernel.org via listexpand
+       id S1755415Ab1BNBaO (ORCPT
+       <rfc822;glbk-linux-bluetooth@m.gmane.org>);
+       Sun, 13 Feb 2011 20:30:14 -0500
+Received: from mail-iw0-f174.google.com ([209.85.214.174]:33676 "EHLO
+       mail-iw0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1755315Ab1BNBaJ (ORCPT
+       <rfc822;linux-bluetooth-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>);
+       Sun, 13 Feb 2011 20:30:09 -0500
+Received: by iwn9 with SMTP id 9so4315727iwn.19
+        for <multiple recipients>; Sun, 13 Feb 2011 17:30:08 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:message-id:from:to:content-type
+         :content-transfer-encoding:mime-version:subject:date:cc:x-mailer;
+        bh=Hm2xpT9F5uspvKowKW51PBMJXHVySz8oO68WD+15vw4=;
+        b=CYWbVChg8u/vPwCQijLtu4qwy88RnlkiXipfYaorEsKoqnL/riJzvgVjtYz3uoSWE5
+         m9IsBgZBGd2zuZMuDEfGiLQo1h5ReLbCsQ2FSLRM8dW15g3xENkK0Zd86EHNATbnU4CQ
+         YMF3gYQHr5BffWBu8xllNHnUKHzMGZz827BEk=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=message-id:from:to:content-type:content-transfer-encoding
+         :mime-version:subject:date:cc:x-mailer;
+        b=DU4h/EWn8O7B48JT0DuiMTjHma3v+7cSup8eYqLmOgYopjvr42kO9BACgxHMR/mpI/
+         sa4AtFAuWg3TkZkPOjJg2SiPiUGQcj7kqjycvHWvWiQHEE6tLEH7g6aGF7ojSTrsiJxN
+         zpjZBS0EvbDtIrQf8YAV9eFQJSkQ5yYXmK00c=
+Received: by 10.42.228.201 with SMTP id jf9mr3782213icb.471.1297647007933;
+        Sun, 13 Feb 2011 17:30:07 -0800 (PST)
+Received: from [10.0.0.13] ([76.89.133.205])
+        by mx.google.com with ESMTPS id y8sm1925328ica.2.2011.02.13.17.30.06
+        (version=TLSv1/SSLv3 cipher=OTHER);
+        Sun, 13 Feb 2011 17:30:07 -0800 (PST)
+X-Mailer: Apple Mail (2.936)
+Sender: linux-bluetooth-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Precedence: bulk
+List-ID: <linux-bluetooth.vger.kernel.org>
+X-Mailing-List: linux-bluetooth-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099583>
+
+maybe I missed something, but my bluetooth is just not functioning with
+2.6.38-rc4(works with 2.6.37-rc4)
+
+I've done a bisect on this, but was pointed to:
+c0e45c1ca3162acb2e77b3d9e152ce6e7b6fa3f5
+but doesn't look correct to me
+
+here is what I am seeing with the bluetooth-applet etc..:
+
+working correctly:
+http://www.flickr.com/photos/44066293@N08/5443727238/
+
+not working:
+http://www.flickr.com/photos/44066293@N08/5443124859/
+
+my /var/log/daemon.log shows:
+
+Feb 13 17:12:22 Linux-2 acpid: 1 client rule loaded
+Feb 13 17:12:23 Linux-2 bluetoothd[1950]: HCI dev 0 registered
+Feb 13 17:12:23 Linux-2 bluetoothd[1950]: Listening for HCI events on  
+hci0
+Feb 13 17:12:23 Linux-2 bluetoothd[1950]: HCI dev 0 up
+Feb 13 17:12:23 Linux-2 bluetoothd[1950]: Unable to find matching  
+adapter
+
+I can try at another bisect, but might take some time.. let me know if  
+there is something I can test
+or do.
+
+Justin P. Mattock
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004613:2, b/test/corpora/lkml/cur/1382298805.004613:2,
new file mode 100644 (file)
index 0000000..5a12c8b
--- /dev/null
@@ -0,0 +1,94 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 1/6] x86: move ioapic_irq_destination_types
+Date: Mon, 14 Feb 2011 11:00:07 +0100
+Lines: 55
+Message-ID: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:00:33 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovEP-0006ED-4Z
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:00:33 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752832Ab1BNKAX (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:00:23 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38305 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752268Ab1BNKAW (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:22 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id D2234950DB2;
+       Mon, 14 Feb 2011 10:59:03 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099645>
+
+This enum is used also by non-ioapic code, e.g apic_noop,
+so its better kept in apicdef.h.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/include/asm/apicdef.h |   12 ++++++++++++
+ arch/x86/include/asm/io_apic.h |   11 -----------
+ 2 files changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/arch/x86/include/asm/apicdef.h b/arch/x86/include/asm/apicdef.h
+index 47a30ff..2de3e95 100644
+--- a/arch/x86/include/asm/apicdef.h
++++ b/arch/x86/include/asm/apicdef.h
+@@ -426,4 +426,16 @@ struct local_apic {
+ #else
+  #define BAD_APICID 0xFFFFu
+ #endif
++
++enum ioapic_irq_destination_types {
++      dest_Fixed = 0,
++      dest_LowestPrio = 1,
++      dest_SMI = 2,
++      dest__reserved_1 = 3,
++      dest_NMI = 4,
++      dest_INIT = 5,
++      dest__reserved_2 = 6,
++      dest_ExtINT = 7
++};
++
+ #endif /* _ASM_X86_APICDEF_H */
+diff --git a/arch/x86/include/asm/io_apic.h b/arch/x86/include/asm/io_apic.h
+index f327d38..e1a9b0e 100644
+--- a/arch/x86/include/asm/io_apic.h
++++ b/arch/x86/include/asm/io_apic.h
+@@ -63,17 +63,6 @@ union IO_APIC_reg_03 {
+       } __attribute__ ((packed)) bits;
+ };
+-enum ioapic_irq_destination_types {
+-      dest_Fixed = 0,
+-      dest_LowestPrio = 1,
+-      dest_SMI = 2,
+-      dest__reserved_1 = 3,
+-      dest_NMI = 4,
+-      dest_INIT = 5,
+-      dest__reserved_2 = 6,
+-      dest_ExtINT = 7
+-};
+-
+ struct IO_APIC_route_entry {
+       __u32   vector          :  8,
+               delivery_mode   :  3,   /* 000: FIXED
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004614:2, b/test/corpora/lkml/cur/1382298805.004614:2,
new file mode 100644 (file)
index 0000000..ce850ff
--- /dev/null
@@ -0,0 +1,89 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 2/6] x86: ifdef enable_IR_x2apic() out
+Date: Mon, 14 Feb 2011 11:00:08 +0100
+Lines: 48
+Message-ID: <1297677612-12405-2-git-send-email-henne@nachtwindheim.de>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:00:34 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovEP-0006ED-LT
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:00:34 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752908Ab1BNKA1 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:00:27 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38321 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752778Ab1BNKAX (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:23 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id 1AFC7950DB3;
+       Mon, 14 Feb 2011 10:59:06 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099646>
+
+The only caller of enable_IR_x2apic() is probe_64.c, which is only
+compiled on x86-64 bit machines.
+This function causes compilation problems on 32-bit machines with no
+io-apic enabled, so we remove it from 32s and keep the way it was on 64s.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/include/asm/apic.h |    2 ++
+ arch/x86/kernel/apic/apic.c |    2 +-
+ 2 files changed, 3 insertions(+), 1 deletions(-)
+
+diff --git a/arch/x86/include/asm/apic.h b/arch/x86/include/asm/apic.h
+index b8a3484..b1d77e1 100644
+--- a/arch/x86/include/asm/apic.h
++++ b/arch/x86/include/asm/apic.h
+@@ -216,7 +216,9 @@ static inline void x2apic_force_phys(void)
+ #define       x2apic_supported()      0
+ #endif
++#ifdef CONFIG_X86_64
+ extern void enable_IR_x2apic(void);
++#endif
+ extern int get_physical_broadcast(void);
+diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
+index 306386f..27a7497 100644
+--- a/arch/x86/kernel/apic/apic.c
++++ b/arch/x86/kernel/apic/apic.c
+@@ -1467,6 +1467,7 @@ int __init enable_IR(void)
+       return 0;
+ }
++#ifdef CONFIG_X86_64
+ void __init enable_IR_x2apic(void)
+ {
+       unsigned long flags;
+@@ -1540,7 +1541,6 @@ out:
+               pr_info("Not enabling x2apic, Intr-remapping init failed.\n");
+ }
+-#ifdef CONFIG_X86_64
+ /*
+  * Detect and enable local APICs on non-SMP boards.
+  * Original code written by Keir Fraser.
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004615:2, b/test/corpora/lkml/cur/1382298805.004615:2,
new file mode 100644 (file)
index 0000000..cceabb5
--- /dev/null
@@ -0,0 +1,64 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 4/6] x86: add dummy mp_save_irq()
+Date: Mon, 14 Feb 2011 11:00:10 +0100
+Lines: 23
+Message-ID: <1297677612-12405-4-git-send-email-henne@nachtwindheim.de>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:00:36 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovEQ-0006ED-6t
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:00:34 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752958Ab1BNKA3 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:00:29 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38321 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752592Ab1BNKAY (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:24 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id 4B47D950DB5;
+       Mon, 14 Feb 2011 10:59:08 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099647>
+
+mmparse.c needs a mp_save_irq() function, which is only available
+when CONFIG_X86_IO_APIC is defined. So here we give it a dummy one.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/include/asm/io_apic.h |    1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+
+diff --git a/arch/x86/include/asm/io_apic.h b/arch/x86/include/asm/io_apic.h
+index e1a9b0e..7af0f5f 100644
+--- a/arch/x86/include/asm/io_apic.h
++++ b/arch/x86/include/asm/io_apic.h
+@@ -188,6 +188,7 @@ static inline int mp_find_ioapic(u32 gsi) { return 0; }
+ struct io_apic_irq_attr;
+ static inline int io_apic_set_pci_routing(struct device *dev, int irq,
+                struct io_apic_irq_attr *irq_attr) { return 0; }
++static inline void mp_save_irq(struct mpc_intsrc *m) { }
+ #endif
+ #endif /* _ASM_X86_IO_APIC_H */
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004617:2, b/test/corpora/lkml/cur/1382298805.004617:2,
new file mode 100644 (file)
index 0000000..71f2f1f
--- /dev/null
@@ -0,0 +1,67 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 6/6] x86: makes X86_UP_IOAPIC work again
+Date: Mon, 14 Feb 2011 11:00:12 +0100
+Lines: 26
+Message-ID: <1297677612-12405-6-git-send-email-henne@nachtwindheim.de>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:01:26 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovFD-0006cV-Si
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:01:24 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753137Ab1BNKAo (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:00:44 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38361 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752877Ab1BNKA0 (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:26 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id 26BC1950DB7;
+       Mon, 14 Feb 2011 10:59:10 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099649>
+
+This fixes a typo, which made CONFIG_X86_UP_IOAPIC defunctional,
+in commit 7cd92366a593246650cc7d6198e2c7d3af8c1d8a.
+
+This has been successfully tested.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/Kconfig |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
+index 95c36c4..66c6801 100644
+--- a/arch/x86/Kconfig
++++ b/arch/x86/Kconfig
+@@ -811,7 +811,7 @@ config X86_LOCAL_APIC
+ config X86_IO_APIC
+       def_bool y
+-      depends on X86_64 || SMP || X86_32_NON_STANDARD || X86_UP_APIC
++      depends on X86_64 || SMP || X86_32_NON_STANDARD || X86_UP_IOAPIC
+ config X86_VISWS_APIC
+       def_bool y
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004618:2, b/test/corpora/lkml/cur/1382298805.004618:2,
new file mode 100644 (file)
index 0000000..620bed1
--- /dev/null
@@ -0,0 +1,70 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 5/6] x86: ifdef ioapic related function out
+Date: Mon, 14 Feb 2011 11:00:11 +0100
+Lines: 29
+Message-ID: <1297677612-12405-5-git-send-email-henne@nachtwindheim.de>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:01:27 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovFE-0006cV-Cn
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:01:24 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753212Ab1BNKAq (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:00:46 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38350 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752841Ab1BNKAZ (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:25 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id 37BBE950DB6;
+       Mon, 14 Feb 2011 10:59:09 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099650>
+
+arch_disable_smp_config() is an IO-APIC related function on x86,
+and should only be needed if SMP is enabled.
+But the IO-APIC code calls it when the parameter "noapic" is given to
+the kernel, which doesn't mean SMP is enabled.
+
+Anyway this fixes compilation on x86_32 UP systems with APIC and no IO-APIC.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/kernel/apic/apic.c |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
+index 999c531..4998f0a 100644
+--- a/arch/x86/kernel/apic/apic.c
++++ b/arch/x86/kernel/apic/apic.c
+@@ -1218,7 +1218,9 @@ void __cpuinit setup_local_APIC(void)
+               rdtscll(tsc);
+       if (disable_apic) {
++#ifdef CONFIG_X86_IO_APIC
+               arch_disable_smp_support();
++#endif
+               return;
+       }
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004619:2, b/test/corpora/lkml/cur/1382298805.004619:2,
new file mode 100644 (file)
index 0000000..f7895dd
--- /dev/null
@@ -0,0 +1,99 @@
+From: Henrik Kretzschmar <henne@nachtwindheim.de>
+Subject: [PATCH 3/6] x86: ifdef INTR_REMAP code out
+Date: Mon, 14 Feb 2011 11:00:09 +0100
+Lines: 58
+Message-ID: <1297677612-12405-3-git-send-email-henne@nachtwindheim.de>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Cc: tglx@linutronix.de, hpa@zytor.com, x86@kernel.org, tj@kernel.org,
+       yinghai@kernel.org, ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org, henne@nachtwindheim.de
+To: mingo@readhat.com
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 11:01:30 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PovFF-0006cV-Te
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 11:01:26 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753126Ab1BNKBX (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 05:01:23 -0500
+Received: from server103.greatnet.de ([83.133.97.6]:38330 "EHLO
+       server103.greatnet.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752268Ab1BNKAX (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 05:00:23 -0500
+Received: from localhost.localdomain (cmnz-d9bab6be.pool.mediaWays.net [217.186.182.190])
+       (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+       (No client certificate requested)
+       by server103.greatnet.de (Postfix) with ESMTPSA id 29517950DB4;
+       Mon, 14 Feb 2011 10:59:07 +0100 (CET)
+X-Mailer: git-send-email 1.7.1
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099651>
+
+Interrupt remapping is only available on 64-bit machines,
+so it has no place in lapic_resume() for 32bit machines.
+
+Compilation on 32bit machines would produce errors here.
+
+Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+---
+ arch/x86/kernel/apic/apic.c |    8 +++++++-
+ 1 files changed, 7 insertions(+), 1 deletions(-)
+
+diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
+index 27a7497..999c531 100644
+--- a/arch/x86/kernel/apic/apic.c
++++ b/arch/x86/kernel/apic/apic.c
+@@ -2109,12 +2109,15 @@ static int lapic_resume(struct sys_device *dev)
+       unsigned long flags;
+       int maxlvt;
+       int ret = 0;
+-      struct IO_APIC_route_entry **ioapic_entries = NULL;
+       if (!apic_pm_state.active)
+               return 0;
+       local_irq_save(flags);
++
++#ifdef CONFIG_INTR_REMAP
++      struct IO_APIC_route_entry **ioapic_entries = NULL;
++
+       if (intr_remapping_enabled) {
+               ioapic_entries = alloc_ioapic_entries();
+               if (!ioapic_entries) {
+@@ -2133,6 +2136,7 @@ static int lapic_resume(struct sys_device *dev)
+               mask_IO_APIC_setup(ioapic_entries);
+               legacy_pic->mask_all();
+       }
++#endif
+       if (x2apic_mode)
+               enable_x2apic();
+@@ -2173,6 +2177,7 @@ static int lapic_resume(struct sys_device *dev)
+       apic_write(APIC_ESR, 0);
+       apic_read(APIC_ESR);
++#ifdef CONFIG_INTR_REMAP
+       if (intr_remapping_enabled) {
+               reenable_intr_remapping(x2apic_mode);
+               legacy_pic->restore_mask();
+@@ -2180,6 +2185,7 @@ static int lapic_resume(struct sys_device *dev)
+               free_ioapic_entries(ioapic_entries);
+       }
+ restore:
++#endif
+       local_irq_restore(flags);
+       return ret;
+-- 
+1.7.2.3
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004636:2, b/test/corpora/lkml/cur/1382298805.004636:2,
new file mode 100644 (file)
index 0000000..c14d42c
--- /dev/null
@@ -0,0 +1,93 @@
+From: Vasiliy Kulikov <segoon@openwall.com>
+Subject: [PATCH] core: dev: don't call BUG() on bad input
+Date: Mon, 14 Feb 2011 13:56:06 +0300
+Lines: 36
+Message-ID: <1297680967-11893-1-git-send-email-segoon@openwall.com>
+Cc: "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <eric.dumazet@gmail.com>,
+       Tom Herbert <therbert@google.com>,
+       Changli Gao <xiaosuo@gmail.com>,
+       Jesse Gross <jesse@nicira.com>, netdev@vger.kernel.org
+To: linux-kernel@vger.kernel.org
+X-From: netdev-owner@vger.kernel.org Mon Feb 14 11:56:26 2011
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1Pow6Q-0007p5-UJ
+       for linux-netdev-2@lo.gmane.org; Mon, 14 Feb 2011 11:56:23 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753243Ab1BNK4O (ORCPT <rfc822;linux-netdev-2@m.gmane.org>);
+       Mon, 14 Feb 2011 05:56:14 -0500
+Received: from mail-bw0-f46.google.com ([209.85.214.46]:60909 "EHLO
+       mail-bw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1752645Ab1BNK4M (ORCPT
+       <rfc822;netdev@vger.kernel.org>); Mon, 14 Feb 2011 05:56:12 -0500
+Received: by bwz15 with SMTP id 15so5332720bwz.19
+        for <multiple recipients>; Mon, 14 Feb 2011 02:56:11 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:sender:from:to:cc:subject:date:message-id
+         :x-mailer;
+        bh=YQn7OCqAZuXaSsRtgaQYckH74o43k6Rppt54AR6UzDo=;
+        b=CxfBmTAbcMf7ySl3szqU/hLEMbY7aJ+FjefneMcTm/AmBnyihy20JuV2k0yYJzcIBi
+         9+2npC4H9oJn7/ocVARq88j9ZA/4firOi9ZddgGu6c8+o0tWoZylA1ehtHzzk+4I173l
+         H8guqK5rplkryj6+PStELYYt36SpAVfaL2EdY=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=sender:from:to:cc:subject:date:message-id:x-mailer;
+        b=G0AqVbcip2oFA2IqAQa6TWwQydu/mJFzt98tGkR1fVNl3m+HaKY433gNCR+Dqdv0gA
+         SGL/R3HRiBBBku/GM4x3gQ8SoAFZiREw6PDtkU55l/mk+yS+v+8YTq7/InPxHoHeTWsv
+         pX0mWUI2HtTXKALBiM+nLsnBWtcC8yInYtyeQ=
+Received: by 10.204.61.73 with SMTP id s9mr6247440bkh.185.1297680970948;
+        Mon, 14 Feb 2011 02:56:10 -0800 (PST)
+Received: from localhost (ppp91-77-40-235.pppoe.mtu-net.ru [91.77.40.235])
+        by mx.google.com with ESMTPS id u23sm1686152bkw.9.2011.02.14.02.56.09
+        (version=TLSv1/SSLv3 cipher=OTHER);
+        Mon, 14 Feb 2011 02:56:10 -0800 (PST)
+X-Mailer: git-send-email 1.7.0.4
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099668>
+
+alloc_netdev() may be called with too long name (more that IFNAMSIZ bytes).
+Currently this leads to BUG().  Other insane inputs (bad txqs, rxqs) and
+even OOM don't lead to BUG().  Made alloc_netdev() return NULL, like on
+other errors.
+
+Signed-off-by: Vasiliy Kulikov <segoon@openwall.com>
+---
+ Compile tested.
+
+ net/core/dev.c |    5 ++++-
+ 1 files changed, 4 insertions(+), 1 deletions(-)
+
+diff --git a/net/core/dev.c b/net/core/dev.c
+index 6392ea0..12ef4b0 100644
+--- a/net/core/dev.c
++++ b/net/core/dev.c
+@@ -5761,7 +5761,10 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
+       size_t alloc_size;
+       struct net_device *p;
+-      BUG_ON(strlen(name) >= sizeof(dev->name));
++      if (strnlen(name, sizeof(dev->name)) >= sizeof(dev->name)) {
++              pr_err("alloc_netdev: Too long device name \n");
++              return NULL;
++      }
+       if (txqs < 1) {
+               pr_err("alloc_netdev: Unable to allocate device "
+-- 
+1.7.0.4
+
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004638:2, b/test/corpora/lkml/cur/1382298805.004638:2,
new file mode 100644 (file)
index 0000000..551ce5b
--- /dev/null
@@ -0,0 +1,88 @@
+From: Ingo Molnar <mingo@elte.hu>
+Subject: Re: [PATCH 5/6] x86: ifdef ioapic related function out
+Date: Mon, 14 Feb 2011 12:00:39 +0100
+Lines: 34
+Message-ID: <20110214110039.GA7140@elte.hu>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+ <1297677612-12405-5-git-send-email-henne@nachtwindheim.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: mingo@readhat.com, tglx@linutronix.de, hpa@zytor.com,
+       x86@kernel.org, tj@kernel.org, yinghai@kernel.org,
+       ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org
+To: Henrik Kretzschmar <henne@nachtwindheim.de>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 12:01:04 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PowAx-0001Lu-Lk
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 12:01:04 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752962Ab1BNLA5 (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 06:00:57 -0500
+Received: from mx3.mail.elte.hu ([157.181.1.138]:38470 "EHLO mx3.mail.elte.hu"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752556Ab1BNLAz (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 06:00:55 -0500
+Received: from elvis.elte.hu ([157.181.1.14])
+       by mx3.mail.elte.hu with esmtp (Exim)
+       id 1PowAd-0003Lr-F7
+       from <mingo@elte.hu>; Mon, 14 Feb 2011 12:00:48 +0100
+Received: by elvis.elte.hu (Postfix, from userid 1004)
+       id 7726F3E236B; Mon, 14 Feb 2011 12:00:41 +0100 (CET)
+Content-Disposition: inline
+In-Reply-To: <1297677612-12405-5-git-send-email-henne@nachtwindheim.de>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+Received-SPF: neutral (mx3: 157.181.1.14 is neither permitted nor denied by domain of elte.hu) client-ip=157.181.1.14; envelope-from=mingo@elte.hu; helo=elvis.elte.hu;
+X-ELTE-SpamScore: -2.0
+X-ELTE-SpamLevel: 
+X-ELTE-SpamCheck: no
+X-ELTE-SpamVersion: ELTE 2.0 
+X-ELTE-SpamCheck-Details: score=-2.0 required=5.9 tests=BAYES_00 autolearn=no SpamAssassin version=3.2.5
+       -2.0 BAYES_00               BODY: Bayesian spam probability is 0 to 1%
+       [score: 0.0000]
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099670>
+
+
+* Henrik Kretzschmar <henne@nachtwindheim.de> wrote:
+
+> arch_disable_smp_config() is an IO-APIC related function on x86,
+> and should only be needed if SMP is enabled.
+> But the IO-APIC code calls it when the parameter "noapic" is given to
+> the kernel, which doesn't mean SMP is enabled.
+> 
+> Anyway this fixes compilation on x86_32 UP systems with APIC and no IO-APIC.
+> 
+> Signed-off-by: Henrik Kretzschmar <henne@nachtwindheim.de>
+> ---
+>  arch/x86/kernel/apic/apic.c |    2 ++
+>  1 files changed, 2 insertions(+), 0 deletions(-)
+> 
+> diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
+> index 999c531..4998f0a 100644
+> --- a/arch/x86/kernel/apic/apic.c
+> +++ b/arch/x86/kernel/apic/apic.c
+> @@ -1218,7 +1218,9 @@ void __cpuinit setup_local_APIC(void)
+>              rdtscll(tsc);
+>  
+>      if (disable_apic) {
+> +#ifdef CONFIG_X86_IO_APIC
+>              arch_disable_smp_support();
+> +#endif
+
+Why not make the arch_disable_smp_support() call generic in the 
+arch/x86/include/asm/smp.h file (via an inline helper) and thus
+avoid an ugly #ifdef in the .c file?
+
+Thanks,
+
+       Ingo
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004639:2, b/test/corpora/lkml/cur/1382298805.004639:2,
new file mode 100644 (file)
index 0000000..56118aa
--- /dev/null
@@ -0,0 +1,95 @@
+From: Ingo Molnar <mingo@elte.hu>
+Subject: Re: [PATCH 3/6] x86: ifdef INTR_REMAP code out
+Date: Mon, 14 Feb 2011 12:02:31 +0100
+Lines: 41
+Message-ID: <20110214110231.GB7140@elte.hu>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+ <1297677612-12405-3-git-send-email-henne@nachtwindheim.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: mingo@readhat.com, tglx@linutronix.de, hpa@zytor.com,
+       x86@kernel.org, tj@kernel.org, yinghai@kernel.org,
+       ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org
+To: Henrik Kretzschmar <henne@nachtwindheim.de>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 12:02:52 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PowCg-00022G-BR
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 12:02:50 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1752997Ab1BNLCn (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 06:02:43 -0500
+Received: from mx3.mail.elte.hu ([157.181.1.138]:38974 "EHLO mx3.mail.elte.hu"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1751314Ab1BNLCl (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 06:02:41 -0500
+Received: from elvis.elte.hu ([157.181.1.14])
+       by mx3.mail.elte.hu with esmtp (Exim)
+       id 1PowCQ-0003df-Gk
+       from <mingo@elte.hu>; Mon, 14 Feb 2011 12:02:34 +0100
+Received: by elvis.elte.hu (Postfix, from userid 1004)
+       id 0D9343E2369; Mon, 14 Feb 2011 12:02:32 +0100 (CET)
+Content-Disposition: inline
+In-Reply-To: <1297677612-12405-3-git-send-email-henne@nachtwindheim.de>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+Received-SPF: neutral (mx3: 157.181.1.14 is neither permitted nor denied by domain of elte.hu) client-ip=157.181.1.14; envelope-from=mingo@elte.hu; helo=elvis.elte.hu;
+X-ELTE-SpamScore: -2.0
+X-ELTE-SpamLevel: 
+X-ELTE-SpamCheck: no
+X-ELTE-SpamVersion: ELTE 2.0 
+X-ELTE-SpamCheck-Details: score=-2.0 required=5.9 tests=BAYES_00 autolearn=no SpamAssassin version=3.2.5
+       -2.0 BAYES_00               BODY: Bayesian spam probability is 0 to 1%
+       [score: 0.0000]
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099671>
+
+
+* Henrik Kretzschmar <henne@nachtwindheim.de> wrote:
+
+> +#ifdef CONFIG_INTR_REMAP
+> +    struct IO_APIC_route_entry **ioapic_entries = NULL;
+> +
+>      if (intr_remapping_enabled) {
+>              ioapic_entries = alloc_ioapic_entries();
+>              if (!ioapic_entries) {
+> @@ -2133,6 +2136,7 @@ static int lapic_resume(struct sys_device *dev)
+>              mask_IO_APIC_setup(ioapic_entries);
+>              legacy_pic->mask_all();
+>      }
+> +#endif
+>  
+>      if (x2apic_mode)
+>              enable_x2apic();
+> @@ -2173,6 +2177,7 @@ static int lapic_resume(struct sys_device *dev)
+>      apic_write(APIC_ESR, 0);
+>      apic_read(APIC_ESR);
+>  
+> +#ifdef CONFIG_INTR_REMAP
+>      if (intr_remapping_enabled) {
+>              reenable_intr_remapping(x2apic_mode);
+>              legacy_pic->restore_mask();
+> @@ -2180,6 +2185,7 @@ static int lapic_resume(struct sys_device *dev)
+>              free_ioapic_entries(ioapic_entries);
+>      }
+>  restore:
+> +#endif
+
+Hm, these bits should be factored out in a cleaner fashion - by adding helper 
+functions, etc. The x2apic code's integration into the lapic code was done in a 
+pretty ugly fashion so it's not your fault - but if we want to reintroduce UP-IOAPIC 
+we need to do it cleanly.
+
+Do you still want to do it? :-)
+
+Thanks,
+
+       Ingo
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004640:2, b/test/corpora/lkml/cur/1382298805.004640:2,
new file mode 100644 (file)
index 0000000..af16a62
--- /dev/null
@@ -0,0 +1,69 @@
+From: Ingo Molnar <mingo@elte.hu>
+Subject: Re: [PATCH 2/6] x86: ifdef enable_IR_x2apic() out
+Date: Mon, 14 Feb 2011 12:03:40 +0100
+Lines: 15
+Message-ID: <20110214110340.GC7140@elte.hu>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+ <1297677612-12405-2-git-send-email-henne@nachtwindheim.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: mingo@readhat.com, tglx@linutronix.de, hpa@zytor.com,
+       x86@kernel.org, tj@kernel.org, yinghai@kernel.org,
+       ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org
+To: Henrik Kretzschmar <henne@nachtwindheim.de>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 12:04:03 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PowDr-0002js-9K
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 12:04:03 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753399Ab1BNLDz (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 06:03:55 -0500
+Received: from mx2.mail.elte.hu ([157.181.151.9]:54341 "EHLO mx2.mail.elte.hu"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1753317Ab1BNLDx (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 06:03:53 -0500
+Received: from elvis.elte.hu ([157.181.1.14])
+       by mx2.mail.elte.hu with esmtp (Exim)
+       id 1PowDY-0002Mp-4N
+       from <mingo@elte.hu>; Mon, 14 Feb 2011 12:03:45 +0100
+Received: by elvis.elte.hu (Postfix, from userid 1004)
+       id 458B63E2369; Mon, 14 Feb 2011 12:03:40 +0100 (CET)
+Content-Disposition: inline
+In-Reply-To: <1297677612-12405-2-git-send-email-henne@nachtwindheim.de>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+Received-SPF: neutral (mx2.mail.elte.hu: 157.181.1.14 is neither permitted nor denied by domain of elte.hu) client-ip=157.181.1.14; envelope-from=mingo@elte.hu; helo=elvis.elte.hu;
+X-ELTE-SpamScore: -2.0
+X-ELTE-SpamLevel: 
+X-ELTE-SpamCheck: no
+X-ELTE-SpamVersion: ELTE 2.0 
+X-ELTE-SpamCheck-Details: score=-2.0 required=5.9 tests=BAYES_00 autolearn=no SpamAssassin version=3.2.5
+       -2.0 BAYES_00               BODY: Bayesian spam probability is 0 to 1%
+       [score: 0.0000]
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099672>
+
+
+
+* Henrik Kretzschmar <henne@nachtwindheim.de> wrote:
+
+> +#ifdef CONFIG_X86_64
+>  extern void enable_IR_x2apic(void);
+> +#endif
+
+Cannot we use the CONFIG_X86_X2APIC Kconfig switch here, instead of CONFIG_X86_64?
+
+enable_IR_x2apic() is not a 64-bit CPU feature.
+
+Thanks,
+
+       Ingo
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004642:2, b/test/corpora/lkml/cur/1382298805.004642:2,
new file mode 100644 (file)
index 0000000..ce8ac11
--- /dev/null
@@ -0,0 +1,93 @@
+From: Ingo Molnar <mingo@elte.hu>
+Subject: Re: [PATCH 1/6] x86: move ioapic_irq_destination_types
+Date: Mon, 14 Feb 2011 12:05:28 +0100
+Lines: 40
+Message-ID: <20110214110528.GD7140@elte.hu>
+References: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Cc: mingo@readhat.com, tglx@linutronix.de, hpa@zytor.com,
+       x86@kernel.org, tj@kernel.org, yinghai@kernel.org,
+       ak@linux.intel.com, robert.richter@amd.com,
+       linux-kernel@vger.kernel.org
+To: Henrik Kretzschmar <henne@nachtwindheim.de>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 12:06:00 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PowFj-0003bW-W9
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 12:06:00 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753317Ab1BNLFq (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 06:05:46 -0500
+Received: from mx3.mail.elte.hu ([157.181.1.138]:46158 "EHLO mx3.mail.elte.hu"
+       rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+       id S1752958Ab1BNLFn (ORCPT <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 06:05:43 -0500
+Received: from elvis.elte.hu ([157.181.1.14])
+       by mx3.mail.elte.hu with esmtp (Exim)
+       id 1PowFH-0003wc-2d
+       from <mingo@elte.hu>; Mon, 14 Feb 2011 12:05:36 +0100
+Received: by elvis.elte.hu (Postfix, from userid 1004)
+       id 30C323E2369; Mon, 14 Feb 2011 12:05:27 +0100 (CET)
+Content-Disposition: inline
+In-Reply-To: <1297677612-12405-1-git-send-email-henne@nachtwindheim.de>
+User-Agent: Mutt/1.5.20 (2009-08-17)
+Received-SPF: neutral (mx3: 157.181.1.14 is neither permitted nor denied by domain of elte.hu) client-ip=157.181.1.14; envelope-from=mingo@elte.hu; helo=elvis.elte.hu;
+X-ELTE-SpamScore: -2.0
+X-ELTE-SpamLevel: 
+X-ELTE-SpamCheck: no
+X-ELTE-SpamVersion: ELTE 2.0 
+X-ELTE-SpamCheck-Details: score=-2.0 required=5.9 tests=BAYES_00 autolearn=no SpamAssassin version=3.2.5
+       -2.0 BAYES_00               BODY: Bayesian spam probability is 0 to 1%
+       [score: 0.0000]
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099674>
+
+
+* Henrik Kretzschmar <henne@nachtwindheim.de> wrote:
+
+> +++ b/arch/x86/include/asm/apicdef.h
+> @@ -426,4 +426,16 @@ struct local_apic {
+>  #else
+>   #define BAD_APICID 0xFFFFu
+>  #endif
+> +
+> +enum ioapic_irq_destination_types {
+> +    dest_Fixed = 0,
+> +    dest_LowestPrio = 1,
+> +    dest_SMI = 2,
+> +    dest__reserved_1 = 3,
+> +    dest_NMI = 4,
+> +    dest_INIT = 5,
+> +    dest__reserved_2 = 6,
+> +    dest_ExtINT = 7
+> +};
+
+one very small request, while we are moving it could you please align the value
+enumeration vertically? Something like:
+
+enum ioapic_irq_destination_types {
+
+       dest_Fixed              = 0,
+       dest_LowestPrio         = 1,
+       dest_SMI                = 2,
+       dest__reserved_1        = 3,
+       dest_NMI                = 4,
+       dest_INIT               = 5,
+       dest__reserved_2        = 6,
+       dest_ExtINT             = 7
+};
+
+... would be much more readable, right?
+
+Thanks,
+
+       Ingo
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004653:2, b/test/corpora/lkml/cur/1382298805.004653:2,
new file mode 100644 (file)
index 0000000..d6bd8d1
--- /dev/null
@@ -0,0 +1,90 @@
+From: Catalin Marinas <catalin.marinas@arm.com>
+Subject: Re: [PATCH] ARM: vfp: Always save VFP state in vfp_pm_suspend
+Date: Mon, 14 Feb 2011 11:42:22 +0000
+Organization: ARM Limited
+Lines: 43
+Message-ID: <1297683742.30092.11.camel@e102109-lin.cambridge.arm.com>
+References: <1297638813-1315-1-git-send-email-ccross@android.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8BIT
+Cc: linux-arm-kernel@lists.infradead.org,
+       Russell King <linux@arm.linux.org.uk>,
+       linux-kernel@vger.kernel.org
+To: Colin Cross <ccross@android.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 12:42:45 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1PowpJ-00069R-8c
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 12:42:45 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753535Ab1BNLmi (ORCPT <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 06:42:38 -0500
+Received: from service87.mimecast.com ([94.185.240.25]:56758 "HELO
+       service87.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with SMTP id S1752997Ab1BNLmg convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 06:42:36 -0500
+Received: from cam-owa2.Emea.Arm.com (fw-tnat.cambridge.arm.com [217.140.96.21])
+       by service87.mimecast.com;
+       Mon, 14 Feb 2011 11:42:31 +0000
+Received: from [10.1.77.95] ([10.1.255.212]) by cam-owa2.Emea.Arm.com with Microsoft SMTPSVC(6.0.3790.3959);
+        Mon, 14 Feb 2011 11:42:28 +0000
+In-Reply-To: <1297638813-1315-1-git-send-email-ccross@android.com>
+X-Mailer: Evolution 2.28.1
+X-OriginalArrivalTime: 14 Feb 2011 11:42:28.0658 (UTC) FILETIME=[41F09120:01CBCC3C]
+X-MC-Unique: 111021411423105201
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099685>
+
+On Sun, 2011-02-13 at 23:13 +0000, Colin Cross wrote:
+> vfp_pm_suspend should save the VFP state any time there is
+> a last_VFP_context.  If it only saves when the VFP is enabled,
+> the state can get lost when, on a UP system:
+>    Thread 1 uses the VFP
+>    Context switch occurs to thread 2, VFP is disabled but the
+>       VFP context is not saved to allow lazy save and restore
+>    Thread 2 initiates suspend
+>    vfp_pm_suspend is called with the VFP disabled, but the
+>       context has not been saved.
+
+At this point is it guaranteed that the thread won't migrate to another
+CPU? If not, we should use get/put_cpu.
+
+> --- a/arch/arm/vfp/vfpmodule.c
+> +++ b/arch/arm/vfp/vfpmodule.c
+> @@ -415,13 +415,12 @@ static int vfp_pm_suspend(struct sys_device *dev, pm_message_t state)
+>         struct thread_info *ti = current_thread_info();
+>         u32 fpexc = fmrx(FPEXC);
+> 
+> -       /* if vfp is on, then save state for resumption */
+> -       if (fpexc & FPEXC_EN) {
+> +       /* save state for resume */
+> +       if (last_VFP_context[ti->cpu]) {
+>                 printk(KERN_DEBUG "%s: saving vfp state\n", __func__);
+> -               vfp_save_state(&ti->vfpstate, fpexc);
+> -
+> -               /* disable, just in case */
+> -               fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
+> +               fmxr(FPEXC, fpexc | FPEXC_EN);
+> +               vfp_save_state(last_VFP_context[ti->cpu], fpexc);
+> +               fmxr(FPEXC, fpexc & ~FPEXC_EN);
+>         }
+
+We may want to set the last_VFP_context to NULL so that after resuming
+(to the same thread) we force the VFP reload from the vfpstate
+structure. The vfp_support_entry code ignores the reloading if the
+last_VFP_context is the same as vfpstate.
+
+-- 
+Catalin
+
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004665:2, b/test/corpora/lkml/cur/1382298805.004665:2,
new file mode 100644 (file)
index 0000000..bfba805
--- /dev/null
@@ -0,0 +1,121 @@
+From: =?ISO-8859-1?Q?Nicolas_de_Peslo=FCan?= 
+       <nicolas.2p.debian@gmail.com>
+Subject: Re: [PATCH] core: dev: don't call BUG() on bad input
+Date: Mon, 14 Feb 2011 13:16:04 +0100
+Lines: 54
+Message-ID: <4D591D04.4050000@gmail.com>
+References: <1297680967-11893-1-git-send-email-segoon@openwall.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1;
+       format=flowed
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: linux-kernel@vger.kernel.org,
+       "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <eric.dumazet@gmail.com>,
+       Tom Herbert <therbert@google.com>,
+       Changli Gao <xiaosuo@gmail.com>,
+       Jesse Gross <jesse@nicira.com>, netdev@vger.kernel.org
+To: Vasiliy Kulikov <segoon@openwall.com>
+X-From: netdev-owner@vger.kernel.org Mon Feb 14 13:16:23 2011
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PoxLn-0007s8-Rx
+       for linux-netdev-2@lo.gmane.org; Mon, 14 Feb 2011 13:16:20 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1753819Ab1BNMQQ convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;linux-netdev-2@m.gmane.org>); Mon, 14 Feb 2011 07:16:16 -0500
+Received: from mail-bw0-f46.google.com ([209.85.214.46]:53692 "EHLO
+       mail-bw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753690Ab1BNMQO (ORCPT
+       <rfc822;netdev@vger.kernel.org>); Mon, 14 Feb 2011 07:16:14 -0500
+Received: by bwz15 with SMTP id 15so5395788bwz.19
+        for <multiple recipients>; Mon, 14 Feb 2011 04:16:13 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:message-id:date:from:user-agent:mime-version:to
+         :cc:subject:references:in-reply-to:content-type
+         :content-transfer-encoding;
+        bh=xx3YxuXgqhBANV8wzcWyUMECJelpRfdmoRSp1AKdYdc=;
+        b=N0KOUEiWNDJjbwFsNkzabK7eGUNcoUNkqBGVRMNJFQ1jIgKtMC9sXdcmSFkLf2G3W0
+         zzRsPc9T6wnstCSnGFjIStR1GQK4bQ7o7SC+bmV0UqsBQAMW8sdDkT20PMZlBl2X7PkF
+         a2TGrPtJKfEcGFXay9Xo1ZMu1aYODhlRfsZKo=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=message-id:date:from:user-agent:mime-version:to:cc:subject
+         :references:in-reply-to:content-type:content-transfer-encoding;
+        b=vyCGwBn8dYUhOjmcV0am255nAUilQntfBxUI4yId3MocKlSBfEE0jJCJIVDySIAYOj
+         4g7FzXDeqZ5brwXgwA1derVfTXYvhKUC/60QA9/377l/PZ0vvRfqyPQcinMoOSCc+Kvv
+         vnBZtq/Kr+D4nUnefIwwjJ/dXS3dGjFBvis6w=
+Received: by 10.204.98.65 with SMTP id p1mr25300616bkn.198.1297685772974;
+        Mon, 14 Feb 2011 04:16:12 -0800 (PST)
+Received: from [192.168.0.101] (eab95-4-88-175-177-37.fbx.proxad.net [88.175.177.37])
+        by mx.google.com with ESMTPS id a17sm1733557bku.23.2011.02.14.04.16.07
+        (version=SSLv3 cipher=OTHER);
+        Mon, 14 Feb 2011 04:16:09 -0800 (PST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.16) Gecko/20101226 Icedove/3.0.11
+In-Reply-To: <1297680967-11893-1-git-send-email-segoon@openwall.com>
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099697>
+
+Le 14/02/2011 11:56, Vasiliy Kulikov a =E9crit :
+> alloc_netdev() may be called with too long name (more that IFNAMSIZ b=
+ytes).
+> Currently this leads to BUG().  Other insane inputs (bad txqs, rxqs) =
+and
+> even OOM don't lead to BUG().  Made alloc_netdev() return NULL, like =
+on
+> other errors.
+>
+> Signed-off-by: Vasiliy Kulikov<segoon@openwall.com>
+> ---
+>   Compile tested.
+>
+>   net/core/dev.c |    5 ++++-
+>   1 files changed, 4 insertions(+), 1 deletions(-)
+>
+> diff --git a/net/core/dev.c b/net/core/dev.c
+> index 6392ea0..12ef4b0 100644
+> --- a/net/core/dev.c
+> +++ b/net/core/dev.c
+> @@ -5761,7 +5761,10 @@ struct net_device *alloc_netdev_mqs(int sizeof=
+_priv, const char *name,
+>      size_t alloc_size;
+>      struct net_device *p;
+>
+> -    BUG_ON(strlen(name)>=3D sizeof(dev->name));
+> +    if (strnlen(name, sizeof(dev->name))>=3D sizeof(dev->name)) {
+
+"size_t strnlen(const char *s, size_t maxlen) : The strnlen() function =
+returns strlen(s), if that is=20
+less than maxlen, or maxlen if there is no '\0' character among the fir=
+st maxlen characters pointed=20
+to by s."
+
+How can strnlen(name, sizeof(dev->name)) be greater than sizeof(dev->na=
+me)?
+
+Shouldn't it be "if (strnlen(name, sizeof(dev->name)) =3D=3D sizeof(dev=
+->name))" instead?
+
+         Nicolas.
+
+> +            pr_err("alloc_netdev: Too long device name \n");
+> +            return NULL;
+> +    }
+>
+>      if (txqs<  1) {
+>              pr_err("alloc_netdev: Unable to allocate device "
+
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004680:2, b/test/corpora/lkml/cur/1382298805.004680:2,
new file mode 100644 (file)
index 0000000..e0e74ce
--- /dev/null
@@ -0,0 +1,103 @@
+From: Vasiliy Kulikov <segoon@openwall.com>
+Subject: Re: [PATCH] core: dev: don't call BUG() on bad input
+Date: Mon, 14 Feb 2011 15:23:13 +0300
+Lines: 34
+Message-ID: <20110214122313.GA10062@albatros>
+References: <1297680967-11893-1-git-send-email-segoon@openwall.com>
+ <4D591D04.4050000@gmail.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: linux-kernel@vger.kernel.org,
+       "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <eric.dumazet@gmail.com>,
+       Tom Herbert <therbert@google.com>,
+       Changli Gao <xiaosuo@gmail.com>,
+       Jesse Gross <jesse@nicira.com>, netdev@vger.kernel.org
+To: Nicolas de =?iso-8859-1?Q?Peslo=FCan?= 
+       <nicolas.2p.debian@gmail.com>
+X-From: netdev-owner@vger.kernel.org Mon Feb 14 13:24:40 2011
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1PoxTr-00046W-DE
+       for linux-netdev-2@lo.gmane.org; Mon, 14 Feb 2011 13:24:39 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754719Ab1BNMXX convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;linux-netdev-2@m.gmane.org>); Mon, 14 Feb 2011 07:23:23 -0500
+Received: from mail-bw0-f46.google.com ([209.85.214.46]:64487 "EHLO
+       mail-bw0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1754086Ab1BNMXU (ORCPT
+       <rfc822;netdev@vger.kernel.org>); Mon, 14 Feb 2011 07:23:20 -0500
+Received: by bwz15 with SMTP id 15so5401470bwz.19
+        for <multiple recipients>; Mon, 14 Feb 2011 04:23:18 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:sender:date:from:to:cc:subject:message-id
+         :references:mime-version:content-type:content-disposition
+         :content-transfer-encoding:in-reply-to:user-agent;
+        bh=4cv3J11meWILtVQ6Drgqk74suEYFVRQKnvtS62ZKPMU=;
+        b=oson8MDOPhFFO5h9lGEmq3EcDJ7bfOy60AsJ8ka5Q45h/Fg5LvGyVGBWB48YesHBg+
+         51Kdb4VrtCWazj2/c1Eauv2jvrGXUjj1hZdo3Rq0jZb5eU+Nvf+7Gl8nWE1S47XmT8YW
+         ed9CdFnNDgvkVUw+Rg48e2nG79kDRNUGWlpKI=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=sender:date:from:to:cc:subject:message-id:references:mime-version
+         :content-type:content-disposition:content-transfer-encoding
+         :in-reply-to:user-agent;
+        b=lEIU6CLzqKxhpDXukIqjR49CQZ370NKAkb0Aah7A1uyEdLZ9ctYpgg1oPjsQX/V9IR
+         TqyIP0zocVjBhUgCi32M9DPe/qjiqe+YS+EXNGLMMrF1oEUY+yFfq1jChaHkk2xuf/EM
+         MaGyK3svEz1q2iV1bgkTLcXCLWyK+A/M1WFlg=
+Received: by 10.204.80.161 with SMTP id t33mr15020786bkk.121.1297686197182;
+        Mon, 14 Feb 2011 04:23:17 -0800 (PST)
+Received: from localhost (ppp91-77-40-235.pppoe.mtu-net.ru [91.77.40.235])
+        by mx.google.com with ESMTPS id w3sm1684029bkt.5.2011.02.14.04.23.14
+        (version=TLSv1/SSLv3 cipher=OTHER);
+        Mon, 14 Feb 2011 04:23:16 -0800 (PST)
+Content-Disposition: inline
+In-Reply-To: <4D591D04.4050000@gmail.com>
+User-Agent: Mutt/1.5.20 (2009-06-14)
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099712>
+
+Hi Nicolas,
+
+On Mon, Feb 14, 2011 at 13:16 +0100, Nicolas de Peslo=FCan wrote:
+> >-   BUG_ON(strlen(name)>=3D sizeof(dev->name));
+> >+   if (strnlen(name, sizeof(dev->name))>=3D sizeof(dev->name)) {
+
+Ehh...  Space after ")" is needed :)
+
+> "size_t strnlen(const char *s, size_t maxlen) : The strnlen()
+> function returns strlen(s), if that is less than maxlen, or maxlen
+> if there is no '\0' character among the first maxlen characters
+> pointed to by s."
+>=20
+> How can strnlen(name, sizeof(dev->name)) be greater than sizeof(dev->=
+name)?
+>=20
+> Shouldn't it be "if (strnlen(name, sizeof(dev->name)) =3D=3D sizeof(d=
+ev->name))" instead?
+
+Not a big deal, but MO it's better to guard from everything that
+is not a good input by negating the check.  strnlen() < sizeof() is OK,
+strnlen() >=3D sizeof() is bad.  Is "=3D=3D" more preferable for net/ c=
+oding style?
+
+
+--=20
+Vasiliy Kulikov
+http://www.openwall.com - bringing security into open computing environ=
+ments
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004688:2, b/test/corpora/lkml/cur/1382298805.004688:2,
new file mode 100644 (file)
index 0000000..dd935b6
--- /dev/null
@@ -0,0 +1,107 @@
+From: =?ISO-8859-1?Q?Nicolas_de_Peslo=FCan?= 
+       <nicolas.2p.debian@gmail.com>
+Subject: Re: [PATCH] core: dev: don't call BUG() on bad input
+Date: Mon, 14 Feb 2011 14:01:44 +0100
+Lines: 40
+Message-ID: <4D5927B8.2070704@gmail.com>
+References: <1297680967-11893-1-git-send-email-segoon@openwall.com> <4D591D04.4050000@gmail.com> <20110214122313.GA10062@albatros>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1;
+       format=flowed
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: linux-kernel@vger.kernel.org,
+       "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <eric.dumazet@gmail.com>,
+       Tom Herbert <therbert@google.com>,
+       Changli Gao <xiaosuo@gmail.com>,
+       Jesse Gross <jesse@nicira.com>, netdev@vger.kernel.org
+To: Vasiliy Kulikov <segoon@openwall.com>
+X-From: netdev-owner@vger.kernel.org Mon Feb 14 14:02:23 2011
+Return-path: <netdev-owner@vger.kernel.org>
+Envelope-to: linux-netdev-2@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <netdev-owner@vger.kernel.org>)
+       id 1Poy4M-0006df-J5
+       for linux-netdev-2@lo.gmane.org; Mon, 14 Feb 2011 14:02:22 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1754233Ab1BNNBu convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;linux-netdev-2@m.gmane.org>); Mon, 14 Feb 2011 08:01:50 -0500
+Received: from mail-fx0-f46.google.com ([209.85.161.46]:54545 "EHLO
+       mail-fx0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1753647Ab1BNNBs (ORCPT
+       <rfc822;netdev@vger.kernel.org>); Mon, 14 Feb 2011 08:01:48 -0500
+Received: by fxm20 with SMTP id 20so5178314fxm.19
+        for <multiple recipients>; Mon, 14 Feb 2011 05:01:47 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=gamma;
+        h=domainkey-signature:message-id:date:from:user-agent:mime-version:to
+         :cc:subject:references:in-reply-to:content-type
+         :content-transfer-encoding;
+        bh=AbxlHNh3L+hBj6Vij/+GRK5xyYUXmvKoB1QZLa2ZBj0=;
+        b=b/GQO7FpiFoh6WrR9d9qEW2Q1ZOK0YtYzl/fLoXZS49QbuYiuExhWkohPnHsdH/n7s
+         liu8crpx1n3Ajna/7GX1mHBP6V4lfhH+NyF0Rmw3w+fx154lFiY9dbyPX7H9MZNdW60a
+         8TmPRR356gmV+7bijgKwyMN1FRVMPNV0Zg0i8=
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=gmail.com; s=gamma;
+        h=message-id:date:from:user-agent:mime-version:to:cc:subject
+         :references:in-reply-to:content-type:content-transfer-encoding;
+        b=C+hWB2Aof37xOLi8SWuN+D3QsDtf/f4yCxcLrNGhRYytyr/7CUmq/rS7PpgnfvBVBr
+         yaKwVZXs7QRxIWbPnzmV38e1K+eUwZ+dd9XuEFN1dnXd5KJVv4CjWr2N84NIHx/NvOBL
+         7QYK5+DuuRaccybcS4xWMNK8mujh9ebSBXTgM=
+Received: by 10.223.87.1 with SMTP id u1mr4464553fal.112.1297688507260;
+        Mon, 14 Feb 2011 05:01:47 -0800 (PST)
+Received: from [192.168.0.101] (eab95-4-88-175-177-37.fbx.proxad.net [88.175.177.37])
+        by mx.google.com with ESMTPS id y3sm1031898fai.38.2011.02.14.05.01.45
+        (version=SSLv3 cipher=OTHER);
+        Mon, 14 Feb 2011 05:01:46 -0800 (PST)
+User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.16) Gecko/20101226 Icedove/3.0.11
+In-Reply-To: <20110214122313.GA10062@albatros>
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <netdev.vger.kernel.org>
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099723>
+
+Le 14/02/2011 13:23, Vasiliy Kulikov a =E9crit :
+> Hi Nicolas,
+
+Hi Vasiliy,
+
+> On Mon, Feb 14, 2011 at 13:16 +0100, Nicolas de Peslo=FCan wrote:
+>>> -  BUG_ON(strlen(name)>=3D sizeof(dev->name));
+>>> +  if (strnlen(name, sizeof(dev->name))>=3D sizeof(dev->name)) {
+>
+> Ehh...  Space after ")" is needed :)
+
+:-D
+
+>> "size_t strnlen(const char *s, size_t maxlen) : The strnlen()
+>> function returns strlen(s), if that is less than maxlen, or maxlen
+>> if there is no '\0' character among the first maxlen characters
+>> pointed to by s."
+>>
+>> How can strnlen(name, sizeof(dev->name)) be greater than sizeof(dev-=
+>name)?
+>>
+>> Shouldn't it be "if (strnlen(name, sizeof(dev->name)) =3D=3D sizeof(=
+dev->name))" instead?
+>
+> Not a big deal, but MO it's better to guard from everything that
+> is not a good input by negating the check.  strnlen()<  sizeof() is O=
+K,
+> strnlen()>=3D sizeof() is bad.  Is "=3D=3D" more preferable for net/ =
+coding style?
+
+Agreed, both cannot cause any troubles. =3D=3D is supposed to be better=
+ from the API point of view, but=20
+ >=3D is probably more readable.
+
+       Nicolas.
+--
+To unsubscribe from this list: send the line "unsubscribe netdev" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
+
diff --git a/test/corpora/lkml/cur/1382298805.004906:2, b/test/corpora/lkml/cur/1382298805.004906:2,
new file mode 100644 (file)
index 0000000..fb3510a
--- /dev/null
@@ -0,0 +1,125 @@
+From: Colin Cross <ccross@android.com>
+Subject: Re: [PATCH] ARM: vfp: Always save VFP state in vfp_pm_suspend
+Date: Mon, 14 Feb 2011 10:35:37 -0800
+Lines: 50
+Message-ID: <AANLkTik_Jey_PtRmr530FVckA6RXHESeX+CyoJC=ZTkR@mail.gmail.com>
+References: <1297638813-1315-1-git-send-email-ccross@android.com>
+       <1297683742.30092.11.camel@e102109-lin.cambridge.arm.com>
+Mime-Version: 1.0
+Content-Type: text/plain; charset=ISO-8859-1
+Content-Transfer-Encoding: QUOTED-PRINTABLE
+Cc: linux-arm-kernel@lists.infradead.org,
+       Russell King <linux@arm.linux.org.uk>,
+       linux-kernel@vger.kernel.org
+To: Catalin Marinas <catalin.marinas@arm.com>
+X-From: linux-kernel-owner@vger.kernel.org Mon Feb 14 19:36:14 2011
+Return-path: <linux-kernel-owner@vger.kernel.org>
+Envelope-to: glk-linux-kernel-3@lo.gmane.org
+Received: from vger.kernel.org ([209.132.180.67])
+       by lo.gmane.org with esmtp (Exim 4.69)
+       (envelope-from <linux-kernel-owner@vger.kernel.org>)
+       id 1Pp3HR-0002ph-ME
+       for glk-linux-kernel-3@lo.gmane.org; Mon, 14 Feb 2011 19:36:14 +0100
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+       id S1751716Ab1BNSf7 convert rfc822-to-quoted-printable (ORCPT
+       <rfc822;glk-linux-kernel-3@m.gmane.org>);
+       Mon, 14 Feb 2011 13:35:59 -0500
+Received: from smtp-out.google.com ([74.125.121.67]:16138 "EHLO
+       smtp-out.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org
+       with ESMTP id S1751472Ab1BNSf5 convert rfc822-to-8bit (ORCPT
+       <rfc822;linux-kernel@vger.kernel.org>);
+       Mon, 14 Feb 2011 13:35:57 -0500
+Received: from kpbe14.cbf.corp.google.com (kpbe14.cbf.corp.google.com [172.25.105.78])
+       by smtp-out.google.com with ESMTP id p1EIZtnZ010066
+       for <linux-kernel@vger.kernel.org>; Mon, 14 Feb 2011 10:35:55 -0800
+DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=google.com; s=beta;
+       t=1297708556; bh=zHQvHco6EycqYjYMR9YZftpchts=;
+       h=MIME-Version:Sender:In-Reply-To:References:Date:Message-ID:
+        Subject:From:To:Cc:Content-Type:Content-Transfer-Encoding;
+       b=vMdLxEMc5VUxTbXeXOfO9iAKtgTlVwOwZrVZEhG7GNUReOYWZKzblAuPHuJUWHr5q
+        8s7bNLIdGKiRH4q28mv9w==
+Received: from vws17 (vws17.prod.google.com [10.241.21.145])
+       by kpbe14.cbf.corp.google.com with ESMTP id p1EIYaTC004311
+       (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT)
+       for <linux-kernel@vger.kernel.org>; Mon, 14 Feb 2011 10:35:54 -0800
+Received: by vws17 with SMTP id 17so3300387vws.2
+        for <linux-kernel@vger.kernel.org>; Mon, 14 Feb 2011 10:35:54 -0800 (PST)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=google.com; s=beta;
+        h=domainkey-signature:mime-version:sender:in-reply-to:references:date
+         :x-google-sender-auth:message-id:subject:from:to:cc:content-type
+         :content-transfer-encoding;
+        bh=FyW95sl8WRQv1ev+TqjP0gzPcCabtWdW1//GAc6oFnY=;
+        b=MNkyYY1htITiUX23N5enGCjsYq2mGBCW4BadxXMha/29ZeIyVP6jrUHlViT88u79RG
+         SxLMzz7lijwW38xTiBfw==
+DomainKey-Signature: a=rsa-sha1; c=nofws;
+        d=google.com; s=beta;
+        h=mime-version:sender:in-reply-to:references:date
+         :x-google-sender-auth:message-id:subject:from:to:cc:content-type
+         :content-transfer-encoding;
+        b=Lio4jom4+RruCzH/a6zheXvUcTpZvSX7eMk6Ld1+PQQybP02I0+Cv4+PgGac2GoAYX
+         tXciu0c/KOGts2phbyMg==
+Received: by 10.220.94.201 with SMTP id a9mr1430388vcn.56.1297708537386; Mon,
+ 14 Feb 2011 10:35:37 -0800 (PST)
+Received: by 10.220.43.142 with HTTP; Mon, 14 Feb 2011 10:35:37 -0800 (PST)
+In-Reply-To: <1297683742.30092.11.camel@e102109-lin.cambridge.arm.com>
+X-Google-Sender-Auth: 47_39DbBbWrnAh28bpWvfpwmbLw
+X-System-Of-Record: true
+Sender: linux-kernel-owner@vger.kernel.org
+Precedence: bulk
+List-ID: <linux-kernel.vger.kernel.org>
+X-Mailing-List: linux-kernel@vger.kernel.org
+Archived-At: <http://permalink.gmane.org/gmane.linux.kernel/1099943>
+
+On Mon, Feb 14, 2011 at 3:42 AM, Catalin Marinas
+<catalin.marinas@arm.com> wrote:
+> On Sun, 2011-02-13 at 23:13 +0000, Colin Cross wrote:
+>> vfp_pm_suspend should save the VFP state any time there is
+>> a last_VFP_context. =A0If it only saves when the VFP is enabled,
+>> the state can get lost when, on a UP system:
+>> =A0 =A0Thread 1 uses the VFP
+>> =A0 =A0Context switch occurs to thread 2, VFP is disabled but the
+>> =A0 =A0 =A0 VFP context is not saved to allow lazy save and restore
+>> =A0 =A0Thread 2 initiates suspend
+>> =A0 =A0vfp_pm_suspend is called with the VFP disabled, but the
+>> =A0 =A0 =A0 context has not been saved.
+>
+> At this point is it guaranteed that the thread won't migrate to anoth=
+er
+> CPU? If not, we should use get/put_cpu.
+
+Yes, VFP suspend is implemented with a sysdev, which is suspended
+after disable_nonboot_cpus.
+
+>> --- a/arch/arm/vfp/vfpmodule.c
+>> +++ b/arch/arm/vfp/vfpmodule.c
+>> @@ -415,13 +415,12 @@ static int vfp_pm_suspend(struct sys_device *d=
+ev, pm_message_t state)
+>> =A0 =A0 =A0 =A0 struct thread_info *ti =3D current_thread_info();
+>> =A0 =A0 =A0 =A0 u32 fpexc =3D fmrx(FPEXC);
+>>
+>> - =A0 =A0 =A0 /* if vfp is on, then save state for resumption */
+>> - =A0 =A0 =A0 if (fpexc & FPEXC_EN) {
+>> + =A0 =A0 =A0 /* save state for resume */
+>> + =A0 =A0 =A0 if (last_VFP_context[ti->cpu]) {
+>> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 printk(KERN_DEBUG "%s: saving vfp st=
+ate\n", __func__);
+>> - =A0 =A0 =A0 =A0 =A0 =A0 =A0 vfp_save_state(&ti->vfpstate, fpexc);
+>> -
+>> - =A0 =A0 =A0 =A0 =A0 =A0 =A0 /* disable, just in case */
+>> - =A0 =A0 =A0 =A0 =A0 =A0 =A0 fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
+>> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fmxr(FPEXC, fpexc | FPEXC_EN);
+>> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 vfp_save_state(last_VFP_context[ti->cp=
+u], fpexc);
+>> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fmxr(FPEXC, fpexc & ~FPEXC_EN);
+>> =A0 =A0 =A0 =A0 }
+>
+> We may want to set the last_VFP_context to NULL so that after resumin=
+g
+> (to the same thread) we force the VFP reload from the vfpstate
+> structure. The vfp_support_entry code ignores the reloading if the
+> last_VFP_context is the same as vfpstate.
+
+Right, will fix.
+
+
diff --git a/test/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S b/test/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S
new file mode 100644 (file)
index 0000000..62bf98d
--- /dev/null
@@ -0,0 +1,9 @@
+From: Gregor Zattler <g.zattler@xxxxxxx-xxxxxxxxx.de>
+To: xxx request tracker <rt-xxx@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Subject: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx
+Date: Tue, 19 Jun 2016 18:26:26 +0200
+Message-ID: <87bmc6lp3h.fsf@len.workgroup>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
diff --git a/test/corpora/threading/ghost-root/1532672447.R3166642290392477575.len:2,S b/test/corpora/threading/ghost-root/1532672447.R3166642290392477575.len:2,S
new file mode 100644 (file)
index 0000000..b79eaf7
--- /dev/null
@@ -0,0 +1,17 @@
+Return-Path: <prvs=701fd58e1=www-data@support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Subject: [support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de #33575] AutoReply: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx
+From: " via RT" <rt-xxx@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Reply-To: rt-xxx@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de
+In-Reply-To: <87bmc6lp3h.fsf@len.workgroup>
+References: <RT-Ticket-33575@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+ <87bmc6lp3h.fsf@len.workgroup>
+Message-ID: <rt-4.2.8-22046-1529425595-591.33575-211-0@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+To: g.zattler@xxxxxxx-xxxxxxxxx.de
+Content-Type: text/plain; charset="utf-8"
+Date: Tue, 19 Jun 2016 18:26:36 +0200
+MIME-Version: 1.0
+Content-Transfer-Encoding: 8bit
+
+
+
+
diff --git a/test/corpora/threading/ghost-root/1532672447.R6968667928580738175.len:2,S b/test/corpora/threading/ghost-root/1532672447.R6968667928580738175.len:2,S
new file mode 100644 (file)
index 0000000..343a855
--- /dev/null
@@ -0,0 +1,18 @@
+Return-Path: <prvs=708ebe06b=www-data@support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Subject: [support.xxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de #33575] Resolved: FYI: xxxx  xxxxxxx  xxxxxxxxxxxx xxx
+From: " via RT" <rt-xxx@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Reply-To: rt-xxx@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de
+References: <RT-Ticket-33575@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+Message-ID: <rt-4.2.8-6644-1530017064-1465.33575-215-0@xxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxx-xxxxxxxxx.de>
+To: g.zattler@xxxxxxx-xxxxxxxxx.de
+Content-Type: text/plain; charset="utf-8"
+Date: Tue, 26 Jun 2016 14:44:24 +0200
+MIME-Version: 1.0
+Content-Transfer-Encoding: 8bit
+
+
+
+
+According to our records, your request has been resolved. If you have any
+further questions or concerns, please respond to this message.
+
diff --git a/test/corpora/threading/ghost-root/child b/test/corpora/threading/ghost-root/child
new file mode 100644 (file)
index 0000000..4c36af9
--- /dev/null
@@ -0,0 +1,9 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: child message
+Message-ID: <001-child@example.org>
+In-Reply-To: <000-real-root@example.org>
+References:  <000-real-root@example.org>
+Date: Fri, 17 Jun 2016 22:14:41 -0400
+
+
diff --git a/test/corpora/threading/ghost-root/fake-root b/test/corpora/threading/ghost-root/fake-root
new file mode 100644 (file)
index 0000000..a698185
--- /dev/null
@@ -0,0 +1,9 @@
+From: Mallory <mallory@example.org>
+To: Daniel <daniel@example.org>
+Subject: fake root message
+Message-ID: <001-fake-message-root@example.org>
+In-Reply-to: <nonexistent-message@example.org>
+References: <000-real-root@example.org> <001-child@example.org> <nonexistent-message@example.org>
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+
+This message has an in-reply-to pointing to a non-existent message
diff --git a/test/corpora/threading/ghost-root/grand-child b/test/corpora/threading/ghost-root/grand-child
new file mode 100644 (file)
index 0000000..5f77ac3
--- /dev/null
@@ -0,0 +1,9 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: grand-child message
+Message-ID: <001-grand-child@example.org>
+In-Reply-To: <001-child@example.org>
+References:  <000-real-root@example.org> <001-child@example.org>
+Date: Fri, 17 Jun 2016 22:24:41 -0400
+
+
diff --git a/test/corpora/threading/ghost-root/grand-child2 b/test/corpora/threading/ghost-root/grand-child2
new file mode 100644 (file)
index 0000000..59682a9
--- /dev/null
@@ -0,0 +1,9 @@
+From: Daniel <daniel@example.org>
+To: Alice <alice@example.org>
+Subject: grand-child message 2
+Message-ID: <001-grand-child2@example.org>
+In-Reply-To: <001-child@example.org>
+References:  <000-real-root@example.org> <001-child@example.org>
+Date: Fri, 17 Jun 2016 22:34:41 -0400
+
+
diff --git a/test/corpora/threading/ghost-root/great-grand-child b/test/corpora/threading/ghost-root/great-grand-child
new file mode 100644 (file)
index 0000000..287a895
--- /dev/null
@@ -0,0 +1,9 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: great grand-child message
+Message-ID: <001-great-grand-child@example.org>
+In-Reply-To: <001-grand-child@example.org>
+References:  <000-real-root@example.org> <001-grand-child@example.org>
+Date: Fri, 17 Jun 2016 22:44:41 -0400
+
+
diff --git a/test/corpora/threading/ghost-root/real-root b/test/corpora/threading/ghost-root/real-root
new file mode 100644 (file)
index 0000000..f1b16a0
--- /dev/null
@@ -0,0 +1,7 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: root message
+Message-ID: <000-real-root@example.org>
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+
+This message has no in-reply-to
diff --git a/test/corpora/threading/parent-priority/cur/child b/test/corpora/threading/parent-priority/cur/child
new file mode 100644 (file)
index 0000000..23ee649
--- /dev/null
@@ -0,0 +1,11 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: child message
+Message-ID: <B01-child@example.org>
+In-Reply-To: <B00-root@example.org>
+References:  <B00--root@example.org>
+Date: Fri, 17 Jun 2016 22:14:41 -0400
+
+This is a normal-ish reply, and has both a references header and an
+in-reply-to header.
+
diff --git a/test/corpora/threading/parent-priority/cur/grand-child b/test/corpora/threading/parent-priority/cur/grand-child
new file mode 100644 (file)
index 0000000..028371d
--- /dev/null
@@ -0,0 +1,10 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: grand-child message
+Message-ID: <B01-grand-child@example.org>
+In-Reply-To: <B01-child@example.org>
+References:  <B01-child@example.org> <B00-root@example.org>
+Date: Fri, 17 Jun 2016 22:24:41 -0400
+
+This has the references headers in the wrong order, with oldest first.
+Debbugs does this.
diff --git a/test/corpora/threading/parent-priority/cur/root b/test/corpora/threading/parent-priority/cur/root
new file mode 100644 (file)
index 0000000..3990843
--- /dev/null
@@ -0,0 +1,7 @@
+From: Alice <alice@example.org>
+To: Daniel <daniel@example.org>
+Subject: root message
+Message-ID: <B00-root@example.org>
+Date: Thu, 16 Jun 2016 22:14:41 -0400
+
+This message has no reply-to
diff --git a/test/database-test.c b/test/database-test.c
new file mode 100644 (file)
index 0000000..42f6655
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Database routines intended only for testing, not exported from
+ * library.
+ *
+ * Copyright (c) 2012 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "notmuch-private.h"
+#include "database-test.h"
+
+notmuch_status_t
+notmuch_database_add_stub_message (notmuch_database_t *notmuch,
+                                  const char *message_id,
+                                  const char **tags)
+{
+    const char **tag;
+    notmuch_status_t ret;
+    notmuch_private_status_t private_status;
+    notmuch_message_t *message;
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    message = _notmuch_message_create_for_message_id (notmuch,
+                                                     message_id,
+                                                     &private_status);
+    if (message == NULL) {
+       return COERCE_STATUS (private_status,
+                             "Unexpected status value from _notmuch_message_create_for_message_id");
+
+    }
+
+    if (private_status != NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+       return NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+
+    _notmuch_message_add_term (message, "type", "mail");
+
+    if (tags) {
+       ret = notmuch_message_freeze (message);
+       if (ret)
+           return ret;
+
+       for (tag = tags; *tag; tag++) {
+           ret = notmuch_message_add_tag (message, *tag);
+           if (ret)
+               return ret;
+       }
+
+       ret = notmuch_message_thaw (message);
+       if (ret)
+           return ret;
+    }
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
diff --git a/test/database-test.h b/test/database-test.h
new file mode 100644 (file)
index 0000000..84f7988
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef _DATABASE_TEST_H
+#define _DATABASE_TEST_H
+/* Add a new stub message to the given notmuch database.
+ *
+ * At least the following return values are possible:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
+ *     ID as another message already in the database.
+ *
+ * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only
+ *     mode so no message can be added.
+ */
+
+notmuch_status_t
+notmuch_database_add_stub_message (notmuch_database_t *database,
+                                  const char *message_id,
+                                  const char **tag_list);
+
+#endif
diff --git a/test/emacs-address-cleaning.el b/test/emacs-address-cleaning.el
new file mode 100644 (file)
index 0000000..8423245
--- /dev/null
@@ -0,0 +1,39 @@
+(defun notmuch-test-address-cleaning-1 ()
+  (notmuch-test-expect-equal (notmuch-show-clean-address "dme@dme.org")
+                       "dme@dme.org"))
+
+(defun notmuch-test-address-cleaning-2 ()
+  (let* ((input '("foo@bar.com"
+                 "<foo@bar.com>"
+                 "Foo Bar <foo@bar.com>"
+                 "foo@bar.com <foo@bar.com>"
+                 "\"Foo Bar\" <foo@bar.com>"))
+        (expected '("foo@bar.com"
+                    "foo@bar.com"
+                    "Foo Bar <foo@bar.com>"
+                    "foo@bar.com"
+                    "Foo Bar <foo@bar.com>"))
+        (output (mapcar #'notmuch-show-clean-address input)))
+    (notmuch-test-expect-equal output expected)))
+
+(defun notmuch-test-address-cleaning-3 ()
+  (let* ((input '("ДБ <db-uknot@stop.me.uk>"
+                 "foo (at home) <foo@bar.com>"
+                 "foo [at home] <foo@bar.com>"
+                 "Foo Bar"
+                 "'Foo Bar' <foo@bar.com>"
+                 "\"'Foo Bar'\" <foo@bar.com>"
+                 "'\"Foo Bar\"' <foo@bar.com>"
+                 "'\"'Foo Bar'\"' <foo@bar.com>"
+                 "Fred Dibna \\[extraordinaire\\] <fred@dibna.com>"))
+        (expected '("ДБ <db-uknot@stop.me.uk>"
+                    "foo (at home) <foo@bar.com>"
+                    "foo [at home] <foo@bar.com>"
+                    "Foo Bar"
+                    "Foo Bar <foo@bar.com>"
+                    "Foo Bar <foo@bar.com>"
+                    "Foo Bar <foo@bar.com>"
+                    "Foo Bar <foo@bar.com>"
+                    "Fred Dibna [extraordinaire] <fred@dibna.com>"))
+        (output (mapcar #'notmuch-show-clean-address input)))
+    (notmuch-test-expect-equal output expected)))
diff --git a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-off
new file mode 100644 (file)
index 0000000..e0bd2c7
--- /dev/null
@@ -0,0 +1,82 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+  Subject: Re: [notmuch] Working with Maildir storage?
+  To: Mikhail Gusarov <dottedmag@dottedmag.net>
+  Cc: notmuch@notmuchmail.org
+  Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+  [ multipart/mixed ]
+  [ multipart/signed ]
+  [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+  [ text/plain ]
+  > See the patch just posted here.
+
+  Is the list archived anywhere?  The obvious archives
+  (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+  think I subscribed too late to get the patch (I only just saw the
+  discussion about it).
+
+  It doesn't look like the patch is in git yet.
+
+  -- Lars
+
+  [ 4-line signature. Click/Enter to show. ]
+  [ application/pgp-signature ]
+  [ text/plain ]
+  [ 4-line signature. Click/Enter to show. ]
+   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+    Subject: Re: [notmuch] Working with Maildir storage?
+    To: Keith Packard <keithp@keithp.com>
+    Cc: notmuch@notmuchmail.org
+    Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+    [ multipart/mixed ]
+    [ multipart/signed ]
+    [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+    [ text/plain ]
+    > I've also pushed a slightly more complicated (and complete) fix to my
+    > private notmuch repository
+
+    The version of lib/messages.cc in your repo doesn't build because it's
+    missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+    [ 4-line signature. Click/Enter to show. ]
+    [ application/pgp-signature ]
+    [ text/plain ]
+    [ 4-line signature. Click/Enter to show. ]
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on b/test/emacs-show.expected-output/notmuch-show-elide-non-matching-messages-on
new file mode 100644 (file)
index 0000000..d76d095
--- /dev/null
@@ -0,0 +1,78 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+  Subject: Re: [notmuch] Working with Maildir storage?
+  To: Mikhail Gusarov <dottedmag@dottedmag.net>
+  Cc: notmuch@notmuchmail.org
+  Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+  [ multipart/mixed ]
+  [ multipart/signed ]
+  [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+  [ text/plain ]
+  > See the patch just posted here.
+
+  Is the list archived anywhere?  The obvious archives
+  (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+  think I subscribed too late to get the patch (I only just saw the
+  discussion about it).
+
+  It doesn't look like the patch is in git yet.
+
+  -- Lars
+
+  [ 4-line signature. Click/Enter to show. ]
+  [ application/pgp-signature ]
+  [ text/plain ]
+  [ 4-line signature. Click/Enter to show. ]
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+    Subject: Re: [notmuch] Working with Maildir storage?
+    To: Keith Packard <keithp@keithp.com>
+    Cc: notmuch@notmuchmail.org
+    Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+    [ multipart/mixed ]
+    [ multipart/signed ]
+    [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+    [ text/plain ]
+    > I've also pushed a slightly more complicated (and complete) fix to my
+    > private notmuch repository
+
+    The version of lib/messages.cc in your repo doesn't build because it's
+    missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+    [ 4-line signature. Click/Enter to show. ]
+    [ application/pgp-signature ]
+    [ text/plain ]
+    [ 4-line signature. Click/Enter to show. ]
diff --git a/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off b/test/emacs-show.expected-output/notmuch-show-indent-thread-content-off
new file mode 100644 (file)
index 0000000..1a06374
--- /dev/null
@@ -0,0 +1,82 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+> See the patch just posted here.
+
+Is the list archived anywhere?  The obvious archives
+(http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+think I subscribed too late to get the patch (I only just saw the
+discussion about it).
+
+It doesn't look like the patch is in git yet.
+
+-- Lars
+
+[ 4-line signature. Click/Enter to show. ]
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Keith Packard <keithp@keithp.com>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+> I've also pushed a slightly more complicated (and complete) fix to my
+> private notmuch repository
+
+The version of lib/messages.cc in your repo doesn't build because it's
+missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+[ 4-line signature. Click/Enter to show. ]
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off b/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-off
new file mode 100644 (file)
index 0000000..ce2892a
--- /dev/null
@@ -0,0 +1,32 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+Subject: [notmuch] Working with Maildir storage?
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+  Subject: Re: [notmuch] Working with Maildir storage?
+  To: Mikhail Gusarov <dottedmag@dottedmag.net>
+  Cc: notmuch@notmuchmail.org
+  Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+  [ multipart/mixed ]
+  [ multipart/signed ]
+  [ Unknown signature status ]
+  [ text/plain ]
+  > See the patch just posted here.
+
+  Is the list archived anywhere?  The obvious archives
+  (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+  think I subscribed too late to get the patch (I only just saw the
+  discussion about it).
+
+  It doesn't look like the patch is in git yet.
+
+  -- Lars
+
+  [ 4-line signature. Click/Enter to show. ]
+  [ application/pgp-signature ]
+  [ text/plain ]
+  [ 4-line signature. Click/Enter to show. ]
+   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-on b/test/emacs-show.expected-output/notmuch-show-process-crypto-mime-parts-on
new file mode 100644 (file)
index 0000000..eaa557a
--- /dev/null
@@ -0,0 +1,32 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+Subject: [notmuch] Working with Maildir storage?
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+  Subject: Re: [notmuch] Working with Maildir storage?
+  To: Mikhail Gusarov <dottedmag@dottedmag.net>
+  Cc: notmuch@notmuchmail.org
+  Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+  [ multipart/mixed ]
+  [ multipart/signed ]
+  [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+  [ text/plain ]
+  > See the patch just posted here.
+
+  Is the list archived anywhere?  The obvious archives
+  (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+  think I subscribed too late to get the patch (I only just saw the
+  discussion about it).
+
+  It doesn't look like the patch is in git yet.
+
+  -- Lars
+
+  [ 4-line signature. Click/Enter to show. ]
+  [ application/pgp-signature ]
+  [ text/plain ]
+  [ 4-line signature. Click/Enter to show. ]
+   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs-tree.expected-output/notmuch-tree-show-window b/test/emacs-tree.expected-output/notmuch-tree-show-window
new file mode 100644 (file)
index 0000000..7d860c6
--- /dev/null
@@ -0,0 +1,41 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
diff --git a/test/emacs-tree.expected-output/notmuch-tree-single-thread b/test/emacs-tree.expected-output/notmuch-tree-single-thread
new file mode 100644 (file)
index 0000000..2285d10
--- /dev/null
@@ -0,0 +1,6 @@
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+End of search results.
diff --git a/test/emacs-tree.expected-output/notmuch-tree-tag-inbox b/test/emacs-tree.expected-output/notmuch-tree-tag-inbox
new file mode 100644 (file)
index 0000000..f28d485
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+End of search results.
diff --git a/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-tagged b/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-tagged
new file mode 100644 (file)
index 0000000..428c0ae
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox test_tag unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+End of search results.
diff --git a/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-thread-tagged b/test/emacs-tree.expected-output/notmuch-tree-tag-inbox-thread-tagged
new file mode 100644 (file)
index 0000000..828c525
--- /dev/null
@@ -0,0 +1,53 @@
+  2010-12-29  François Boulogne     ─►[aur-general] Guidelines: cp, mkdir vs install      (inbox unread)
+  2010-12-16  Olivier Berger        ─►Essai accentué                                      (inbox unread)
+  2009-11-18  Chris Wilson          ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2009-11-18  Alex Botero-Lowry     ┬►[notmuch] [PATCH] Error out if no query is supplied to search        instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (inbox unread)
+  2009-11-17  Ingmar Vanhassel      ┬►[notmuch] [PATCH] Typsos                            (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Adrian Perez de Cast  ┬►[notmuch] Introducing myself                        (inbox signed unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Israel Herraiz        ┬►[notmuch] New to the list                           (inbox unread)
+  2009-11-18  Keith Packard         ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] What a great idea!                        (inbox unread)
+  2009-11-17  Jan Janak             ├─► ...                                               (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Jan Janak             ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox unread)
+  2009-11-17  Aron Griffis          ┬►[notmuch] archive                                   (inbox unread)
+  2009-11-18  Keith Packard         ╰┬► ...                                               (inbox unread)
+  2009-11-18  Carl Worth             ╰─► ...                                              (inbox unread)
+  2009-11-17  Keith Packard         ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove    inbox (and unread) tags (inbox unread)
+  2009-11-18  Carl Worth            ╰─►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17  Lars Kellogg-Stedman  ┬►[notmuch] Working with Maildir storage?             (inbox signed test_thread_tag unread)
+  2009-11-17  Mikhail Gusarov       ├┬► ...                                               (inbox signed test_thread_tag unread)
+  2009-11-17  Lars Kellogg-Stedman  │╰┬► ...                                              (inbox signed test_thread_tag unread)
+  2009-11-17  Mikhail Gusarov       │ ├─► ...                                             (inbox test_thread_tag unread)
+  2009-11-17  Keith Packard         │ ╰┬► ...                                             (inbox test_thread_tag unread)
+  2009-11-18  Lars Kellogg-Stedman  │  ╰─► ...                                            (inbox signed test_thread_tag unread)
+  2009-11-18  Carl Worth            ╰─► ...                                               (inbox test_thread_tag unread)
+  2009-11-17  Mikhail Gusarov       ┬►[notmuch] [PATCH 1/2] Close message file after parsing message       headers (inbox unread)
+  2009-11-17  Mikhail Gusarov       ├─►[notmuch] [PATCH 2/2] Include <stdint.h> to get uint32_t in C++   file with gcc 4.4 (inbox unread)
+  2009-11-17  Carl Worth            ╰┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17  Keith Packard          ╰┬► ...                                              (inbox unread)
+  2009-11-18  Carl Worth              ╰─► ...                                             (inbox unread)
+  2009-11-18  Keith Packard         ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that    highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰─►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18  Alexander Botero-Low  ─►[notmuch] request for pull                          (inbox unread)
+  2009-11-18  Jjgod Jiang           ┬►[notmuch] Mac OS X/Darwin compatibility issues      (inbox unread)
+  2009-11-18  Alexander Botero-Low  ╰┬► ...                                               (inbox unread)
+  2009-11-18  Jjgod Jiang            ╰┬► ...                                              (inbox unread)
+  2009-11-18  Alexander Botero-Low    ╰─► ...                                             (inbox unread)
+  2009-11-18  Rolland Santimano     ─►[notmuch] Link to mailing list archives ?           (inbox unread)
+  2009-11-18  Jan Janak             ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool       subdirectories into tags (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] count_files: sort directory in inode order before  statting (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18  Stewart Smith         ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++     libs. (inbox unread)
+  2009-11-18  Lars Kellogg-Stedman  ┬►[notmuch] "notmuch help" outputs to stderr?         (attachment inbox signed unread)
+  2009-11-18  Lars Kellogg-Stedman  ╰─► ...                                               (attachment inbox signed unread)
+  2009-11-17  Mikhail Gusarov       ─►[notmuch] [PATCH] Handle rename of message file     (inbox unread)
+  2009-11-17  Alex Botero-Lowry     ┬►[notmuch] preliminary FreeBSD support               (attachment inbox unread)
+  2009-11-17  Carl Worth            ╰─► ...                                               (inbox unread)
+End of search results.
diff --git a/test/emacs.expected-output/attachment b/test/emacs.expected-output/attachment
new file mode 100644 (file)
index 0000000..1e22d3a
--- /dev/null
@@ -0,0 +1,32 @@
+From e3bc4bbd7b9d0d086816ab5f8f2d6ffea1dd3ea4 Mon Sep 17 00:00:00 2001
+From: Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+Date: Tue, 17 Nov 2009 11:30:39 -0800
+Subject: [PATCH] Deal with situation where sysconf(_SC_GETPW_R_SIZE_MAX) returns -1
+
+---
+ notmuch-config.c |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/notmuch-config.c b/notmuch-config.c
+index 248149c..e7220d8 100644
+--- a/notmuch-config.c
++++ b/notmuch-config.c
+@@ -77,6 +77,7 @@ static char *
+ get_name_from_passwd_file (void *ctx)
+ {
+     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
++    if (pw_buf_size == -1) pw_buf_size = 64;
+     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+     struct passwd passwd, *ignored;
+     char *name;
+@@ -101,6 +102,7 @@ static char *
+ get_username_from_passwd_file (void *ctx)
+ {
+     long pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
++    if (pw_buf_size == -1) pw_buf_size = 64;
+     char *pw_buf = talloc_zero_size (ctx, pw_buf_size);
+     struct passwd passwd, *ignored;
+     char *name;
+-- 
+1.6.5.2
+
diff --git a/test/emacs.expected-output/notmuch-hello b/test/emacs.expected-output/notmuch-hello
new file mode 100644 (file)
index 0000000..8918608
--- /dev/null
@@ -0,0 +1,12 @@
+   Welcome to notmuch. You have 52 messages.
+
+Saved searches: [edit]
+
+         52 inbox           52 unread          52 all mail
+
+Search:                                                                     .
+
+All tags: [show]
+
+        Hit `?' for context-sensitive help in any Notmuch screen.
+                     Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-empty-custom-queries-section b/test/emacs.expected-output/notmuch-hello-empty-custom-queries-section
new file mode 100644 (file)
index 0000000..cd0fdf0
--- /dev/null
@@ -0,0 +1,3 @@
+: [hide]
+
+
diff --git a/test/emacs.expected-output/notmuch-hello-empty-custom-tags-section b/test/emacs.expected-output/notmuch-hello-empty-custom-tags-section
new file mode 100644 (file)
index 0000000..b56fd67
--- /dev/null
@@ -0,0 +1,5 @@
+: [hide]
+
+          4 attachment             7 signed
+         52 inbox                 52 unread
+
diff --git a/test/emacs.expected-output/notmuch-hello-long-names b/test/emacs.expected-output/notmuch-hello-long-names
new file mode 100644 (file)
index 0000000..da0f352
--- /dev/null
@@ -0,0 +1,15 @@
+   Welcome to notmuch. You have 52 messages.
+
+Saved searches: [edit]
+
+         52 inbox           52 unread          52 all mail
+
+Search:                                                                     .
+
+All tags: [hide]
+
+         52 a-very-long-tag       52 inbox                 52 unread
+          4 attachment             7 signed
+
+        Hit `?' for context-sensitive help in any Notmuch screen.
+                     Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-new-section b/test/emacs.expected-output/notmuch-hello-new-section
new file mode 100644 (file)
index 0000000..67fdef2
--- /dev/null
@@ -0,0 +1,4 @@
+Test: [hide]
+
+         52 inbox
+
diff --git a/test/emacs.expected-output/notmuch-hello-no-saved-searches b/test/emacs.expected-output/notmuch-hello-no-saved-searches
new file mode 100644 (file)
index 0000000..939965f
--- /dev/null
@@ -0,0 +1,8 @@
+   Welcome to notmuch. You have 52 messages.
+
+Search:                                                                     .
+
+All tags: [show]
+
+        Hit `?' for context-sensitive help in any Notmuch screen.
+                     Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-hello-section-counts b/test/emacs.expected-output/notmuch-hello-section-counts
new file mode 100644 (file)
index 0000000..7a9827c
--- /dev/null
@@ -0,0 +1,5 @@
+Test-with-counts: [hide]
+
+          2 attachment             7 signed
+          7 inbox                  7 unread
+
diff --git a/test/emacs.expected-output/notmuch-hello-section-hidden-tag b/test/emacs.expected-output/notmuch-hello-section-hidden-tag
new file mode 100644 (file)
index 0000000..809a114
--- /dev/null
@@ -0,0 +1,4 @@
+Test-with-filtered: [hide]
+
+          4 attachment            52 inbox                  7 signed
+
diff --git a/test/emacs.expected-output/notmuch-hello-section-with-empty b/test/emacs.expected-output/notmuch-hello-section-with-empty
new file mode 100644 (file)
index 0000000..5c67317
--- /dev/null
@@ -0,0 +1,4 @@
+Test-with-empty: [hide]
+
+         52 inbox
+
diff --git a/test/emacs.expected-output/notmuch-hello-view-inbox b/test/emacs.expected-output/notmuch-hello-view-inbox
new file mode 100644 (file)
index 0000000..1688d67
--- /dev/null
@@ -0,0 +1,25 @@
+  2009-11-17 [5/5]   Mikhail Gusarov, Carl Worth, Keith Packard  [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-17 [7/7]   Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth  [notmuch] Working with Maildir storage? (inbox signed unread)
+  2009-11-17 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] preliminary FreeBSD support (attachment inbox unread)
+  2009-11-17 [1/1]   Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file (inbox unread)
+  2009-11-17 [2/2]   Keith Packard, Carl Worth    [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-17 [2/2]   Jan Janak, Carl Worth        [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-17 [3/3]   Jan Janak, Carl Worth        [notmuch] What a great idea! (inbox unread)
+  2009-11-17 [3/3]   Israel Herraiz, Keith Packard, Carl Worth   [notmuch] New to the list (inbox unread)
+  2009-11-17 [3/3]   Adrian Perez de Castro, Keith Packard, Carl Worth  [notmuch] Introducing myself (inbox signed unread)
+  2009-11-17 [3/3]   Aron Griffis, Keith Packard, Carl Worth     [notmuch] archive (inbox unread)
+  2009-11-17 [2/2]   Ingmar Vanhassel, Carl Worth  [notmuch] [PATCH] Typsos (inbox unread)
+  2009-11-18 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18 [2/2]   Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+  2009-11-18 [4/4]   Jjgod Jiang, Alexander Botero-Lowry      [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+  2009-11-18 [1/1]   Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18 [1/1]   Rolland Santimano    [notmuch] Link to mailing list archives ? (inbox unread)
+  2009-11-18 [1/1]   Alexander Botero-Lowry  [notmuch] request for pull (inbox unread)
+  2009-11-18 [2/2]   Keith Packard, Alexander Botero-Lowry    [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18 [1/1]   Chris Wilson         [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2010-12-16 [1/1]   Olivier Berger       Essai accentué (inbox unread)
+  2010-12-29 [1/1]   François Boulogne    [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+End of search results.
diff --git a/test/emacs.expected-output/notmuch-hello-with-empty b/test/emacs.expected-output/notmuch-hello-with-empty
new file mode 100644 (file)
index 0000000..97d7db2
--- /dev/null
@@ -0,0 +1,12 @@
+   Welcome to notmuch. You have 52 messages.
+
+Saved searches: [edit]
+
+         52 inbox           52 unread           0 empty
+
+Search:                                                                     .
+
+All tags: [show]
+
+        Hit `?' for context-sensitive help in any Notmuch screen.
+                     Customize Notmuch or this page.
diff --git a/test/emacs.expected-output/notmuch-search-tag-inbox b/test/emacs.expected-output/notmuch-search-tag-inbox
new file mode 100644 (file)
index 0000000..8a53555
--- /dev/null
@@ -0,0 +1,25 @@
+  2010-12-29 [1/1]   François Boulogne    [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
+  2010-12-16 [1/1]   Olivier Berger       Essai accentué (inbox unread)
+  2009-11-18 [1/1]   Chris Wilson         [notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
+  2009-11-18 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+  2009-11-18 [2/2]   Ingmar Vanhassel, Carl Worth  [notmuch] [PATCH] Typsos (inbox unread)
+  2009-11-18 [3/3]   Adrian Perez de Castro, Keith Packard, Carl Worth  [notmuch] Introducing myself (inbox signed unread)
+  2009-11-18 [3/3]   Israel Herraiz, Keith Packard, Carl Worth   [notmuch] New to the list (inbox unread)
+  2009-11-18 [3/3]   Jan Janak, Carl Worth        [notmuch] What a great idea! (inbox unread)
+  2009-11-18 [2/2]   Jan Janak, Carl Worth        [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+  2009-11-18 [3/3]   Aron Griffis, Keith Packard, Carl Worth     [notmuch] archive (inbox unread)
+  2009-11-18 [2/2]   Keith Packard, Carl Worth    [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+  2009-11-18 [7/7]   Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth  [notmuch] Working with Maildir storage? (inbox signed unread)
+  2009-11-18 [5/5]   Mikhail Gusarov, Carl Worth, Keith Packard  [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+  2009-11-18 [2/2]   Keith Packard, Alexander Botero-Lowry    [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
+  2009-11-18 [1/1]   Alexander Botero-Lowry  [notmuch] request for pull (inbox unread)
+  2009-11-18 [4/4]   Jjgod Jiang, Alexander Botero-Lowry      [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
+  2009-11-18 [1/1]   Rolland Santimano    [notmuch] Link to mailing list archives ? (inbox unread)
+  2009-11-18 [1/1]   Jan Janak            [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
+  2009-11-18 [1/1]   Stewart Smith        [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
+  2009-11-18 [2/2]   Lars Kellogg-Stedman [notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
+  2009-11-17 [1/1]   Mikhail Gusarov      [notmuch] [PATCH] Handle rename of message file (inbox unread)
+  2009-11-17 [2/2]   Alex Botero-Lowry, Carl Worth  [notmuch] preliminary FreeBSD support (attachment inbox unread)
+End of search results.
diff --git a/test/emacs.expected-output/notmuch-show-message-with-headers-hidden b/test/emacs.expected-output/notmuch-show-message-with-headers-hidden
new file mode 100644 (file)
index 0000000..9d7f91b
--- /dev/null
@@ -0,0 +1,22 @@
+Jan Janak <jan@ryngle.com> (2009-11-17) (inbox unread)
+Subject: [notmuch] What a great idea!
+ Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+
+ On Tue, Nov 17, 2009 at 11:35 PM, Jan Janak <jan at ryngle.com> wrote:
+ > Hello,
+ >
+ > First of all, notmuch is a wonderful idea, both the cmdline tool and
+ [ 2 more citation lines. Click/Enter to show. ]
+ >
+ > Have you considered sending an announcement to the org-mode mailing list?
+ > http://org-mode.org
+
+ Sorry, wrong URL, the correct one is: http://orgmode.org
+
+ > Various ways of searching/referencing emails from emacs were discussed
+ > there several times and none of them were as elegant as notmuch (not
+ > even close). Maybe notmuch would attract some of the developers
+ > there..
+
+   -- Jan
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs.expected-output/notmuch-show-message-with-headers-visible b/test/emacs.expected-output/notmuch-show-message-with-headers-visible
new file mode 100644 (file)
index 0000000..8efbd60
--- /dev/null
@@ -0,0 +1,25 @@
+Jan Janak <jan@ryngle.com> (2009-11-17) (inbox unread)
+Subject: [notmuch] What a great idea!
+ Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+ Subject: [notmuch] What a great idea!
+ To: notmuch@notmuchmail.org
+ Date: Tue, 17 Nov 2009 23:38:47 +0100
+
+ On Tue, Nov 17, 2009 at 11:35 PM, Jan Janak <jan at ryngle.com> wrote:
+ > Hello,
+ >
+ > First of all, notmuch is a wonderful idea, both the cmdline tool and
+ [ 2 more citation lines. Click/Enter to show. ]
+ >
+ > Have you considered sending an announcement to the org-mode mailing list?
+ > http://org-mode.org
+
+ Sorry, wrong URL, the correct one is: http://orgmode.org
+
+ > Various ways of searching/referencing emails from emacs were discussed
+ > there several times and none of them were as elegant as notmuch (not
+ > even close). Maybe notmuch would attract some of the developers
+ > there..
+
+   -- Jan
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage b/test/emacs.expected-output/notmuch-show-thread-maildir-storage
new file mode 100644 (file)
index 0000000..1f89dbe
--- /dev/null
@@ -0,0 +1,218 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+ Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+ Subject: Re: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 01:02:38 +0600
+
+ [ multipart/mixed ]
+ [ multipart/signed ]
+ [ Unknown key ID 0x9D20F6503E338888 or unsupported algorithm ]
+ [ text/plain ]
+
+ Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
+ gyre and gimble:
+
+  LK> Resulted in 4604 lines of errors along the lines of:
+
+  LK>   Error opening
+  LK>  
+ /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  LK>   Too many open files
+
+ See the patch just posted here.
+
+ [ 2-line signature. Click/Enter to show. ]
+ -- 
+ http://fossarchy.blogspot.com/
+ [ application/pgp-signature ]
+ [ text/plain ]
+ [ 4-line signature. Click/Enter to show. ]
+ _______________________________________________
+ notmuch mailing list
+ notmuch@notmuchmail.org
+ http://notmuchmail.org/mailman/listinfo/notmuch
+  Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+  Subject: Re: [notmuch] Working with Maildir storage?
+  To: Mikhail Gusarov <dottedmag@dottedmag.net>
+  Cc: notmuch@notmuchmail.org
+  Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+  [ multipart/mixed ]
+  [ multipart/signed ]
+  [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+  [ text/plain ]
+  > See the patch just posted here.
+
+  Is the list archived anywhere?  The obvious archives
+  (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+  think I subscribed too late to get the patch (I only just saw the
+  discussion about it).
+
+  It doesn't look like the patch is in git yet.
+
+  -- Lars
+
+  [ 4-line signature. Click/Enter to show. ]
+  -- 
+  Lars Kellogg-Stedman <lars@seas.harvard.edu>
+  Senior Technologist, Computing and Information Technology
+  Harvard University School of Engineering and Applied Sciences
+  [ application/pgp-signature ]
+  [ text/plain ]
+  [ 4-line signature. Click/Enter to show. ]
+  _______________________________________________
+  notmuch mailing list
+  notmuch@notmuchmail.org
+  http://notmuchmail.org/mailman/listinfo/notmuch
+   Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+   Subject: [notmuch] Working with Maildir storage?
+   To: notmuch@notmuchmail.org
+   Date: Wed, 18 Nov 2009 02:50:48 +0600
+
+   Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu
+   did gyre and gimble:
+
+    LK> Is the list archived anywhere?  The obvious archives
+    LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+    LK> think I subscribed too late to get the patch (I only just saw the
+    LK> discussion about it).
+
+    LK> It doesn't look like the patch is in git yet.
+
+   Just has been pushed
+
+   [ 10-line signature. Click/Enter to show. ]
+   -- 
+   http://fossarchy.blogspot.com/
+   -------------- next part --------------
+   A non-text attachment was scrubbed...
+   Name: not available
+   Type: application/pgp-signature
+   Size: 834 bytes
+   Desc: not available
+   URL:
+   <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+   Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+   Subject: [notmuch] Working with Maildir storage?
+   To: notmuch@notmuchmail.org
+   Date: Tue, 17 Nov 2009 13:24:13 -0800
+
+   On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at
+   seas.harvard.edu> wrote:
+   > > See the patch just posted here.
+
+   I've also pushed a slightly more complicated (and complete) fix to my
+   private notmuch repository
+
+   git://keithp.com/git/notmuch
+
+   > Is the list archived anywhere?
+
+   Oops. Looks like Carl's mail server is broken. He's traveling to
+   Barcelona today and so it won't get fixed for a while.
+
+   Thanks to everyone for trying out notmuch!
+
+   -keith
+    Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+    Subject: Re: [notmuch] Working with Maildir storage?
+    To: Keith Packard <keithp@keithp.com>
+    Cc: notmuch@notmuchmail.org
+    Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+    [ multipart/mixed ]
+    [ multipart/signed ]
+    [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+    [ text/plain ]
+    > I've also pushed a slightly more complicated (and complete) fix to my
+    > private notmuch repository
+
+    The version of lib/messages.cc in your repo doesn't build because it's
+    missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+    [ 4-line signature. Click/Enter to show. ]
+    -- 
+    Lars Kellogg-Stedman <lars@seas.harvard.edu>
+    Senior Technologist, Computing and Information Technology
+    Harvard University School of Engineering and Applied Sciences
+    [ application/pgp-signature ]
+    [ text/plain ]
+    [ 4-line signature. Click/Enter to show. ]
+    _______________________________________________
+    notmuch mailing list
+    notmuch@notmuchmail.org
+    http://notmuchmail.org/mailman/listinfo/notmuch
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+ Subject: [notmuch] Working with Maildir storage?
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 02:08:10 -0800
+
+ On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at
+ seas.harvard.edu> wrote:
+ > I saw the LWN article and decided to take a look at notmuch.  I'm
+ > currently using mutt and mairix to index and read a collection of
+ > Maildir mail folders (around 40,000 messages total).
+
+ Welcome, Lars!
+
+ I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+ That's very interesting. So, thanks for coming and trying out notmuch.
+
+ >   Error opening
+ > /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ >   Too many open files
+
+ Sadly, the lwn article coincided with me having just introduced this
+ bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+ fairly quickly, but there was quite a bit of latency before I could push
+ the fix out. It should be fixed now.
+
+ > I'm curious if this is expected behavior (i.e., notmuch does not work
+ > with Maildir) or if something else is going on.
+
+ Notmuch works just fine with maildir---it's one of the things that it
+ likes the best.
+
+ Happy hacking,
+
+ -Carl
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-with-fourfold-indentation
new file mode 100644 (file)
index 0000000..5c4ec97
--- /dev/null
@@ -0,0 +1,223 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+    Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+    Subject: Re: [notmuch] Working with Maildir storage?
+    To: notmuch@notmuchmail.org
+    Date: Wed, 18 Nov 2009 01:02:38 +0600
+
+    [ multipart/mixed ]
+    [ multipart/signed ]
+    [ Unknown key ID 0x9D20F6503E338888 or unsupported algorithm ]
+    [ text/plain ]
+
+    Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did
+    gyre and gimble:
+
+     LK> Resulted in 4604 lines of errors along the lines of:
+
+     LK>   Error opening
+     LK>  
+    /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+     LK>   Too many open files
+
+    See the patch just posted here.
+
+    [ 2-line signature. Click/Enter to show. ]
+    -- 
+    http://fossarchy.blogspot.com/
+    [ application/pgp-signature ]
+    [ text/plain ]
+    [ 4-line signature. Click/Enter to show. ]
+    _______________________________________________
+    notmuch mailing list
+    notmuch@notmuchmail.org
+    http://notmuchmail.org/mailman/listinfo/notmuch
+        Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+       Subject: Re: [notmuch] Working with Maildir storage?
+       To: Mikhail Gusarov <dottedmag@dottedmag.net>
+       Cc: notmuch@notmuchmail.org
+       Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+       [ multipart/mixed ]
+       [ multipart/signed ]
+       [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+       [ text/plain ]
+       > See the patch just posted here.
+
+       Is the list archived anywhere?  The obvious archives
+       (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+       think I subscribed too late to get the patch (I only just saw the
+       discussion about it).
+
+       It doesn't look like the patch is in git yet.
+
+       -- Lars
+
+       [ 4-line signature. Click/Enter to show. ]
+       -- 
+       Lars Kellogg-Stedman <lars@seas.harvard.edu>
+       Senior Technologist, Computing and Information Technology
+       Harvard University School of Engineering and Applied Sciences
+       [ application/pgp-signature ]
+       [ text/plain ]
+       [ 4-line signature. Click/Enter to show. ]
+       _______________________________________________
+       notmuch mailing list
+       notmuch@notmuchmail.org
+       http://notmuchmail.org/mailman/listinfo/notmuch
+            Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+           Subject: [notmuch] Working with Maildir storage?
+           To: notmuch@notmuchmail.org
+           Date: Wed, 18 Nov 2009 02:50:48 +0600
+
+           Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at
+           seas.harvard.edu did gyre and gimble:
+
+            LK> Is the list archived anywhere?  The obvious archives
+            LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available,
+           and I
+            LK> think I subscribed too late to get the patch (I only just saw
+           the
+            LK> discussion about it).
+
+            LK> It doesn't look like the patch is in git yet.
+
+           Just has been pushed
+
+           [ 10-line signature. Click/Enter to show. ]
+           -- 
+           http://fossarchy.blogspot.com/
+           -------------- next part --------------
+           A non-text attachment was scrubbed...
+           Name: not available
+           Type: application/pgp-signature
+           Size: 834 bytes
+           Desc: not available
+           URL:
+           <http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+            Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+           Subject: [notmuch] Working with Maildir storage?
+           To: notmuch@notmuchmail.org
+           Date: Tue, 17 Nov 2009 13:24:13 -0800
+
+           On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at
+           seas.harvard.edu> wrote:
+           > > See the patch just posted here.
+
+           I've also pushed a slightly more complicated (and complete) fix to
+           my
+           private notmuch repository
+
+           git://keithp.com/git/notmuch
+
+           > Is the list archived anywhere?
+
+           Oops. Looks like Carl's mail server is broken. He's traveling to
+           Barcelona today and so it won't get fixed for a while.
+
+           Thanks to everyone for trying out notmuch!
+
+           -keith
+                Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+               Subject: Re: [notmuch] Working with Maildir storage?
+               To: Keith Packard <keithp@keithp.com>
+               Cc: notmuch@notmuchmail.org
+               Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+               [ multipart/mixed ]
+               [ multipart/signed ]
+               [ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+               [ text/plain ]
+               > I've also pushed a slightly more complicated (and complete)
+               > fix to my
+               > private notmuch repository
+
+               The version of lib/messages.cc in your repo doesn't build
+               because it's
+               missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+               [ 4-line signature. Click/Enter to show. ]
+               -- 
+               Lars Kellogg-Stedman <lars@seas.harvard.edu>
+               Senior Technologist, Computing and Information Technology
+               Harvard University School of Engineering and Applied Sciences
+               [ application/pgp-signature ]
+               [ text/plain ]
+               [ 4-line signature. Click/Enter to show. ]
+               _______________________________________________
+               notmuch mailing list
+               notmuch@notmuchmail.org
+               http://notmuchmail.org/mailman/listinfo/notmuch
+    Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+    Subject: [notmuch] Working with Maildir storage?
+    To: notmuch@notmuchmail.org
+    Date: Wed, 18 Nov 2009 02:08:10 -0800
+
+    On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at
+    seas.harvard.edu> wrote:
+    > I saw the LWN article and decided to take a look at notmuch.  I'm
+    > currently using mutt and mairix to index and read a collection of
+    > Maildir mail folders (around 40,000 messages total).
+
+    Welcome, Lars!
+
+    I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+    That's very interesting. So, thanks for coming and trying out notmuch.
+
+    >   Error opening
+    > /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+    >   Too many open files
+
+    Sadly, the lwn article coincided with me having just introduced this
+    bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+    fairly quickly, but there was quite a bit of latency before I could push
+    the fix out. It should be fixed now.
+
+    > I'm curious if this is expected behavior (i.e., notmuch does not work
+    > with Maildir) or if something else is going on.
+
+    Notmuch works just fine with maildir---it's one of the things that it
+    likes the best.
+
+    Happy hacking,
+
+    -Carl
diff --git a/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation b/test/emacs.expected-output/notmuch-show-thread-maildir-storage-without-indentation
new file mode 100644 (file)
index 0000000..24cdd56
--- /dev/null
@@ -0,0 +1,218 @@
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 14:00:54 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+I saw the LWN article and decided to take a look at notmuch.  I'm
+currently using mutt and mairix to index and read a collection of
+Maildir mail folders (around 40,000 messages total).
+
+notmuch indexed the messages without complaint, but my attempt at
+searching bombed out. Running, for example:
+
+  notmuch search storage
+
+Resulted in 4604 lines of errors along the lines of:
+
+  Error opening
+  /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+  Too many open files
+
+I'm curious if this is expected behavior (i.e., notmuch does not work
+with Maildir) or if something else is going on.
+
+Cheers,
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 01:02:38 +0600
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0x9D20F6503E338888 or unsupported algorithm ]
+[ text/plain ]
+
+Twas brillig at 14:00:54 17.11.2009 UTC-05 when lars@seas.harvard.edu did gyre
+and gimble:
+
+ LK> Resulted in 4604 lines of errors along the lines of:
+
+ LK>   Error opening
+ LK>  
+/home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+ LK>   Too many open files
+
+See the patch just posted here.
+
+[ 2-line signature. Click/Enter to show. ]
+-- 
+http://fossarchy.blogspot.com/
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-17) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 15:33:01 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+> See the patch just posted here.
+
+Is the list archived anywhere?  The obvious archives
+(http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+think I subscribed too late to get the patch (I only just saw the
+discussion about it).
+
+It doesn't look like the patch is in git yet.
+
+-- Lars
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Mikhail Gusarov <dottedmag@dottedmag.net> (2009-11-17) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:50:48 +0600
+
+Twas brillig at 15:33:01 17.11.2009 UTC-05 when lars at seas.harvard.edu did
+gyre and gimble:
+
+ LK> Is the list archived anywhere?  The obvious archives
+ LK> (http://notmuchmail.org/pipermail/notmuch/) aren't available, and I
+ LK> think I subscribed too late to get the patch (I only just saw the
+ LK> discussion about it).
+
+ LK> It doesn't look like the patch is in git yet.
+
+Just has been pushed
+
+[ 10-line signature. Click/Enter to show. ]
+-- 
+http://fossarchy.blogspot.com/
+-------------- next part --------------
+A non-text attachment was scrubbed...
+Name: not available
+Type: application/pgp-signature
+Size: 834 bytes
+Desc: not available
+URL:
+<http://notmuchmail.org/pipermail/notmuch/attachments/20091118/0e33d964/attachment.pgp>
+Keith Packard <keithp@keithp.com> (2009-11-17) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 13:24:13 -0800
+
+On Tue, 17 Nov 2009 15:33:01 -0500, Lars Kellogg-Stedman <lars at
+seas.harvard.edu> wrote:
+> > See the patch just posted here.
+
+I've also pushed a slightly more complicated (and complete) fix to my
+private notmuch repository
+
+git://keithp.com/git/notmuch
+
+> Is the list archived anywhere?
+
+Oops. Looks like Carl's mail server is broken. He's traveling to
+Barcelona today and so it won't get fixed for a while.
+
+Thanks to everyone for trying out notmuch!
+
+-keith
+Lars Kellogg-Stedman <lars@seas.harvard.edu> (2009-11-18) (inbox signed unread)
+Subject: Re: [notmuch] Working with Maildir storage?
+To: Keith Packard <keithp@keithp.com>
+Cc: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 19:50:40 -0500
+
+[ multipart/mixed ]
+[ multipart/signed ]
+[ Unknown key ID 0xD74695063141ACD8 or unsupported algorithm ]
+[ text/plain ]
+> I've also pushed a slightly more complicated (and complete) fix to my
+> private notmuch repository
+
+The version of lib/messages.cc in your repo doesn't build because it's
+missing "#include <stdint.h>" (for the uint32_t on line 466).
+
+[ 4-line signature. Click/Enter to show. ]
+-- 
+Lars Kellogg-Stedman <lars@seas.harvard.edu>
+Senior Technologist, Computing and Information Technology
+Harvard University School of Engineering and Applied Sciences
+[ application/pgp-signature ]
+[ text/plain ]
+[ 4-line signature. Click/Enter to show. ]
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+Subject: [notmuch] Working with Maildir storage?
+To: notmuch@notmuchmail.org
+Date: Wed, 18 Nov 2009 02:08:10 -0800
+
+On Tue, 17 Nov 2009 14:00:54 -0500, Lars Kellogg-Stedman <lars at
+seas.harvard.edu> wrote:
+> I saw the LWN article and decided to take a look at notmuch.  I'm
+> currently using mutt and mairix to index and read a collection of
+> Maildir mail folders (around 40,000 messages total).
+
+Welcome, Lars!
+
+I hadn't even seen that Keith's blog post had been picked up by lwn.net.
+That's very interesting. So, thanks for coming and trying out notmuch.
+
+>   Error opening
+> /home/lars/Mail/read-messages.2008/cur/1246413773.24928_27334.hostname,U=3026:2,S:
+>   Too many open files
+
+Sadly, the lwn article coincided with me having just introduced this
+bug, and then getting on a Trans-Atlantic flight. So I fixed the bug
+fairly quickly, but there was quite a bit of latency before I could push
+the fix out. It should be fixed now.
+
+> I'm curious if this is expected behavior (i.e., notmuch does not work
+> with Maildir) or if something else is going on.
+
+Notmuch works just fine with maildir---it's one of the things that it
+likes the best.
+
+Happy hacking,
+
+-Carl
diff --git a/test/emacs.expected-output/notmuch-show-thread-with-all-messages-collapsed b/test/emacs.expected-output/notmuch-show-thread-with-all-messages-collapsed
new file mode 100644 (file)
index 0000000..73b0e60
--- /dev/null
@@ -0,0 +1,4 @@
+Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+Subject: [notmuch] What a great idea!
+ Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs.expected-output/notmuch-show-thread-with-all-messages-uncollapsed b/test/emacs.expected-output/notmuch-show-thread-with-all-messages-uncollapsed
new file mode 100644 (file)
index 0000000..bd5598e
--- /dev/null
@@ -0,0 +1,79 @@
+Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+Subject: [notmuch] What a great idea!
+To: notmuch@notmuchmail.org
+Date: Tue, 17 Nov 2009 23:35:30 +0100
+
+Hello,
+
+First of all, notmuch is a wonderful idea, both the cmdline tool and
+the emacs interface! Thanks a lot for writing it, I was really excited
+when I read the announcement today.
+
+Have you considered sending an announcement to the org-mode mailing list?
+http://org-mode.org
+
+Various ways of searching/referencing emails from emacs were discussed
+there several times and none of them were as elegant as notmuch (not
+even close). Maybe notmuch would attract some of the developers
+there..
+
+   -- Jan
+ Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+ Subject: [notmuch] What a great idea!
+ To: notmuch@notmuchmail.org
+ Date: Tue, 17 Nov 2009 23:38:47 +0100
+
+ On Tue, Nov 17, 2009 at 11:35 PM, Jan Janak <jan at ryngle.com> wrote:
+ > Hello,
+ >
+ > First of all, notmuch is a wonderful idea, both the cmdline tool and
+ [ 2 more citation lines. Click/Enter to show. ]
+ >
+ > Have you considered sending an announcement to the org-mode mailing list?
+ > http://org-mode.org
+
+ Sorry, wrong URL, the correct one is: http://orgmode.org
+
+ > Various ways of searching/referencing emails from emacs were discussed
+ > there several times and none of them were as elegant as notmuch (not
+ > even close). Maybe notmuch would attract some of the developers
+ > there..
+
+   -- Jan
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
+ Subject: [notmuch] What a great idea!
+ To: notmuch@notmuchmail.org
+ Date: Wed, 18 Nov 2009 02:49:52 -0800
+
+ On Tue, 17 Nov 2009 23:35:30 +0100, Jan Janak <jan at ryngle.com> wrote:
+ > First of all, notmuch is a wonderful idea, both the cmdline tool and
+ > the emacs interface! Thanks a lot for writing it, I was really excited
+ > when I read the announcement today.
+
+ Ah, here's where I planned a nice welcome. So welcome (again), Jan! :-)
+
+ I've been having a lot of fun with notmuch already, (though there have
+ been some days of pain before it was functional enough and my
+ email-reply latency went way up). But regardless---I got through that,
+ and I'm able to work more efficiently with notmuch now than I could with
+ sup before. So I'm happy.
+
+ And I'm delighted when other people find this interesting as well.
+
+ > Have you considered sending an announcement to the org-mode mailing list?
+ > http://orgmode.org
+
+ Thanks for the idea. I think I may have looked into org-mode years ago,
+ (when I was investigating planner-mode and various emacs "personal wiki"
+ systems for keeping random notes and what-not).
+
+ > Various ways of searching/referencing emails from emacs were discussed
+ > there several times and none of them were as elegant as notmuch (not
+ > even close). Maybe notmuch would attract some of the developers
+ > there..
+
+ Yeah. I'll drop them a mail. Having a real emacs wizard on board would
+ be nice. (I'm afraid the elisp I've written so far for this project is
+ fairly grim.)
+
+ -Carl
diff --git a/test/emacs.expected-output/notmuch-show-thread-with-hidden-messages b/test/emacs.expected-output/notmuch-show-thread-with-hidden-messages
new file mode 100644 (file)
index 0000000..8a0660f
--- /dev/null
@@ -0,0 +1,4 @@
+Jan Janak <jan@ryngle.com> (2009-11-17) (inbox unread)
+Subject: [notmuch] What a great idea!
+ Jan Janak <jan@ryngle.com> (2009-11-17) (inbox)
+ Carl Worth <cworth@cworth.org> (2009-11-18) (inbox unread)
diff --git a/test/emacs.expected-output/raw-message-cf0c4d-52ad0a b/test/emacs.expected-output/raw-message-cf0c4d-52ad0a
new file mode 100644 (file)
index 0000000..75b05fa
--- /dev/null
@@ -0,0 +1,104 @@
+MIME-Version: 1.0
+Date: Tue, 17 Nov 2009 11:36:14 -0800
+Message-ID: <cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com>
+From: Alex Botero-Lowry <alex.boterolowry@gmail.com>
+To: notmuch@notmuchmail.org
+Content-Type: multipart/mixed; boundary=0016e687869333b1570478963d35
+Subject: [notmuch] preliminary FreeBSD support
+X-BeenThere: notmuch@notmuchmail.org
+X-Mailman-Version: 2.1.12
+Precedence: list
+List-Id: "Use and development of the notmuch mail system."
+       <notmuch.notmuchmail.org>
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>
+List-Post: <mailto:notmuch@notmuchmail.org>
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
+Sender: notmuch-bounces@notmuchmail.org
+Errors-To: notmuch-bounces@notmuchmail.org
+
+--0016e687869333b1570478963d35
+Content-Type: multipart/alternative; boundary=0016e687869333b14e0478963d33
+
+--0016e687869333b14e0478963d33
+Content-Type: text/plain; charset=ISO-8859-1
+
+I saw the announcement this morning, and was very excited, as I had been
+hoping sup would be turned into a library,
+since I like the concept more than the UI (I'd rather an emacs interface).
+
+I did a preliminary compile which worked out fine, but
+sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns -1 on
+FreeBSD, so notmuch_config_open segfaulted.
+
+Attached is a patch that supplies a default buffer size of 64 in cases where
+-1 is returned.
+
+http://www.opengroup.org/austin/docs/austin_328.txt - seems to indicate this
+is acceptable behavior,
+and http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.htmlspecifically
+uses 64 as the
+buffer size.
+
+--0016e687869333b14e0478963d33
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+I saw the announcement this morning, and was very excited, as I had been ho=
+ping sup would be turned into a library,<br>since I like the concept more t=
+han the UI (I&#39;d rather an emacs interface).<br><br>I did a preliminary =
+compile which worked out fine, but sysconf(_SC_SC_GETPW_R_SIZE_MAX) returns=
+ -1 on<br>
+FreeBSD, so notmuch_config_open segfaulted.<br><br>Attached is a patch that=
+ supplies a default buffer size of 64 in cases where -1 is returned.<br><br=
+><a href=3D"http://www.opengroup.org/austin/docs/austin_328.txt">http://www=
+.opengroup.org/austin/docs/austin_328.txt</a> - seems to indicate this is a=
+cceptable behavior,<br>
+and <a href=3D"http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg01680=
+8.html">http://mail-index.netbsd.org/pkgsrc-bugs/2006/06/07/msg016808.html<=
+/a> specifically uses 64 as the<br>buffer size.<br><br><br>
+
+--0016e687869333b14e0478963d33--
+--0016e687869333b1570478963d35
+Content-Type: application/octet-stream; 
+       name="0001-Deal-with-situation-where-sysconf-_SC_GETPW_R_SIZE_M.patch"
+Content-Disposition: attachment; 
+       filename="0001-Deal-with-situation-where-sysconf-_SC_GETPW_R_SIZE_M.patch"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_g252e6gs0
+
+RnJvbSBlM2JjNGJiZDdiOWQwZDA4NjgxNmFiNWY4ZjJkNmZmZWExZGQzZWE0IE1vbiBTZXAgMTcg
+MDA6MDA6MDAgMjAwMQpGcm9tOiBBbGV4YW5kZXIgQm90ZXJvLUxvd3J5IDxhbGV4LmJvdGVyb2xv
+d3J5QGdtYWlsLmNvbT4KRGF0ZTogVHVlLCAxNyBOb3YgMjAwOSAxMTozMDozOSAtMDgwMApTdWJq
+ZWN0OiBbUEFUQ0hdIERlYWwgd2l0aCBzaXR1YXRpb24gd2hlcmUgc3lzY29uZihfU0NfR0VUUFdf
+Ul9TSVpFX01BWCkgcmV0dXJucyAtMQoKLS0tCiBub3RtdWNoLWNvbmZpZy5jIHwgICAgMiArKwog
+MSBmaWxlcyBjaGFuZ2VkLCAyIGluc2VydGlvbnMoKyksIDAgZGVsZXRpb25zKC0pCgpkaWZmIC0t
+Z2l0IGEvbm90bXVjaC1jb25maWcuYyBiL25vdG11Y2gtY29uZmlnLmMKaW5kZXggMjQ4MTQ5Yy4u
+ZTcyMjBkOCAxMDA2NDQKLS0tIGEvbm90bXVjaC1jb25maWcuYworKysgYi9ub3RtdWNoLWNvbmZp
+Zy5jCkBAIC03Nyw2ICs3Nyw3IEBAIHN0YXRpYyBjaGFyICoKIGdldF9uYW1lX2Zyb21fcGFzc3dk
+X2ZpbGUgKHZvaWQgKmN0eCkKIHsKICAgICBsb25nIHB3X2J1Zl9zaXplID0gc3lzY29uZihfU0Nf
+R0VUUFdfUl9TSVpFX01BWCk7CisgICAgaWYgKHB3X2J1Zl9zaXplID09IC0xKSBwd19idWZfc2l6
+ZSA9IDY0OwogICAgIGNoYXIgKnB3X2J1ZiA9IHRhbGxvY196ZXJvX3NpemUgKGN0eCwgcHdfYnVm
+X3NpemUpOwogICAgIHN0cnVjdCBwYXNzd2QgcGFzc3dkLCAqaWdub3JlZDsKICAgICBjaGFyICpu
+YW1lOwpAQCAtMTAxLDYgKzEwMiw3IEBAIHN0YXRpYyBjaGFyICoKIGdldF91c2VybmFtZV9mcm9t
+X3Bhc3N3ZF9maWxlICh2b2lkICpjdHgpCiB7CiAgICAgbG9uZyBwd19idWZfc2l6ZSA9IHN5c2Nv
+bmYoX1NDX0dFVFBXX1JfU0laRV9NQVgpOworICAgIGlmIChwd19idWZfc2l6ZSA9PSAtMSkgcHdf
+YnVmX3NpemUgPSA2NDsKICAgICBjaGFyICpwd19idWYgPSB0YWxsb2NfemVyb19zaXplIChjdHgs
+IHB3X2J1Zl9zaXplKTsKICAgICBzdHJ1Y3QgcGFzc3dkIHBhc3N3ZCwgKmlnbm9yZWQ7CiAgICAg
+Y2hhciAqbmFtZTsKLS0gCjEuNi41LjIKCg==
+--0016e687869333b1570478963d35
+Content-Type: text/plain; charset="us-ascii"
+MIME-Version: 1.0
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+_______________________________________________
+notmuch mailing list
+notmuch@notmuchmail.org
+http://notmuchmail.org/mailman/listinfo/notmuch
+
+--0016e687869333b1570478963d35--
+
diff --git a/test/export-dirs.sh b/test/export-dirs.sh
new file mode 100644 (file)
index 0000000..0578b1e
--- /dev/null
@@ -0,0 +1,32 @@
+# Source this script to set and export NOTMUCH_SRCDIR and
+# NOTMUCH_BUILDDIR.
+#
+# For this to work, always have current directory somewhere within the
+# build directory hierarchy, and run the script sourcing this script
+# using a path (relative or absolute) to the source directory.
+
+if [[ -z "${NOTMUCH_SRCDIR}" ]]; then
+       export NOTMUCH_SRCDIR="$(cd "$(dirname "$0")"/.. && pwd)"
+fi
+
+find_builddir()
+{
+       local dir="$1"
+
+       while [[ -n "$dir" ]] && [[ "$dir" != "/" ]]; do
+               if [[ -x "$dir/notmuch" ]] && [[ ! -d "$dir/notmuch" ]]; then
+                       echo "$dir"
+                       break
+               fi
+               dir="$(dirname "$dir")"
+       done
+}
+
+if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+       export NOTMUCH_BUILDDIR="$(find_builddir "$(pwd)")"
+
+       if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+               echo "Run tests in a subdir of built notmuch tree." >&2
+               exit 1
+       fi
+fi
diff --git a/test/gen-threads.py b/test/gen-threads.py
new file mode 100644 (file)
index 0000000..70fb1f6
--- /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 = {child: to_expand[0] for child in children}
+            ntree.update(tree)
+            nfree = free.difference(children)
+            queue.append((ntree, nfree, to_expand[1:] + tuple(children)))
diff --git a/test/ghost-report.cc b/test/ghost-report.cc
new file mode 100644 (file)
index 0000000..3e1b07c
--- /dev/null
@@ -0,0 +1,14 @@
+#include <iostream>
+#include <cstdlib>
+#include <xapian.h>
+
+int main(int argc, char **argv) {
+
+    if (argc < 2) {
+       std::cerr << "usage: ghost-report xapian-dir" << std::endl;
+       exit(1);
+    }
+
+    Xapian::Database db(argv[1]);
+    std::cout << db.get_termfreq("Tghost") << std::endl;
+}
diff --git a/test/gnupg-secret-key.NOTE b/test/gnupg-secret-key.NOTE
new file mode 100644 (file)
index 0000000..604496c
--- /dev/null
@@ -0,0 +1,9 @@
+How the crypto test gnupg secret was generated:
+
+GNUPGHOME=gnupghome gpg --quick-random --gen-key
+kind: 1 (RSA/RSA)
+size: 1024
+expire: 0
+name: Notmuch Test Suite
+email: test_suite@notmuchmail.org
+(no passphrase)
diff --git a/test/gnupg-secret-key.asc b/test/gnupg-secret-key.asc
new file mode 100644 (file)
index 0000000..6431b56
--- /dev/null
@@ -0,0 +1,34 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+lQHYBE1Mm18BBADlMsMlUeO6usp/XuulgimqlCSphHcYZvH6+Sy7u7W4TpJzid7e
+jEOCrk3UZi2XMPW9+snDMhV9e28HeRz61zAO9G/gedn4N+mKOyTaELEmj9SP2IG2
+ZTvdUvn30vWIHyfRIww3qEiSzNULKn6zTDfcg6BIY6ZDQ6GFSfH5EioxuQARAQAB
+AAP8CM2/sS9JZWLHZHJrmsU6fygxlaarlxmyhxwLG9WZ+qUJ+xDQqWZkhssrMigP
+7ZQehwLwZ7mvbvfOy/qwTPJMZjQMMuTGEzclwBTOTttSxEDS+kgYmZ05CBjIgXbo
+8+k+L347l+kVRBFsi1cqOkCr+VZQwhOnbeNb8uJsUx27aAECAPD7jsBP73LRgoXQ
+x650D2fzjjuomGVsIxSAPjkDRYmtorsRftaEy7DkvX3Ihu5WN6YRRjJavoL+f8ar
+4escR40CAPN7NOFOGmiFZYzQcfJYQI2m7YDk4B51JxORFvLrvGT+LJnVwhtFsdGS
+QnMyO4eNpKH0qeEkT5Zqha2oyAc0Yd0B/3f962YCmYlbDAvWjcbMvhV7G4DbazVp
+2TNR0BhhEMiOlHuwmTO59s2iukuE5ifaVbwrj+NgpipTsaffKnhALlGjV7Q7Tm90
+bXVjaCBUZXN0IFN1aXRlIDx0ZXN0X3N1aXRlQG5vdG11Y2htYWlsLm9yZz4gKElO
+U0VDVVJFISmIuAQTAQIAIgUCTUybXwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC
+F4AACgkQbZJhLZTkY4GJFAP9E0mOw+RUGdmqbxSbd2rm0/inUSYOC0Pvt/D05pUY
+xzXDAMZwsy1DWhfS7bSgdD3YTM/22b/LJ2FmbLUF1cU6cNslmdPdfHDZ5+C4qpa1
+uW11y7djlBFAwxc3NBypT6Bmh/iIixrx413cw8CEU0lSZbSXUvbxZ7Rg4JYm2K6f
+Y7SdAdgETUybXwEEAM74QJJWzPavquSF0IkKDFjEvI44WC1HGNsJF3JVuKv9G00P
+RaHavNNcHEG8MorbfaWk7pipaEJ3+zbPKgp2vRCSJnLL6z813JIQqXJTZzu1ip63
+s4icfOfXkxFJ5AaFd/pVdi+wjmEwvv+YMtJT9DyXANo6b2eQu+0bMtP4Xuv/ABEB
+AAEAA/wJArUJw450070K6eoXeg22wT0iq/O0aCExSzoI5Kmywytj6KnnAmp9TftL
+WVgrkQntVjrhzPsYoB40JEMrGKd7QL/6LPTNWq3eFW38PSpCiG83T0rtmKCKqHB1
+Uo0B78AHfYYX7MUOEuCq2AhKTAdZukesoCpmVxcEFtjDEbOB8QIA3cvXrPJN/J2S
+W61mdMT7KlaXZZD8Phs/TY2ZLAiMKUAP1dVYNDvRSDjZLvQrqKQjEAN5jM81cWAV
+pvOIavLhOwIA7uMVIiaQ3vIy10C7ltiLT6YuJL/O6XDnXY/PDuXOatQahd/gmI0q
+dGQLSaHIxYILPZPaW6t0orx+dduQ0ES0DQIA21nEKX0MZpYOY1eIt6OlKemsjL2a
+UTdFhq/OgwVv+QRVHNdYQXmKpKDeW30lN/+BI3zyDTZjtehwKMMxNTu4AJu/iJ8E
+GAECAAkFAk1Mm18CGwwACgkQbZJhLZTkY4H8kgQA4vHsTt8dlJdWJAu2SKZGOPRs
+bIPu5XtRXe3RYbW5H7PqbAnrKIzlIKpkPNTwLL4wVXaF+R/aHa8ZKX3paohrPL74
+qpbffwtHXyVEwyWlw3m9mgti0de1dy1YvVasCe/UQ8Frc6uNmOwtlQE20k4R4cLI
+SWXT1JrwPoKh9xe++90=
+=rvTR
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/test/hex-xcode.c b/test/hex-xcode.c
new file mode 100644 (file)
index 0000000..33046e9
--- /dev/null
@@ -0,0 +1,109 @@
+/* No, nothing to to with IDE from Apple Inc.
+ * testbed for ../util/hex-escape.c.
+ *
+ * usage:
+ * hex-xcode [--direction=(encode|decode)] [--omit-newline] < file
+ * hex-xcode [--direction=(encode|decode)] [--omit-newline] [--in-place] arg1 arg2 arg3 ...
+ *
+ */
+
+#include "notmuch-client.h"
+#include "hex-escape.h"
+#include <assert.h>
+
+enum direction {
+    ENCODE,
+    DECODE
+};
+
+static bool inplace = false;
+
+static int
+xcode (void *ctx, enum direction dir, char *in, char **buf_p, size_t *size_p)
+{
+    hex_status_t status;
+
+    if (dir == ENCODE)
+       status = hex_encode (ctx, in, buf_p, size_p);
+    else
+       if (inplace) {
+           status = hex_decode_inplace (in);
+           *buf_p = in;
+           *size_p = strlen(in);
+       } else {
+           status = hex_decode (ctx, in, buf_p, size_p);
+       }
+
+    if (status == HEX_SUCCESS)
+       fputs (*buf_p, stdout);
+
+    return status;
+}
+
+int
+main (int argc, char **argv)
+{
+
+    int dir = DECODE;
+    bool omit_newline = false;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_keyword = &dir, .name = "direction", .keywords =
+         (notmuch_keyword_t []){ { "encode", ENCODE },
+                                 { "decode", DECODE },
+                                 { 0, 0 } } },
+       { .opt_bool = &omit_newline, .name = "omit-newline" },
+       { .opt_bool = &inplace, .name = "in-place" },
+       { }
+    };
+
+    int opt_index = parse_arguments (argc, argv, options, 1);
+
+    if (opt_index < 0)
+       exit (1);
+
+    void *ctx = talloc_new (NULL);
+
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
+
+    char *buffer = NULL;
+    size_t buf_size = 0;
+
+    bool read_stdin = true;
+
+    for (; opt_index < argc; opt_index++) {
+
+       if (xcode (ctx, dir, argv[opt_index],
+                  &buffer, &buf_size) != HEX_SUCCESS)
+           return 1;
+
+       if (! omit_newline)
+           putchar ('\n');
+
+       read_stdin = false;
+    }
+
+    if (! read_stdin)
+       return 0;
+
+    while ((line_len = getline (&line, &line_size, stdin)) != -1) {
+
+       chomp_newline (line);
+
+       if (xcode (ctx, dir, line, &buffer, &buf_size) != HEX_SUCCESS)
+           return 1;
+
+       if (! omit_newline)
+           putchar ('\n');
+
+    }
+
+    if (line)
+       free (line);
+
+    talloc_free (ctx);
+
+    return 0;
+}
diff --git a/test/make-db-version.cc b/test/make-db-version.cc
new file mode 100644 (file)
index 0000000..fa80cac
--- /dev/null
@@ -0,0 +1,35 @@
+/* Create an empty notmuch database with a specific version and
+ * features. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <xapian.h>
+
+int main(int argc, char **argv)
+{
+    if (argc != 4) {
+       fprintf (stderr, "Usage: %s mailpath version features\n", argv[0]);
+       exit (2);
+    }
+
+    std::string nmpath (argv[1]);
+    nmpath += "/.notmuch";
+    if (mkdir (nmpath.c_str (), 0777) < 0) {
+       perror (("failed to create " + nmpath).c_str ());
+       exit (1);
+    }
+
+    try {
+       Xapian::WritableDatabase db (
+           nmpath + "/xapian", Xapian::DB_CREATE_OR_OPEN);
+       db.set_metadata ("version", argv[2]);
+       db.set_metadata ("features", argv[3]);
+       db.commit ();
+    } catch (const Xapian::Error &e) {
+       fprintf (stderr, "%s\n", e.get_description ().c_str ());
+       exit (1);
+    }
+}
diff --git a/test/message-id-parse.c b/test/message-id-parse.c
new file mode 100644 (file)
index 0000000..752eb1f
--- /dev/null
@@ -0,0 +1,26 @@
+#include <stdio.h>
+#include <talloc.h>
+#include "notmuch-private.h"
+
+int
+main (unused (int argc), unused (char **argv))
+{
+    char *line = NULL;
+    size_t len = 0;
+    ssize_t nread;
+    void *local = talloc_new (NULL);
+
+    while ((nread = getline (&line, &len, stdin)) != -1) {
+       int last = strlen (line) - 1;
+       if (line[last] == '\n')
+           line[last] = '\0';
+
+       char *mid = _notmuch_message_id_parse_strict (local, line);
+       if (mid)
+           printf ("GOOD: %s\n", mid);
+       else
+           printf ("BAD: %s\n", line);
+    }
+
+    talloc_free (local);
+}
diff --git a/test/notmuch-test b/test/notmuch-test
new file mode 100755 (executable)
index 0000000..ca68dd4
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
+#
+# Adapted from a Makefile to a shell script by Carl Worth (2010)
+
+if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
+    echo "Error: The notmuch test suite requires a bash version >= 4.0"
+    echo "due to use of associative arrays within the test suite."
+    echo "Please try again with a newer bash (or help us fix the"
+    echo "test suite to be more portable). Thanks."
+    exit 1
+fi
+
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
+
+TESTS=
+for test in $NOTMUCH_TESTS; do
+    TESTS="$TESTS $NOTMUCH_SRCDIR/test/$test"
+done
+
+if [[ -z "$TESTS" ]]; then
+    TESTS="$NOTMUCH_SRCDIR/test/T[0-9][0-9][0-9]-*.sh"
+fi
+
+# Clean up any results from a previous run
+rm -rf $NOTMUCH_BUILDDIR/test/test-results
+
+# Test for timeout utility
+if command -v timeout >/dev/null; then
+    TEST_TIMEOUT_CMD="timeout 2m"
+    echo "INFO: using 2 minute timeout for tests"
+else
+    TEST_TIMEOUT_CMD=""
+fi
+
+trap 'e=$?; kill $!; exit $e' HUP INT TERM
+# Run the tests
+for test in $TESTS; do
+    $TEST_TIMEOUT_CMD $test "$@" &
+    wait $!
+    # If the test failed without producing results, then it aborted,
+    # so we should abort, too.
+    RES=$?
+    testname=$(basename $test .sh)
+    if [[ $RES != 0 && ! -e "$NOTMUCH_BUILDDIR/test/test-results/$testname" ]]; then
+        exit $RES
+    fi
+done
+trap - HUP INT TERM
+
+# Report results
+echo
+$NOTMUCH_SRCDIR/test/aggregate-results.sh $NOTMUCH_BUILDDIR/test/test-results/*
+ev=$?
+
+# Clean up
+rm -rf $NOTMUCH_BUILDDIR/test/test-results $NOTMUCH_BUILDDIR/test/corpora.mail
+
+exit $ev
diff --git a/test/notmuch-test.h b/test/notmuch-test.h
new file mode 100644 (file)
index 0000000..45d03d6
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef _NOTMUCH_TEST_H
+#define _NOTMUCH_TEST_H
+#include <stdio.h>
+#include <notmuch.h>
+
+inline static void
+expect0(int line, notmuch_status_t ret)
+{
+   if (ret) {
+       fprintf (stderr, "line %d: %d\n", line, ret);
+       exit (1);
+   }
+}
+
+#define EXPECT0(v)  expect0(__LINE__, v);
+#endif
diff --git a/test/parse-time.c b/test/parse-time.c
new file mode 100644 (file)
index 0000000..694761c
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * parse time string - user friendly date and time parser
+ * Copyright © 2012 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 2 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/>.
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parse-time-string.h"
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
+
+static const char *parse_time_error_strings[] = {
+    [PARSE_TIME_OK]                    = "OK",
+    [PARSE_TIME_ERR]                   = "ERR",
+    [PARSE_TIME_ERR_LIB]               = "LIB",
+    [PARSE_TIME_ERR_ALREADYSET]                = "ALREADYSET",
+    [PARSE_TIME_ERR_FORMAT]            = "FORMAT",
+    [PARSE_TIME_ERR_DATEFORMAT]                = "DATEFORMAT",
+    [PARSE_TIME_ERR_TIMEFORMAT]                = "TIMEFORMAT",
+    [PARSE_TIME_ERR_INVALIDDATE]       = "INVALIDDATE",
+    [PARSE_TIME_ERR_INVALIDTIME]       = "INVALIDTIME",
+    [PARSE_TIME_ERR_KEYWORD]           = "KEYWORD",
+};
+
+static const char *
+parse_time_strerror (unsigned int errnum)
+{
+    if (errnum < ARRAY_SIZE (parse_time_error_strings))
+       return parse_time_error_strings[errnum];
+    else
+       return NULL;
+}
+
+/*
+ * concat argv[start]...argv[end - 1], separating them by a single
+ * space, to a malloced string
+ */
+static char *
+concat_args (int start, int end, char *argv[])
+{
+    int i;
+    size_t len = 1;
+    char *p;
+
+    for (i = start; i < end; i++)
+       len += strlen (argv[i]) + 1;
+
+    p = malloc (len);
+    if (!p)
+       return NULL;
+
+    *p = 0;
+
+    for (i = start; i < end; i++) {
+       if (i != start)
+           strcat (p, " ");
+       strcat (p, argv[i]);
+    }
+
+    return p;
+}
+
+#define DEFAULT_FORMAT "%a %b %d %T %z %Y"
+
+static void
+usage (const char *name)
+{
+    printf ("Usage: %s [options ...] [<date/time>]\n\n", name);
+    printf (
+       "Parse <date/time> and display it in given format. If <date/time> is\n"
+       "not given, parse each line in stdin according to:\n\n"
+       "  <date/time> [(==>|==_>|==^>|==^^>)<ignored>] [#<comment>]\n\n"
+       "and produce output:\n\n"
+       "  <date/time> (==>|==_>|==^>|==^^>) <time in --format=FMT> [#<comment>]\n\n"
+       "preserving whitespace and comment in input. The operators ==>, ==_>,\n"
+       "==^>, and ==^^> define rounding as no rounding, round down, round up\n"
+       "inclusive, and round up, respectively.\n\n"
+
+       "  -f, --format=FMT output format, FMT according to strftime(3)\n"
+       "                   (default: \"%s\")\n"
+       "  -r, --ref=N      use N seconds since epoch as reference time\n"
+       "                   (default: now)\n"
+       "  -u, --^          round result up inclusive (default: no rounding)\n"
+       "  -U, --^^         round result up (default: no rounding)\n"
+       "  -d, --_          round result down (default: no rounding)\n"
+       "  -h, --help       print this help\n",
+       DEFAULT_FORMAT);
+}
+
+struct {
+    const char *operator;
+    int round;
+} operators[] = {
+    { "==>",   PARSE_TIME_NO_ROUND },
+    { "==_>",  PARSE_TIME_ROUND_DOWN },
+    { "==^>",  PARSE_TIME_ROUND_UP_INCLUSIVE },
+    { "==^^>", PARSE_TIME_ROUND_UP },
+};
+
+static const char *
+find_operator_in_string (char *str, char **ptr, int *round)
+{
+    const char *oper = NULL;
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (operators); i++) {
+       char *p = strstr (str, operators[i].operator);
+       if (p) {
+           if (round)
+               *round = operators[i].round;
+           if (ptr)
+               *ptr = p;
+
+           oper = operators[i].operator;
+           break;
+       }
+    }
+
+    return oper;
+}
+
+static const char *
+get_operator (int round)
+{
+    const char *oper = NULL;
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE(operators); i++) {
+       if (round == operators[i].round) {
+           oper = operators[i].operator;
+           break;
+       }
+    }
+
+    return oper;
+}
+
+static int
+parse_stdin (FILE *infile, time_t *ref, int round, const char *format)
+{
+    char *input = NULL;
+    char result[1024];
+    size_t inputsize;
+    ssize_t len;
+    struct tm tm;
+    time_t t;
+    int r;
+
+    while ((len = getline (&input, &inputsize, infile)) != -1) {
+       const char *oper;
+       char *trail, *tmp;
+
+       /* trail is trailing whitespace and (optional) comment */
+       trail = strchr (input, '#');
+       if (!trail)
+           trail = input + len;
+
+       while (trail > input && isspace ((unsigned char) *(trail-1)))
+           trail--;
+
+       if (trail == input) {
+           printf ("%s", input);
+           continue;
+       }
+
+       tmp = strdup (trail);
+       if (!tmp) {
+           fprintf (stderr, "strdup() failed\n");
+           continue;
+       }
+       *trail = '\0';
+       trail = tmp;
+
+       /* operator */
+       oper = find_operator_in_string (input, &tmp, &round);
+       if (oper) {
+           *tmp = '\0';
+       } else {
+           oper = get_operator (round);
+           assert (oper);
+       }
+
+       r = parse_time_string (input, &t, ref, round);
+       if (!r) {
+           if (!localtime_r (&t, &tm)) {
+               fprintf (stderr, "localtime_r() failed\n");
+               free (trail);
+               continue;
+           }
+
+           strftime (result, sizeof (result), format, &tm);
+       } else {
+           const char *errstr = parse_time_strerror (r);
+           if (errstr)
+               snprintf (result, sizeof (result), "ERROR: %s", errstr);
+           else
+               snprintf (result, sizeof (result), "ERROR: %d", r);
+       }
+
+       printf ("%s%s %s%s", input, oper, result, trail);
+       free (trail);
+    }
+
+    free (input);
+
+    return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+    int r;
+    struct tm tm;
+    time_t result;
+    time_t now;
+    time_t *nowp = NULL;
+    char *argstr;
+    int round = PARSE_TIME_NO_ROUND;
+    char buf[1024];
+    const char *format = DEFAULT_FORMAT;
+    struct option options[] = {
+       { "help",       no_argument,            NULL,   'h' },
+       { "^",          no_argument,            NULL,   'u' },
+       { "^^",         no_argument,            NULL,   'U' },
+       { "_",          no_argument,            NULL,   'd' },
+       { "format",     required_argument,      NULL,   'f' },
+       { "ref",        required_argument,      NULL,   'r' },
+       { NULL, 0, NULL, 0 },
+    };
+
+    for (;;) {
+       int c;
+
+       c = getopt_long (argc, argv, "huUdf:r:", options, NULL);
+       if (c == -1)
+           break;
+
+       switch (c) {
+       case 'f':
+           /* output format */
+           format = optarg;
+           break;
+       case 'u':
+           round = PARSE_TIME_ROUND_UP_INCLUSIVE;
+           break;
+       case 'U':
+           round = PARSE_TIME_ROUND_UP;
+           break;
+       case 'd':
+           round = PARSE_TIME_ROUND_DOWN;
+           break;
+       case 'r':
+           /* specify now in seconds since epoch */
+           now = (time_t) strtol (optarg, NULL, 10);
+           if (now >= (time_t) 0)
+               nowp = &now;
+           break;
+       case 'h':
+       case '?':
+       default:
+           usage (argv[0]);
+           return 1;
+       }
+    }
+
+    if (optind == argc)
+       return parse_stdin (stdin, nowp, round, format);
+
+    argstr = concat_args (optind, argc, argv);
+    if (!argstr)
+       return 1;
+
+    r = parse_time_string (argstr, &result, nowp, round);
+
+    free (argstr);
+
+    if (r) {
+       const char *errstr = parse_time_strerror (r);
+       if (errstr)
+           fprintf (stderr, "ERROR: %s\n", errstr);
+       else
+           fprintf (stderr, "ERROR: %d\n", r);
+
+       return r;
+    }
+
+    if (!localtime_r (&result, &tm))
+       return 1;
+
+    strftime (buf, sizeof (buf), format, &tm);
+    printf ("%s\n", buf);
+
+    return 0;
+}
diff --git a/test/random-corpus.c b/test/random-corpus.c
new file mode 100644 (file)
index 0000000..9272afd
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Generate a random corpus of stub messages.
+ *
+ * Initial use case is testing dump and restore, so we only have
+ * message-ids and tags.
+ *
+ * Generated message-id's and tags are intentionally nasty.
+ *
+ * Copyright (c) 2012 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <talloc.h>
+#include <string.h>
+#include <glib.h>
+#include <math.h>
+
+#include "notmuch-client.h"
+#include "command-line-arguments.h"
+#include "database-test.h"
+
+/* Current largest Unicode value defined. Note that most of these will
+ * be printed as boxes in most fonts.
+ */
+
+#define GLYPH_MAX 0x10FFFE
+
+
+typedef struct {
+    int weight;
+    int start;
+    int stop;
+} char_class_t;
+
+/*
+ *  Choose about half ascii as test characters, as ascii
+ *  punctation and whitespace is the main cause of problems for
+ *  the (old) restore parser.
+ *
+ *  We then favour code points with 2 byte encodings. Note that
+ *  code points 0xD800-0xDFFF are forbidden in UTF-8.
+ */
+
+static const
+char_class_t char_class[] = { { 0.50 * GLYPH_MAX, 0x0001, 0x007f },
+                             { 0.75 * GLYPH_MAX, 0x0080, 0x07ff },
+                             { 0.88 * GLYPH_MAX, 0x0800, 0xd7ff },
+                             { 0.90 * GLYPH_MAX, 0xE000, 0xffff },
+                             {        GLYPH_MAX, 0x10000, GLYPH_MAX } };
+
+static gunichar
+random_unichar ()
+{
+    int i;
+    int class = random () % GLYPH_MAX;
+    int size;
+
+    for (i = 0; char_class[i].weight < class; i++) /* nothing */;
+
+    size = char_class[i].stop - char_class[i].start + 1;
+
+    return char_class[i].start + (random () % size);
+}
+
+static char *
+random_utf8_string (void *ctx, size_t char_count)
+{
+    size_t offset = 0;
+    size_t i;
+    gchar *buf = NULL;
+    size_t buf_size = 0;
+
+    for (i = 0; i < char_count; i++) {
+       gunichar randomchar;
+       size_t written;
+
+       /* 6 for one glyph, one for null, one for luck */
+       while (buf_size <= offset + 8) {
+           buf_size = 2 * buf_size + 8;
+           buf = talloc_realloc (ctx, buf, gchar, buf_size);
+       }
+
+       do {
+           randomchar = random_unichar ();
+       } while (randomchar == '\n');
+
+       written = g_unichar_to_utf8 (randomchar, buf + offset);
+
+       if (written <= 0) {
+           fprintf (stderr, "error converting to utf8\n");
+           exit (1);
+       }
+
+       offset += written;
+
+    }
+    buf[offset] = 0;
+    return buf;
+}
+
+/* stubs since we cannot link with notmuch.o */
+const notmuch_opt_desc_t notmuch_shared_options[] = {
+       { }
+};
+
+const char *notmuch_requested_db_uuid = NULL;
+
+void
+notmuch_process_shared_options (unused (const char *dummy))
+{
+}
+
+int
+notmuch_minimal_options (unused (const char *subcommand),
+                        unused (int argc),
+                        unused (char **argv))
+{
+    return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+
+    void *ctx = talloc_new (NULL);
+
+    const char *config_path = NULL;
+    notmuch_config_t *config;
+    notmuch_database_t *notmuch;
+
+    int num_messages = 500;
+    int max_tags = 10;
+    // leave room for UTF-8 encoding.
+    int tag_len = NOTMUCH_TAG_MAX / 6;
+    // NOTMUCH_MESSAGE_ID_MAX is not exported, so we make a
+    // conservative guess.
+    int message_id_len = (NOTMUCH_TAG_MAX - 20) / 6;
+
+    int seed = 734569;
+
+    notmuch_opt_desc_t options[] = {
+       { .opt_string = &config_path, .name = "config-path" },
+       { .opt_int = &num_messages, .name = "num-messages" },
+       { .opt_int = &max_tags, .name = "max-tags" },
+       { .opt_int = &message_id_len, .name = "message-id-len" },
+       { .opt_int = &tag_len, .name = "tag-len" },
+       { .opt_int = &seed, .name = "seed" },
+       { }
+    };
+
+    int opt_index = parse_arguments (argc, argv, options, 1);
+
+    if (opt_index < 0)
+       exit (1);
+
+    if (message_id_len < 1) {
+       fprintf (stderr, "message id's must be least length 1\n");
+       exit (1);
+    }
+
+    if (config_path == NULL) {
+       fprintf (stderr, "configuration path must be specified");
+       exit (1);
+    }
+
+    config = notmuch_config_open (ctx, config_path, false);
+    if (config == NULL)
+       return 1;
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+       return 1;
+
+    srandom (seed);
+
+    int count;
+    for (count = 0; count < num_messages; count++) {
+       int j;
+       /* explicitly allow zero tags */
+       int num_tags = random () % (max_tags + 1);
+       /* message ids should be non-empty */
+       int this_mid_len = (random () % message_id_len) + 1;
+       const char **tag_list;
+       char *mid;
+       notmuch_status_t status;
+
+       do {
+           mid = random_utf8_string (ctx, this_mid_len);
+
+           tag_list = talloc_realloc (ctx, NULL, const char *, num_tags + 1);
+
+           for (j = 0; j < num_tags; j++) {
+               int this_tag_len = random () % tag_len + 1;
+
+               tag_list[j] = random_utf8_string (ctx, this_tag_len);
+           }
+
+           tag_list[j] = NULL;
+
+           status = notmuch_database_add_stub_message (notmuch, mid, tag_list);
+       } while (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID);
+
+       if (status != NOTMUCH_STATUS_SUCCESS) {
+           fprintf (stderr, "error %d adding message", status);
+           exit (status);
+       }
+    }
+
+    notmuch_database_destroy (notmuch);
+
+    talloc_free (ctx);
+
+    return 0;
+}
diff --git a/test/smime/README b/test/smime/README
new file mode 100644 (file)
index 0000000..92803c7
--- /dev/null
@@ -0,0 +1,7 @@
+test.crt: self signed certificated
+    % gpgsm --gen-key # needs gpgsm 2.1
+
+key+cert.pem: cert + unencryped private
+    % gpsm --import test.crt
+    % gpgsm --export-private-key-p12 -out foo.p12  (no passphrase)
+    % openssl pkcs12 -in ns.p12 -clcerts -nodes > key+cert.pem
diff --git a/test/smime/key+cert.pem b/test/smime/key+cert.pem
new file mode 100644 (file)
index 0000000..6ee30cf
--- /dev/null
@@ -0,0 +1,56 @@
+Bag Attributes
+    friendlyName: GnuPG exported certificate e0972a47
+    localKeyID: 61 6F 46 CD 73 83 4C 63 84 77 56 AF 0D FB 64 A6 E0 97 2A 47 
+subject=/CN=Notmuch Test Suite
+issuer=/CN=Notmuch Test Suite
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIIb3SMlL0MZ6kwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UE
+AxMSTm90bXVjaCBUZXN0IFN1aXRlMCAXDTE1MTIxNDAyMDgxMFoYDzIwNjMwNDA1
+MTcwMDAwWjAdMRswGQYDVQQDExJOb3RtdWNoIFRlc3QgU3VpdGUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7vH1/lkENTAJRbyq2036K7Pw+imSIhB5T
+U0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57Fi/4leBH7x217BnnqWNU
+QV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNjmRFIjB1afSSXWnCvRpAR
+v+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9FxopWJL5rW/o2WEfRPGpYe
+HNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+pCMWs9dHmOsiC73/+P6E
+AhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpaVhQnu6YLAgMBAAGjTTBL
+MCUGA1UdEQQeMByBGnRlc3Rfc3VpdGVAbm90bXVjaG1haWwub3JnMBEGCisGAQQB
+2kcCAgEEAwEB/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb
+XP5OnRVplrEdlnivx3CbCLWO13fcMWXfvKxLGsKFwKuxtpvINFUKM+jDr0kVdQ3d
+u3DJe2hNFQMILK/KrGyN5qEz2YBdHNvdkkvWA+3WHr/tiNr6Rly6QuxBzouxzmRu
+MmnUhsOzZaHT3GmLSVJlwie8KqSfKVGwyBmCyHbUQkMrSEV6QDESN6KyWt85gokB
+56Bc/wVq073xS1nFbfF1M3Z5q5BlLZK4IOerKTQx/oSfR4EX6B7rW2pttWsUCyEj
+LljaA8ehxR9B29m08IGGl43pHEpC1WnOHvsEGs99mPpjWbUgVv5KY7OuS/8iVw6v
+/Yy5Z+JBwlMzTBaUXXl3
+-----END CERTIFICATE-----
+Bag Attributes
+    friendlyName: GnuPG exported certificate e0972a47
+    localKeyID: 61 6F 46 CD 73 83 4C 63 84 77 56 AF 0D FB 64 A6 E0 97 2A 47 
+Key Attributes: <No Attributes>
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7vH1/lkENTAJR
+byq2036K7Pw+imSIhB5TU0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57
+Fi/4leBH7x217BnnqWNUQV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNj
+mRFIjB1afSSXWnCvRpARv+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9Fx
+opWJL5rW/o2WEfRPGpYeHNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+
+pCMWs9dHmOsiC73/+P6EAhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpa
+VhQnu6YLAgMBAAECggEAVhtHCHz3C01Ahu9RDRgGI1w8+cZqA/9tFVTNTqNrne9r
+GHLXKB4z8W/KYmhsjtAnnri31neXb1prfNMZX5AGlZfD7cwDubCEgYGWV6qldNXT
+YVeV54VkdBV+2k9Lp/Ifc5RZJILWk4+Ge8kaF0dEs1tQrCbsJkhcDfgQUdR5PnGe
+6cKv/8HJo0ep6u5cJloIluit8yF3z4+aHixMQBvQKm/8tug+EsrQZ3IVXbh1hONO
+AZ68z9CrU2pJ/0w/jwwcM5feRfTMC7bZ3vkQb1mQKYFJrvN77TGroUtAZFWqJw7M
+r0f2MShdVjfEdJ1ySnCyKF24cSSPSQsLZUe4UlFyQQKBgQDlqr9ajaUzc6Lyma2e
+Q1IJapbX2OZQtf5tlKVCVtZOlu5r97YMOK96XsQFKtdxhAhrGvvTJwPmwhj+fqfR
+XltNrmUBpHCMsm9nloADvBS83KTP5tw9TMT0VZpt+m5XmvutdyQbSKwy+KMy+GZz
+/XBQCfTEoiDS4grGFftvZuRB4QKBgQDRQvsVFMh2NOnVGqczHJNGjvbDueUJmPUN
+3VxZc/FpBGLRSoN7uxQ4dGNnwyvXHs+pLAAC6xZpFCos9c3R8EPvoMyUehoDSAKW
+CMD4C+K8z7n4ducE5a0NrGIgQvnXtteKr3ZwK8V7cscyTCyjXdrQmQ5XHeue8asR
+758g+dG9awKBgEWuZJho2XKe5xWMIu0dp8pLmLCsklRyo1tD+lACYMs/Z99CLO3Q
+VQ1fq0GWGf/K+3LjoPwTnk9pHIQ6kVgotLMA8oxpA+zsRni7ZOO9MN2MZETf2nqO
+zEMFpfEwRkI2N54Nw9qzVeuxHHLegtc2Udk27BisyCCzjGlFSiAmq6KBAoGAFGfE
+RXjcvT65HX8Gaya+wtugFB8BRx0JX7dI6OLk5ZKLmq0ykH2bQepgnWermmU4we77
+0Dvtfa3u0YjZ/24XXg2YbSpWiWps0Y2/C7AyAAzq12/1OGcX5qk4Tbd0f+QkIset
+qxzmt4XcAKw50J+Vf3DmbYQ1M/BftCZcTm0ShHcCgYEAxp8mjE8iIHxFrm7nHMS0
+2/iWxO8DYaAZ0OLfjaZELHchVvTwa+DynbkwvOc3l4cbNTVaf9O6nmHTkLyBLBNr
+2htPKm1vi9TzNdvGqobFO3ijfvdGvq1rjQl86ns0cf395REmEaVX3zcw2v+GyC5n
+qE6Aa5bvdZ9Yykg6aoFo1mY=
+-----END PRIVATE KEY-----
diff --git a/test/smime/test.crt b/test/smime/test.crt
new file mode 100644 (file)
index 0000000..e5d1e82
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfOgAwIBAgIIb3SMlL0MZ6kwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UE
+AxMSTm90bXVjaCBUZXN0IFN1aXRlMCAXDTE1MTIxNDAyMDgxMFoYDzIwNjMwNDA1
+MTcwMDAwWjAdMRswGQYDVQQDExJOb3RtdWNoIFRlc3QgU3VpdGUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7vH1/lkENTAJRbyq2036K7Pw+imSIhB5T
+U0WnAgVGWOemY1Eppi9Dk6rjDxuuUKOCQ5el2wmFZN57Fi/4leBH7x217BnnqWNU
+QV88DxEfV+sk8dSb4a5FOOyfhFJmZso/0lK8x0fBcCNjmRFIjB1afSSXWnCvRpAR
+v+O9trLJuIjbbmXg1gltjuB5yDw8/OLEI7G7YSIop9FxopWJL5rW/o2WEfRPGpYe
+HNRLObCRIvbyDd6XjaCrKBuIrhN7R7mmIa9PUyl8TiY+pCMWs9dHmOsiC73/+P6E
+AhsTOY1bfbGQXBAGZ/FL+SgC5wEcPr2u3+y8y5gw2bpaVhQnu6YLAgMBAAGjTTBL
+MCUGA1UdEQQeMByBGnRlc3Rfc3VpdGVAbm90bXVjaG1haWwub3JnMBEGCisGAQQB
+2kcCAgEEAwEB/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb
+XP5OnRVplrEdlnivx3CbCLWO13fcMWXfvKxLGsKFwKuxtpvINFUKM+jDr0kVdQ3d
+u3DJe2hNFQMILK/KrGyN5qEz2YBdHNvdkkvWA+3WHr/tiNr6Rly6QuxBzouxzmRu
+MmnUhsOzZaHT3GmLSVJlwie8KqSfKVGwyBmCyHbUQkMrSEV6QDESN6KyWt85gokB
+56Bc/wVq073xS1nFbfF1M3Z5q5BlLZK4IOerKTQx/oSfR4EX6B7rW2pttWsUCyEj
+LljaA8ehxR9B29m08IGGl43pHEpC1WnOHvsEGs99mPpjWbUgVv5KY7OuS/8iVw6v
+/Yy5Z+JBwlMzTBaUXXl3
+-----END CERTIFICATE-----
diff --git a/test/smtp-dummy.c b/test/smtp-dummy.c
new file mode 100644 (file)
index 0000000..71992ed
--- /dev/null
@@ -0,0 +1,274 @@
+/* smtp-dummy - Dummy SMTP server that delivers mail to the given file
+ *
+ * Copyright © 2010 Carl Worth
+ *
+ * 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: Carl Worth <cworth@cworth.org>
+ */
+
+/* This (non-compliant) SMTP server listens on localhost, port 25025
+ * and delivers a mail received to the given filename, (specified as a
+ * command-line argument). It exists after the first client connection
+ * completes.
+ *
+ * It implements very little of the SMTP protocol, even less than
+ * specified as the minimum implementation in the SMTP RFC, (not
+ * implementing RSET, NOOP, nor VRFY). And it doesn't do any
+ * error-checking on the input.
+ *
+ * That is to say, if you use this program, you will very likely find
+ * cases where it doesn't do everything your SMTP client expects. You
+ * have been warned.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#define STRNCMP_LITERAL(var, literal) \
+    strncmp ((var), (literal), sizeof (literal) - 1)
+
+static void
+receive_data_to_file (FILE *peer, FILE *output)
+{
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
+
+    while ((line_len = getline (&line, &line_size, peer)) != -1) {
+       if (STRNCMP_LITERAL (line, ".\r\n") == 0)
+           break;
+       if (line_len < 2)
+           continue;
+       if (line[line_len - 1] == '\n' && line[line_len - 2] == '\r') {
+           line[line_len - 2] = '\n';
+           line[line_len - 1] = '\0';
+       }
+       fprintf (output, "%s",
+                line[0] == '.' ? line + 1 : line);
+    }
+
+    free (line);
+}
+
+static int
+process_command (FILE *peer, FILE *output, const char *command)
+{
+    if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
+       fprintf (peer, "502 not implemented\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
+       fprintf (peer, "250 localhost\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
+              STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
+       fprintf (peer, "250 OK\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
+       fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
+       fflush (peer);
+       receive_data_to_file (peer, output);
+       fprintf (peer, "250 OK\r\n");
+       fflush (peer);
+    } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
+       fprintf (peer, "221 BYE\r\n");
+       fflush (peer);
+       return 1;
+    } else {
+       fprintf (stderr, "Unknown command: %s\n", command);
+    }
+    return 0;
+}
+
+static void
+do_smtp_to_file (FILE *peer, FILE *output)
+{
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
+
+    fprintf (peer, "220 localhost smtp-dummy\r\n");
+    fflush (peer);
+
+    while ((line_len = getline (&line, &line_size, peer)) != -1) {
+       if (process_command (peer, output, line))
+           break;
+    }
+
+    free (line);
+}
+
+int
+main (int argc, char *argv[])
+{
+    const char *progname;
+    char *output_filename;
+    FILE *peer_file = NULL, *output = NULL;
+    int sock = -1, peer, err;
+    struct sockaddr_in addr, peer_addr;
+    struct hostent *hostinfo;
+    socklen_t peer_addr_len;
+    int reuse;
+    int background;
+    int ret = 0;
+
+    progname = argv[0];
+
+    background = 0;
+    for (; argc >= 2; argc--, argv++) {
+       if (argv[1][0] != '-')
+           break;
+       if (strcmp (argv[1], "--") == 0) {
+           argc--;
+           argv++;
+           break;
+       }
+       if (strcmp (argv[1], "--background") == 0) {
+           background = 1;
+           continue;
+       }
+       fprintf (stderr, "%s: unregognized option '%s'\n",
+                progname, argv[1]);
+       return 1;
+    }
+
+    if (argc != 2) {
+       fprintf (stderr,
+                "Usage: %s [--background] <output-file>\n", progname);
+       return 1;
+    }
+
+    output_filename = argv[1];
+    output = fopen (output_filename, "w");
+    if (output == NULL) {
+       fprintf (stderr, "Failed to open %s for writing: %s\n",
+                output_filename, strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    sock = socket (AF_INET, SOCK_STREAM, 0);
+    if (sock == -1) {
+       fprintf (stderr, "Error: socket() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    reuse = 1;
+    err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+    if (err) {
+       fprintf (stderr, "Error: setsockopt() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    hostinfo = gethostbyname ("localhost");
+    if (hostinfo == NULL) {
+       fprintf (stderr, "Unknown host: localhost\n");
+       ret = 1;
+       goto DONE;
+    }
+
+    memset (&addr, 0, sizeof (addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons (25025);
+    addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
+    err = bind (sock, (struct sockaddr *) &addr, sizeof (addr));
+    if (err) {
+       fprintf (stderr, "Error: bind() failed: %s\n",
+                strerror (errno));
+       close (sock);
+       ret = 1;
+       goto DONE;
+    }
+
+    err = listen (sock, 1);
+    if (err) {
+       fprintf (stderr, "Error: listen() failed: %s\n",
+                strerror (errno));
+       close (sock);
+       ret = 1;
+       goto DONE;
+    }
+
+    if (background) {
+       int pid = fork ();
+       if (pid > 0) {
+           printf ("smtp_dummy_pid='%d'\n", pid);
+           fflush (stdout);
+           close (sock);
+           ret = 0;
+           goto DONE;
+       }
+       if (pid < 0) {
+           fprintf (stderr, "Error: fork() failed: %s\n",
+                    strerror (errno));
+           close (sock);
+           ret = 1;
+           goto DONE;
+       }
+       /* Reached if pid == 0 (the child process). */
+       /* Close stdout so that the one interested in pid value will
+        * also get EOF. */
+       close (STDOUT_FILENO);
+       /* dup2() will re-reserve fd of stdout (1) (opportunistically),
+        * in case fd of stderr (2) is open. If that was not open we
+        * don't care fd of stdout (1) either. */
+       dup2 (STDERR_FILENO, STDOUT_FILENO);
+
+       /* This process is now out of reach of shell's job control.
+        * To resolve the rare but possible condition where this
+        * "daemon" is started but never connected this process will
+        * (only) have 30 seconds to exist. */
+       alarm (30);
+    }
+
+    peer_addr_len = sizeof (peer_addr);
+    peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
+    if (peer == -1) {
+       fprintf (stderr, "Error: accept() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    peer_file = fdopen (peer, "w+");
+    if (peer_file == NULL) {
+       fprintf (stderr, "Error: fdopen() failed: %s\n",
+                strerror (errno));
+       ret = 1;
+       goto DONE;
+    }
+
+    do_smtp_to_file (peer_file, output);
+
+ DONE:
+    if (output)
+       fclose (output);
+    if (peer_file)
+       fclose (peer_file);
+    if (sock >= 0)
+       close (sock);
+
+    return ret;
+}
diff --git a/test/symbol-test.cc b/test/symbol-test.cc
new file mode 100644 (file)
index 0000000..7454838
--- /dev/null
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <xapian.h>
+#include <notmuch.h>
+
+int main (int argc, char** argv)
+{
+    notmuch_database_t *notmuch;
+    char *message = NULL;
+
+    if (argc != 3)
+       return 1;
+
+    if (notmuch_database_open_verbose (argv[1], NOTMUCH_DATABASE_MODE_READ_ONLY,
+                                      &notmuch, &message)) {
+       if (message) {
+           fputs (message, stderr);
+           free (message);
+       }
+    }
+
+    try {
+       (void) new Xapian::WritableDatabase (argv[2], Xapian::DB_OPEN);
+    } catch (const Xapian::Error &error) {
+       printf("caught %s\n", error.get_msg().c_str());
+       return 0;
+    }
+
+    return 1;
+}
diff --git a/test/test-databases/.gitignore b/test/test-databases/.gitignore
new file mode 100644 (file)
index 0000000..9452199
--- /dev/null
@@ -0,0 +1 @@
+/*.tar.xz
diff --git a/test/test-databases/Makefile b/test/test-databases/Makefile
new file mode 100644 (file)
index 0000000..b250a8b
--- /dev/null
@@ -0,0 +1,7 @@
+# See Makefile.local for the list of files to be compiled in this
+# directory.
+all:
+       $(MAKE) -C ../.. all
+
+.DEFAULT:
+       $(MAKE) -C ../.. $@
diff --git a/test/test-databases/Makefile.local b/test/test-databases/Makefile.local
new file mode 100644 (file)
index 0000000..7aedff7
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- makefile -*-
+
+TEST_DATABASE_MIRROR=https://notmuchmail.org/releases/test-databases
+
+dir := test/test-databases
+
+test_databases := $(dir)/database-v1.tar.xz
+
+%.tar.xz:
+       @exec 1>&2 ;\
+       if command -v wget >/dev/null ;\
+       then set -x; wget -nv -O $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\
+       elif command -v curl >/dev/null ;\
+       then set -x; curl -L -s -o $@ ${TEST_DATABASE_MIRROR}/$(notdir $@) ;\
+       else echo Cannot fetch databases, no wget nor curl available; exit 1 ;\
+       fi
+
+download-test-databases: ${test_databases}
+
+DATACLEAN := $(DATACLEAN) ${test_databases}
diff --git a/test/test-databases/database-v1.tar.xz.sha256 b/test/test-databases/database-v1.tar.xz.sha256
new file mode 100644 (file)
index 0000000..2cc4f96
--- /dev/null
@@ -0,0 +1 @@
+4299e051b10e1fa7b33ea2862790a09ebfe96859681804e5251e130f800e69d2  database-v1.tar.xz
diff --git a/test/test-lib-FREEBSD.sh b/test/test-lib-FREEBSD.sh
new file mode 100644 (file)
index 0000000..d1840b5
--- /dev/null
@@ -0,0 +1,9 @@
+# If present, use GNU Coreutils instead of a native BSD utils
+if command -v gdate >/dev/null
+   then
+       date () { gdate "$@"; }
+       base64 () { gbase64 "$@"; }
+       wc () { gwc "$@"; }
+       sed () { gsed "$@"; }
+       sha256sum () { gsha256sum "$@"; }
+   fi
diff --git a/test/test-lib-common.sh b/test/test-lib-common.sh
new file mode 100644 (file)
index 0000000..2f7950a
--- /dev/null
@@ -0,0 +1,334 @@
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
+#
+# 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 2 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/ .
+
+# This file contains common code to be used by both the regular
+# (correctness) tests and the performance tests.
+
+# test-lib.sh defines die() which echoes to nonstandard fd where
+# output was redirected earlier in that file. If test-lib.sh is not
+# loaded, neither this redirection nor die() function were defined.
+#
+type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
+
+if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
+       echo "internal: srcdir or builddir not set" >&2
+       exit 1
+fi
+
+backup_database () {
+    test_name=$(basename $0 .sh)
+    rm -rf $TMP_DIRECTORY/notmuch-dir-backup."$test_name"
+    cp -pR ${MAIL_DIR}/.notmuch $TMP_DIRECTORY/notmuch-dir-backup."${test_name}"
+}
+
+restore_database () {
+    test_name=$(basename $0 .sh)
+    rm -rf ${MAIL_DIR}/.notmuch
+    cp -pR $TMP_DIRECTORY/notmuch-dir-backup."${test_name}" ${MAIL_DIR}/.notmuch
+}
+
+# Prepend $TEST_DIRECTORY/../lib to LD_LIBRARY_PATH, to make tests work
+# on systems where ../notmuch depends on LD_LIBRARY_PATH.
+LD_LIBRARY_PATH=${TEST_DIRECTORY%/*}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
+export LD_LIBRARY_PATH
+
+# configure output
+. "$NOTMUCH_BUILDDIR/sh.config" || exit 1
+
+# load OS specifics
+if [[ -e "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" ]]; then
+    . "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" || exit 1
+fi
+
+# Generate a new message in the mail directory, with a unique message
+# ID and subject. The message is not added to the index.
+#
+# After this function returns, the filename of the generated message
+# is available as $gen_msg_filename and the message ID is available as
+# $gen_msg_id .
+#
+# This function supports named parameters with the bash syntax for
+# assigning a value to an associative array ([name]=value). The
+# supported parameters are:
+#
+#  [dir]=directory/of/choice
+#
+#      Generate the message in directory 'directory/of/choice' within
+#      the mail store. The directory will be created if necessary.
+#
+#  [filename]=name
+#
+#      Store the message in file 'name'. The default is to store it
+#      in 'msg-<count>', where <count> is three-digit number of the
+#      message.
+#
+#  [body]=text
+#
+#      Text to use as the body of the email message
+#
+#  '[from]="Some User <user@example.com>"'
+#  '[to]="Some User <user@example.com>"'
+#  '[subject]="Subject of email message"'
+#  '[date]="RFC 822 Date"'
+#
+#      Values for email headers. If not provided, default values will
+#      be generated instead.
+#
+#  '[cc]="Some User <user@example.com>"'
+#  [reply-to]=some-address
+#  [in-reply-to]=<message-id>
+#  [references]=<message-id>
+#  [content-type]=content-type-specification
+#  '[header]=full header line, including keyword'
+#
+#      Additional values for email headers. If these are not provided
+#      then the relevant headers will simply not appear in the
+#      message.
+#
+#  '[id]=message-id'
+#
+#      Controls the message-id of the created message.
+gen_msg_cnt=0
+gen_msg_filename=""
+gen_msg_id=""
+generate_message ()
+{
+    # This is our (bash-specific) magic for doing named parameters
+    local -A template="($@)"
+    local additional_headers
+
+    gen_msg_cnt=$((gen_msg_cnt + 1))
+    if [ -z "${template[filename]}" ]; then
+       gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)"
+    else
+       gen_msg_name=${template[filename]}
+    fi
+
+    if [ -z "${template[id]}" ]; then
+       gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite"
+    else
+       gen_msg_id="${template[id]}"
+    fi
+
+    if [ -z "${template[dir]}" ]; then
+       gen_msg_filename="${MAIL_DIR}/$gen_msg_name"
+    else
+       gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name"
+       mkdir -p "$(dirname "$gen_msg_filename")"
+    fi
+
+    if [ -z "${template[body]}" ]; then
+       template[body]="This is just a test message (#${gen_msg_cnt})"
+    fi
+
+    if [ -z "${template[from]}" ]; then
+       template[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+    fi
+
+    if [ -z "${template[to]}" ]; then
+       template[to]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+    fi
+
+    if [ -z "${template[subject]}" ]; then
+       if [ -n "$test_subtest_name" ]; then
+           template[subject]="$test_subtest_name"
+       else
+           template[subject]="Test message #${gen_msg_cnt}"
+       fi
+    elif [ "${template[subject]}" = "@FORCE_EMPTY" ]; then
+       template[subject]=""
+    fi
+
+    if [ -z "${template[date]}" ]; then
+       # we use decreasing timestamps here for historical reasons;
+       # the existing test suite when we converted to unique timestamps just
+       # happened to have signicantly fewer failures with that choice.
+       local date_secs=$((978709437 - gen_msg_cnt))
+       # printf %(..)T is bash 4.2+ feature. use perl fallback if needed...
+       TZ=UTC printf -v template[date] "%(%a, %d %b %Y %T %z)T" $date_secs 2>/dev/null ||
+           template[date]=`perl -le 'use POSIX "strftime";
+                               @time = gmtime '"$date_secs"';
+                               print strftime "%a, %d %b %Y %T +0000", @time'`
+    fi
+
+    additional_headers=""
+    if [ ! -z "${template[header]}" ]; then
+       additional_headers="${template[header]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[reply-to]}" ]; then
+       additional_headers="Reply-To: ${template[reply-to]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[in-reply-to]}" ]; then
+       additional_headers="In-Reply-To: ${template[in-reply-to]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[cc]}" ]; then
+       additional_headers="Cc: ${template[cc]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[bcc]}" ]; then
+       additional_headers="Bcc: ${template[bcc]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[references]}" ]; then
+       additional_headers="References: ${template[references]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[content-type]}" ]; then
+       additional_headers="Content-Type: ${template[content-type]}
+${additional_headers}"
+    fi
+
+    if [ ! -z "${template[content-transfer-encoding]}" ]; then
+       additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]}
+${additional_headers}"
+    fi
+
+    # Note that in the way we're setting it above and using it below,
+    # `additional_headers' will also serve as the header / body separator
+    # (empty line in between).
+
+    cat <<EOF >"$gen_msg_filename"
+From: ${template[from]}
+To: ${template[to]}
+Message-Id: <${gen_msg_id}>
+Subject: ${template[subject]}
+Date: ${template[date]}
+${additional_headers}
+${template[body]}
+EOF
+}
+
+# Generate a new message and add it to the database.
+#
+# All of the arguments and return values supported by generate_message
+# are also supported here, so see that function for details.
+add_message ()
+{
+    generate_message "$@" &&
+    notmuch new > /dev/null
+}
+
+if test -n "$valgrind"
+then
+       make_symlink () {
+               test -h "$2" &&
+               test "$1" = "$(readlink "$2")" || {
+                       # be super paranoid
+                       if mkdir "$2".lock
+                       then
+                               rm -f "$2" &&
+                               ln -s "$1" "$2" &&
+                               rm -r "$2".lock
+                       else
+                               while test -d "$2".lock
+                               do
+                                       say "Waiting for lock on $2."
+                                       sleep 1
+                               done
+                       fi
+               }
+       }
+
+       make_valgrind_symlink () {
+               # handle only executables
+               test -x "$1" || return
+
+               base=$(basename "$1")
+               symlink_target=$TEST_DIRECTORY/../$base
+               # do not override scripts
+               if test -x "$symlink_target" &&
+                   test ! -d "$symlink_target" &&
+                   test "#!" != "$(head -c 2 < "$symlink_target")"
+               then
+                       symlink_target=$TEST_DIRECTORY/valgrind.sh
+               fi
+               case "$base" in
+               *.sh|*.perl)
+                       symlink_target=$TEST_DIRECTORY/unprocessed-script
+               esac
+               # create the link, or replace it if it is out of date
+               make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
+       }
+
+       # override notmuch executable in TEST_DIRECTORY/..
+       GIT_VALGRIND=$TEST_DIRECTORY/valgrind
+       mkdir -p "$GIT_VALGRIND"/bin
+       make_valgrind_symlink $TEST_DIRECTORY/../notmuch
+       OLDIFS=$IFS
+       IFS=:
+       for path in $PATH
+       do
+               ls "$path"/notmuch 2> /dev/null |
+               while read file
+               do
+                       make_valgrind_symlink "$file"
+               done
+       done
+       IFS=$OLDIFS
+       PATH=$GIT_VALGRIND/bin:$PATH
+       GIT_EXEC_PATH=$GIT_VALGRIND/bin
+       export GIT_VALGRIND
+       test -n "$NOTMUCH_BUILDDIR" && MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
+else # normal case
+       if test -n "$NOTMUCH_BUILDDIR"
+               then
+                       PATH="$NOTMUCH_BUILDDIR:$PATH"
+                       MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
+               fi
+fi
+export PATH MANPATH
+
+# Test repository
+test="tmp.$(basename "$0" .sh)"
+TMP_DIRECTORY="$TEST_DIRECTORY/$test"
+test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY
+rm -rf "$TMP_DIRECTORY" || {
+       GIT_EXIT_OK=t
+       echo >&6 "FATAL: Cannot prepare test area"
+       exit 1
+}
+
+# A temporary home directory is needed by at least:
+# - emacs/"Sending a message via (fake) SMTP"
+# - emacs/"Reply within emacs"
+# - crypto/emacs_deliver_message
+export HOME="${TMP_DIRECTORY}/home"
+mkdir -p "${HOME}"
+
+MAIL_DIR="${TMP_DIRECTORY}/mail"
+export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config"
+
+mkdir -p "${MAIL_DIR}"
+
+cat <<EOF >"${NOTMUCH_CONFIG}"
+[database]
+path=${MAIL_DIR}
+
+[user]
+name=Notmuch Test Suite
+primary_email=test_suite@notmuchmail.org
+other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+EOF
diff --git a/test/test-lib.el b/test/test-lib.el
new file mode 100644 (file)
index 0000000..9946010
--- /dev/null
@@ -0,0 +1,205 @@
+;; test-lib.el --- auxiliary stuff for Notmuch Emacs tests.
+;;
+;; Copyright © Carl Worth
+;; Copyright © David Edmondson
+;;
+;; This file is part of Notmuch test suit.
+;;
+;; 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: Dmitry Kurochkin <dmitry.kurochkin@gmail.com>
+
+(require 'cl)  ;; This code is generally used uncompiled.
+
+;; `read-file-name' by default uses `completing-read' function to read
+;; user input.  It does not respect `standard-input' variable which we
+;; use in tests to provide user input.  So replace it with a plain
+;; `read' call.
+(setq read-file-name-function (lambda (&rest _) (read)))
+
+;; Work around a bug in emacs 23.1 and emacs 23.2 which prevents
+;; noninteractive (kill-emacs) from emacsclient.
+(if (and (= emacs-major-version 23) (< emacs-minor-version 3))
+  (defadvice kill-emacs (before disable-yes-or-no-p activate)
+    "Disable yes-or-no-p before executing kill-emacs"
+    (defun yes-or-no-p (prompt) t)))
+
+;; Emacs bug #2930:
+;;     23.0.92; `accept-process-output' and `sleep-for' do not run sentinels
+;; seems to be present in Emacs 23.1.
+;; Running `list-processes' after `accept-process-output' seems to work
+;; around this problem.
+(if (and (= emacs-major-version 23) (= emacs-minor-version 1))
+  (defadvice accept-process-output (after run-list-processes activate)
+    "run list-processes after executing accept-process-output"
+    (list-processes)))
+
+(defun notmuch-test-wait ()
+  "Wait for process completion."
+  (while (get-buffer-process (current-buffer))
+    (accept-process-output nil 0.1)))
+
+(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))
+       ;; Tests expect output in UTF-8 encoding
+       (coding-system-for-write 'utf-8))
+    (with-temp-file (or filename "OUTPUT") (insert text))))
+
+(defun visible-buffer-string ()
+  "Same as `buffer-string', but excludes invisible text and
+removes any text properties."
+  (visible-buffer-substring (point-min) (point-max)))
+
+(defun visible-buffer-substring (start end)
+  "Same as `buffer-substring-no-properties', but excludes
+invisible text."
+  (let (str)
+    (while (< start end)
+      (let ((next-pos (next-char-property-change start end)))
+       (when (not (invisible-p start))
+         (setq str (concat str (buffer-substring-no-properties
+                                start next-pos))))
+       (setq start next-pos)))
+    str))
+
+;; process-attributes is not defined everywhere, so define an
+;; alternate way to test if a process still exists.
+
+(defun test-process-running (pid)
+  (= 0
+   (signal-process pid 0)))
+
+(defun orphan-watchdog-check (pid)
+  "Periodically check that the process with id PID is still
+running, quit if it terminated."
+  (if (not (test-process-running pid))
+      (kill-emacs)))
+
+(defun orphan-watchdog (pid)
+  "Initiate orphan watchdog check."
+  (run-at-time 60 60 'orphan-watchdog-check pid))
+
+(defun hook-counter (hook)
+  "Count how many times a hook is called.  Increments
+`hook'-counter variable value if it is bound, otherwise does
+nothing."
+  (let ((counter (intern (concat (symbol-name hook) "-counter"))))
+    (if (boundp counter)
+       (set counter (1+ (symbol-value counter))))))
+
+(defun add-hook-counter (hook)
+  "Add hook to count how many times `hook' is called."
+  (add-hook hook (apply-partially 'hook-counter hook)))
+
+(add-hook-counter 'notmuch-hello-mode-hook)
+(add-hook-counter 'notmuch-hello-refresh-hook)
+
+(defadvice notmuch-search-process-filter (around pessimal activate disable)
+  "Feed notmuch-search-process-filter one character at a time."
+  (let ((string (ad-get-arg 1)))
+    (loop for char across string
+         do (progn
+              (ad-set-arg 1 (char-to-string char))
+              ad-do-it))))
+
+(defun notmuch-test-mark-links ()
+  "Enclose links in the current buffer with << and >>."
+  ;; Links are often created by jit-lock functions
+  (jit-lock-fontify-now)
+  (save-excursion
+    (let ((inhibit-read-only t))
+      (goto-char (point-min))
+      (let ((button))
+       (while (setq button (next-button (point)))
+         (goto-char (button-start button))
+         (insert "<<")
+         (goto-char (button-end button))
+         (insert ">>"))))))
+
+(defmacro notmuch-test-run (&rest body)
+  "Evaluate a BODY of test expressions and output the result."
+  `(with-temp-buffer
+     (let ((buffer (current-buffer))
+          (result (progn ,@body)))
+       (switch-to-buffer buffer)
+       (insert (if (stringp result)
+                  result
+                (prin1-to-string result)))
+       (test-output))))
+
+(defun notmuch-test-report-unexpected (output expected)
+  "Report that the OUTPUT does not match the EXPECTED result."
+  (concat "Expect:\t" (prin1-to-string expected) "\n"
+         "Output:\t" (prin1-to-string output) "\n"))
+
+(defun notmuch-test-expect-equal (output expected)
+  "Compare OUTPUT with EXPECTED. Report any discrepencies."
+  (if (equal output expected)
+      t
+    (cond
+     ((and (listp output)
+          (listp expected))
+      ;; Reporting the difference between two lists is done by
+      ;; reporting differing elements of OUTPUT and EXPECTED
+      ;; pairwise. This is expected to make analysis of failures
+      ;; simpler.
+      (apply #'concat (loop for o in output
+                           for e in expected
+                           if (not (equal o e))
+                           collect (notmuch-test-report-unexpected o e))))
+
+     (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
+      '((".*" nil)))
+
+;; Also for historical reasons, we set the fcc handler to file not
+;; insert.
+
+(setq notmuch-maildir-use-notmuch-insert nil)
+
+;; force a common html renderer, to avoid test variations between
+;; environments
+
+(setq mm-text-html-renderer 'html2text)
+
+;; Set some variables for S/MIME tests.
+
+(setq smime-keys '(("" "test_suite.pem" nil)))
+
+(setq mml-smime-use 'openssl)
+
+;; all test keys are without passphrase
+(eval-after-load 'smime
+  '(defun smime-ask-passphrase (cache)  nil))
diff --git a/test/test-lib.sh b/test/test-lib.sh
new file mode 100644 (file)
index 0000000..fca5277
--- /dev/null
@@ -0,0 +1,1202 @@
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2010 Notmuch Developers
+#
+# 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 2 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/ .
+
+if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
+    echo "Error: The notmuch test suite requires a bash version >= 4.0"
+    echo "due to use of associative arrays within the test suite."
+    echo "Please try again with a newer bash (or help us fix the"
+    echo "test suite to be more portable). Thanks."
+    exit 1
+fi
+
+# Make sure echo builtin does not expand backslash-escape sequences by default.
+shopt -u xpg_echo
+
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
+
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
+       echo >&2 'You do not seem to have built notmuch yet.'
+       exit 1
+fi
+
+this_test=${0##*/}
+this_test=${this_test%.sh}
+this_test_bare=${this_test#T[0-9][0-9][0-9]-}
+
+# if --tee was passed, write the output not only to the terminal, but
+# additionally to the file test-results/$BASENAME.out, too.
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+       # do not redirect again
+       ;;
+*' --tee '*|*' --va'*)
+       mkdir -p test-results
+       BASE=test-results/$this_test
+       (GIT_TEST_TEE_STARTED=done "$BASH" "$0" "$@" 2>&1;
+        echo $? > $BASE.exit) | tee $BASE.out
+       test "$(cat $BASE.exit)" = 0
+       exit
+       ;;
+esac
+
+# Save STDOUT to fd 6 and STDERR to fd 7.
+exec 6>&1 7>&2
+# Make xtrace debugging (when used) use redirected STDERR, with verbose lead:
+BASH_XTRACEFD=7
+export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
+
+# Keep the original TERM for say_color and test_emacs
+ORIGINAL_TERM=$TERM
+
+# Set SMART_TERM to vt100 for known dumb/unknown terminal.
+# Otherwise use whatever TERM is currently used so that
+# users' actual TERM environments are being used in tests.
+case ${TERM-} in
+       '' | dumb | unknown )
+               SMART_TERM=vt100 ;;
+       *)
+               SMART_TERM=$TERM ;;
+esac
+
+# For repeatability, reset the environment to known value.
+LANG=C
+LC_ALL=C
+PAGER=cat
+TZ=UTC
+TERM=dumb
+export LANG LC_ALL PAGER TERM TZ
+GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u}
+if [[ ( -n "$TEST_EMACS" && -z "$TEST_EMACSCLIENT" ) || \
+      ( -z "$TEST_EMACS" && -n "$TEST_EMACSCLIENT" ) ]]; then
+    echo "error: must specify both or neither of TEST_EMACS and TEST_EMACSCLIENT" >&2
+    exit 1
+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"}
+
+# Protect ourselves from common misconfiguration to export
+# CDPATH into the environment
+unset CDPATH
+
+unset GREP_OPTIONS
+
+# For emacsclient
+unset ALTERNATE_EDITOR
+
+add_gnupg_home ()
+{
+    local output
+    [ -d ${GNUPGHOME} ] && return
+    _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
+    at_exit_function _gnupg_exit
+    mkdir -m 0700 "$GNUPGHOME"
+    gpg --no-tty --import <$NOTMUCH_SRCDIR/test/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1
+    test_debug "cat $GNUPGHOME/import.log"
+    if (gpg --quick-random --version >/dev/null 2>&1) ; then
+       echo quick-random >> "$GNUPGHOME"/gpg.conf
+    elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
+       echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
+    fi
+    echo no-emit-version >> "$GNUPGHOME"/gpg.conf
+}
+
+# Each test should start with something like this, after copyright notices:
+#
+# test_description='Description of this test...
+# This test checks if command xyzzy does the right thing...
+# '
+# . ./test-lib.sh || exit 1
+
+[ "x$ORIGINAL_TERM" != "xdumb" ] && (
+               TERM=$ORIGINAL_TERM &&
+               export TERM &&
+               [ -t 1 ] &&
+               tput bold >/dev/null 2>&1 &&
+               tput setaf 1 >/dev/null 2>&1 &&
+               tput sgr0 >/dev/null 2>&1
+       ) &&
+       color=t
+
+while test "$#" -ne 0
+do
+       case "$1" in
+       -d|--debug)
+               debug=t; shift ;;
+       -i|--immediate)
+               immediate=t; shift ;;
+       -h|--help)
+               help=t; shift ;;
+       -v|--verbose)
+               verbose=t; shift ;;
+       -q|--quiet)
+               quiet=t; shift ;;
+       --with-dashes)
+               with_dashes=t; shift ;;
+       --no-color)
+               color=; shift ;;
+       --no-python)
+               # noop now...
+               shift ;;
+       --valgrind)
+               valgrind=t; verbose=t; shift ;;
+       --tee)
+               shift ;; # was handled already
+       *)
+               echo "error: unknown test option '$1'" >&2; exit 1 ;;
+       esac
+done
+
+if test -n "$debug"; then
+    print_subtest () {
+       printf " %-4s" "[$((test_count - 1))]"
+    }
+else
+    print_subtest () {
+       true
+    }
+fi
+
+if test -n "$color"; then
+       say_color () {
+               (
+               TERM=$ORIGINAL_TERM
+               export TERM
+               case "$1" in
+                       error) tput bold; tput setaf 1;; # bold red
+                       skip)  tput bold; tput setaf 2;; # bold green
+                       pass)  tput setaf 2;;            # green
+                       info)  tput setaf 3;;            # brown
+                       *) test -n "$quiet" && return;;
+               esac
+               shift
+               printf " "
+               printf "$@"
+               tput sgr0
+               print_subtest
+               )
+       }
+else
+       say_color() {
+               test -z "$1" && test -n "$quiet" && return
+               shift
+               printf " "
+               printf "$@"
+               print_subtest
+       }
+fi
+
+error () {
+       say_color error "error: $*\n"
+       GIT_EXIT_OK=t
+       exit 1
+}
+
+say () {
+       say_color info "$*"
+}
+
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+if test "$help" = "t"
+then
+       echo "Tests ${test_description}"
+       exit 0
+fi
+
+test_description_printed=
+print_test_description ()
+{
+       test -z "$test_description_printed" || return 0
+       echo
+       echo $this_test: "Testing ${test_description}"
+       test_description_printed=1
+}
+if [ -z "$NOTMUCH_TEST_QUIET" ]
+then
+       print_test_description
+fi
+
+test_failure=0
+test_count=0
+test_fixed=0
+test_broken=0
+test_success=0
+
+declare -a _exit_functions=()
+
+at_exit_function () {
+       _exit_functions=($1 ${_exit_functions[@]/$1})
+}
+
+rm_exit_function () {
+       _exit_functions=(${_exit_functions[@]/$1})
+}
+
+_exit_common () {
+       code=$?
+       trap - EXIT
+       set +ex
+       for _fn in ${_exit_functions[@]}; do $_fn; done
+       rm -rf "$TEST_TMPDIR"
+}
+
+trap_exit () {
+       _exit_common
+       if test -n "$GIT_EXIT_OK"
+       then
+               exit $code
+       else
+               exec >&6
+               say_color error '%-6s' FATAL
+               echo " $test_subtest_name"
+               echo
+               echo "Unexpected exit while executing $0. Exit code $code."
+               exit 1
+       fi
+}
+
+trap_signal () {
+       _exit_common
+       echo >&6 "FATAL: $0: interrupted by signal" $((code - 128))
+       exit $code
+}
+
+die () {
+       _exit_common
+       exec >&6
+       say_color error '%-6s' FATAL
+       echo " $*"
+       echo
+       echo "Unexpected exit while executing $0."
+       exit 1
+}
+
+GIT_EXIT_OK=
+# Note: TEST_TMPDIR *NOT* exported!
+TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX")
+# Put GNUPGHOME in TMPDIR to avoid problems with long paths.
+export GNUPGHOME="${TEST_TMPDIR}/gnupg"
+trap 'trap_exit' EXIT
+trap 'trap_signal' HUP INT TERM
+
+# Deliver a message with emacs and add it to the database
+#
+# Uses emacs to generate and deliver a message to the mail store.
+# Accepts arbitrary extra emacs/elisp functions to modify the message
+# before sending, which is useful to doing things like attaching files
+# to the message and encrypting/signing.
+emacs_deliver_message ()
+{
+    local subject="$1"
+    local body="$2"
+    shift 2
+    # before we can send a message, we have to prepare the FCC maildir
+    mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp}
+    # eval'ing smtp-dummy --background will set smtp_dummy_pid
+    smtp_dummy_pid=
+    eval `$TEST_DIRECTORY/smtp-dummy --background sent_message`
+    test -n "$smtp_dummy_pid" || return 1
+
+    test_emacs \
+       "(let ((message-send-mail-function 'message-smtpmail-send-it)
+               (mail-host-address \"example.com\")
+              (smtpmail-smtp-server \"localhost\")
+              (smtpmail-smtp-service \"25025\"))
+          (notmuch-mua-mail)
+          (message-goto-to)
+          (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\")
+          (message-goto-subject)
+          (insert \"${subject}\")
+          (message-goto-body)
+          (insert \"${body}\")
+          $@
+          (notmuch-mua-send-and-exit))"
+
+    # In case message was sent properly, client waits for confirmation
+    # before exiting and resuming control here; therefore making sure
+    # that server exits by sending (KILL) signal to it is safe.
+    kill -9 $smtp_dummy_pid
+    notmuch new >/dev/null
+}
+
+# Pretend to deliver a message with emacs. Really save it to a file
+# and add it to the database
+#
+# Uses emacs to generate and deliver a message to the mail store.
+# Accepts arbitrary extra emacs/elisp functions to modify the message
+# before sending, which is useful to doing things like attaching files
+# to the message and encrypting/signing.
+#
+# If any GNU-style long-arguments (like --quiet or --decrypt=true) are
+# at the head of the argument list, they are sent directly to "notmuch
+# new" after message delivery
+emacs_fcc_message ()
+{
+    local nmn_args=''
+    while [[ "$1" =~ ^-- ]]; do
+        nmn_args="$nmn_args $1"
+        shift
+    done
+    local subject="$1"
+    local body="$2"
+    shift 2
+    # before we can send a message, we have to prepare the FCC maildir
+    mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp}
+
+    test_emacs \
+       "(let ((message-send-mail-function (lambda () t))
+               (mail-host-address \"example.com\"))
+          (notmuch-mua-mail)
+          (message-goto-to)
+          (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\")
+          (message-goto-subject)
+          (insert \"${subject}\")
+          (message-goto-body)
+          (insert \"${body}\")
+          $@
+          (notmuch-mua-send-and-exit))" || return 1
+    notmuch new $nmn_args >/dev/null
+}
+
+# Add an existing, fixed corpus of email to the database.
+#
+# $1 is the corpus dir under corpora to add, using "default" if unset.
+#
+# The default corpus is based on about 50 messages from early in the
+# history of the notmuch mailing list, which allows for reliably
+# testing commands that need to operate on a not-totally-trivial
+# number of messages.
+add_email_corpus ()
+{
+    corpus=${1:-default}
+
+    rm -rf ${MAIL_DIR}
+    if [ -d $TEST_DIRECTORY/corpora.mail/$corpus ]; then
+       cp -a $TEST_DIRECTORY/corpora.mail/$corpus ${MAIL_DIR}
+    else
+       cp -a $NOTMUCH_SRCDIR/test/corpora/$corpus ${MAIL_DIR}
+       notmuch new >/dev/null || die "'notmuch new' failed while adding email corpus"
+       mkdir -p $TEST_DIRECTORY/corpora.mail
+       cp -a ${MAIL_DIR} $TEST_DIRECTORY/corpora.mail/$corpus
+    fi
+}
+
+test_begin_subtest ()
+{
+    if [ -n "$inside_subtest" ]; then
+       exec 1>&6 2>&7          # Restore stdout and stderr
+       error "bug in test script: Missing test_expect_equal in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
+    fi
+    test_subtest_name="$1"
+    test_reset_state_
+    # Redirect test output to the previously prepared file descriptors
+    # 3 and 4 (see below)
+    if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi
+    exec >&3 2>&4
+    inside_subtest=t
+}
+
+# Pass test if two arguments match
+#
+# Note: Unlike all other test_expect_* functions, this function does
+# not accept a test name. Instead, the caller should call
+# test_begin_subtest before calling this function in order to set the
+# name.
+test_expect_equal ()
+{
+       exec 1>&6 2>&7          # Restore stdout and stderr
+       if [ -z "$inside_subtest" ]; then
+               error "bug in the test script: test_expect_equal without test_begin_subtest"
+       fi
+       inside_subtest=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 parameters to test_expect_equal"
+
+       output="$1"
+       expected="$2"
+       if ! test_skip "$test_subtest_name"
+       then
+               if [ "$output" = "$expected" ]; then
+                       test_ok_
+               else
+                       testname=$this_test.$test_count
+                       echo "$expected" > $testname.expected
+                       echo "$output" > $testname.output
+                       test_failure_ "$(diff -u $testname.expected $testname.output)"
+               fi
+    fi
+}
+
+# Like test_expect_equal, but takes two filenames.
+test_expect_equal_file ()
+{
+       exec 1>&6 2>&7          # Restore stdout and stderr
+       if [ -z "$inside_subtest" ]; then
+               error "bug in the test script: test_expect_equal_file without test_begin_subtest"
+       fi
+       inside_subtest=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 parameters to test_expect_equal_file"
+
+       file1="$1"
+       file2="$2"
+       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")"
+               fi
+    fi
+}
+
+# Like test_expect_equal, but arguments are JSON expressions to be
+# canonicalized before diff'ing.  If an argument cannot be parsed, it
+# is used unchanged so that there's something to diff against.
+test_expect_equal_json () {
+    # The test suite forces LC_ALL=C, but this causes Python 3 to
+    # decode stdin as ASCII.  We need to read JSON in UTF-8, so
+    # override Python's stdio encoding defaults.
+    local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)'
+    output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
+        || echo "$1")
+    expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
+        || echo "$2")
+    shift 2
+    test_expect_equal "$output" "$expected" "$@"
+}
+
+# Sort the top-level list of JSON data from stdin.
+test_sort_json () {
+    PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c \
+        "import sys, json; json.dump(sorted(json.load(sys.stdin)),sys.stdout)"
+}
+
+test_emacs_expect_t () {
+       test "$#" = 1 ||
+       error "bug in the test script: not 1 parameter to test_emacs_expect_t"
+       if [ -z "$inside_subtest" ]; then
+               error "bug in the test script: test_emacs_expect_t without test_begin_subtest"
+       fi
+
+       # Run the test.
+       if ! test_skip "$test_subtest_name"
+       then
+               test_emacs "(notmuch-test-run $1)" >/dev/null
+
+               # Restore state after the test.
+               exec 1>&6 2>&7          # Restore stdout and stderr
+               inside_subtest=
+
+               # Report success/failure.
+               result=$(cat OUTPUT)
+               if [ "$result" = t ]
+               then
+                       test_ok_
+               else
+                       test_failure_ "${result}"
+               fi
+       else
+               # Restore state after the (non) test.
+               exec 1>&6 2>&7          # Restore stdout and stderr
+               inside_subtest=
+       fi
+}
+
+NOTMUCH_NEW ()
+{
+    notmuch new "${@}" | grep -v -E -e '^Processed [0-9]*( total)? file|Found [0-9]* total file'
+}
+
+NOTMUCH_DUMP_TAGS ()
+{
+    # this relies on the default format being batch-tag, otherwise some tests will break
+    notmuch dump --include=tags "${@}" | sed '/^#/d' | sort
+}
+
+notmuch_drop_mail_headers ()
+{
+    $NOTMUCH_PYTHON -c '
+import email, sys
+msg = email.message_from_file(sys.stdin)
+for hdr in sys.argv[1:]: del msg[hdr]
+print(msg.as_string(False))
+' "$@"
+}
+
+notmuch_search_sanitize ()
+{
+    perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
+}
+
+notmuch_search_files_sanitize ()
+{
+    notmuch_dir_sanitize
+}
+
+notmuch_dir_sanitize ()
+{
+    sed -e "s,$MAIL_DIR,MAIL_DIR," -e "s,${PWD},CWD,g" "$@"
+}
+
+NOTMUCH_SHOW_FILENAME_SQUELCH='s,filename:.*/mail,filename:/XXX/mail,'
+notmuch_show_sanitize ()
+{
+    sed -e "$NOTMUCH_SHOW_FILENAME_SQUELCH"
+}
+notmuch_show_sanitize_all ()
+{
+    sed \
+       -e 's| filename:.*| filename:XXXXX|' \
+       -e 's| id:[^ ]* | id:XXXXX |' | \
+       notmuch_date_sanitize
+}
+
+notmuch_json_show_sanitize ()
+{
+    sed \
+       -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|"timestamp": 97.......|"timestamp": 42|g' \
+        -e 's|"content-length": [1-9][0-9]*|"content-length": "NONZERO"|g'
+}
+
+notmuch_emacs_error_sanitize ()
+{
+    local command=$1
+    shift
+    for file in "$@"; do
+       echo "=== $file ==="
+       cat "$file"
+    done | sed  \
+       -e 's/^\[.*\]$/[XXX]/' \
+       -e "s|^\(command: \)\{0,1\}/.*/$command|\1YYY/$command|"
+}
+
+notmuch_date_sanitize ()
+{
+    sed \
+       -e 's/^Date: Fri, 05 Jan 2001 .*0000/Date: GENERATED_DATE/'
+}
+
+notmuch_uuid_sanitize ()
+{
+    sed 's/[0-9a-f]\{8\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{4\}-[0-9a-f]\{12\}/UUID/g'
+}
+
+notmuch_built_with_sanitize ()
+{
+    sed 's/^built_with[.]\(.*\)=.*$/built_with.\1=something/'
+}
+
+notmuch_config_sanitize ()
+{
+    notmuch_dir_sanitize | notmuch_built_with_sanitize
+}
+
+notmuch_show_part ()
+{
+    awk '/^\014part}/{ f=0 }; { if (f) { print $0 } } /^\014part{ ID: '"$1"'/{ f=1 }'
+}
+
+# End of notmuch helper functions
+
+# Use test_set_prereq to tell that a particular prerequisite is available.
+#
+# The prerequisite can later be checked for by using test_have_prereq.
+#
+# The single parameter is the prerequisite tag (a simple word, in all
+# capital letters by convention).
+
+test_set_prereq () {
+       satisfied="$satisfied$1 "
+}
+satisfied=" "
+
+test_have_prereq () {
+       case $satisfied in
+       *" $1 "*)
+               : yes, have it ;;
+       *)
+               ! : nope ;;
+       esac
+}
+
+declare -A test_missing_external_prereq_
+declare -A test_subtest_missing_external_prereq_
+
+# declare prerequisite for the given external binary
+test_declare_external_prereq () {
+       binary="$1"
+       test "$#" = 2 && name=$2 || name="$binary(1)"
+
+       if ! hash $binary 2>/dev/null; then
+               test_missing_external_prereq_["${binary}"]=t
+               eval "
+$binary () {
+       test_subtest_missing_external_prereq_[\"${name}\"]=t
+       false
+}"
+       fi
+}
+
+# Explicitly require external prerequisite.  Useful when binary is
+# called indirectly (e.g. from emacs).
+# Returns success if dependency is available, failure otherwise.
+test_require_external_prereq () {
+       binary="$1"
+       if [[ ${test_missing_external_prereq_["${binary}"]} == t ]]; then
+               # dependency is missing, call the replacement function to note it
+               eval "$binary"
+       else
+               true
+       fi
+}
+
+# You are not expected to call test_ok_ and test_failure_ directly, use
+# the text_expect_* functions instead.
+
+test_ok_ () {
+       if test "$test_subtest_known_broken_" = "t"; then
+               test_known_broken_ok_
+               return
+       fi
+       test_success=$(($test_success + 1))
+       if test -n "$NOTMUCH_TEST_QUIET"; then
+               return 0
+       fi
+       say_color pass "%-6s" "PASS"
+       echo " $test_subtest_name"
+}
+
+test_failure_ () {
+       print_test_description
+       if test "$test_subtest_known_broken_" = "t"; then
+               test_known_broken_failure_ "$@"
+               return
+       fi
+       test_failure=$(($test_failure + 1))
+       test_failure_message_ "FAIL" "$test_subtest_name" "$@"
+       test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
+       return 1
+}
+
+test_failure_message_ () {
+       say_color error "%-6s" "$1"
+       echo " $2"
+       shift 2
+       if [ "$#" != "0" ]; then
+               echo "$@" | sed -e 's/^/        /'
+       fi
+       if test "$verbose" != "t"; then cat test.output; fi
+}
+
+test_known_broken_ok_ () {
+       test_reset_state_
+       test_fixed=$(($test_fixed+1))
+       say_color pass "%-6s" "FIXED"
+       echo " $test_subtest_name"
+}
+
+test_known_broken_failure_ () {
+       test_reset_state_
+       test_broken=$(($test_broken+1))
+       if [ -z "$NOTMUCH_TEST_QUIET" ]; then
+               test_failure_message_ "BROKEN" "$test_subtest_name" "$@"
+       else
+               test_failure_message_ "BROKEN" "$test_subtest_name"
+       fi
+       return 1
+}
+
+test_debug () {
+       test "$debug" = "" || eval "$1"
+}
+
+test_run_ () {
+       test_cleanup=:
+       if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi
+       eval >&3 2>&4 "$1"
+       eval_ret=$?
+       eval >&3 2>&4 "$test_cleanup"
+       return 0
+}
+
+test_skip () {
+       test_count=$(($test_count+1))
+       to_skip=
+       for skp in $NOTMUCH_SKIP_TESTS
+       do
+               case $this_test.$test_count in
+               $skp)
+                       to_skip=t
+                       break
+               esac
+               case $this_test_bare.$test_count in
+               $skp)
+                       to_skip=t
+                       break
+               esac
+       done
+       case "$to_skip" in
+       t)
+               test_report_skip_ "$@"
+               ;;
+       *)
+               test_check_missing_external_prereqs_ "$@"
+               ;;
+       esac
+}
+
+test_check_missing_external_prereqs_ () {
+       if [[ ${#test_subtest_missing_external_prereq_[@]} != 0 ]]; then
+               say_color skip >&1 "missing prerequisites: "
+               echo ${!test_subtest_missing_external_prereq_[@]} >&1
+               test_report_skip_ "$@"
+       else
+               false
+       fi
+}
+
+test_report_skip_ () {
+       test_reset_state_
+       say_color skip >&3 "skipping test:"
+       echo " $@" >&3
+       say_color skip "%-6s" "SKIP"
+       echo " $1"
+}
+
+test_subtest_known_broken () {
+       test_subtest_known_broken_=t
+}
+
+test_expect_success () {
+       exec 1>&6 2>&7          # Restore stdout and stderr
+       if [ -z "$inside_subtest" ]; then
+               error "bug in the test script: test_expect_success without test_begin_subtest"
+       fi
+       inside_subtest=
+       test "$#" = 1 ||
+       error "bug in the test script: not 1 parameters to test_expect_success"
+
+       if ! test_skip "$test_subtest_name"
+       then
+               test_run_ "$1"
+               run_ret="$?"
+               # test_run_ may update missing external prerequisites
+               test_check_missing_external_prereqs_ "$@" ||
+               if [ "$run_ret" = 0 -a "$eval_ret" = 0 ]
+               then
+                       test_ok_
+               else
+                       test_failure_ "$1"
+               fi
+       fi
+}
+
+test_expect_code () {
+       exec 1>&6 2>&7          # Restore stdout and stderr
+       if [ -z "$inside_subtest" ]; then
+               error "bug in the test script: test_expect_code without test_begin_subtest"
+       fi
+       inside_subtest=
+       test "$#" = 2 ||
+       error "bug in the test script: not 2 parameters to test_expect_code"
+
+       if ! test_skip "$test_subtest_name"
+       then
+               test_run_ "$2"
+               run_ret="$?"
+               # test_run_ may update missing external prerequisites,
+               test_check_missing_external_prereqs_ "$@" ||
+               if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ]
+               then
+                       test_ok_
+               else
+                       test_failure_ "exit code $eval_ret, expected $1" "$2"
+               fi
+       fi
+}
+
+# This is not among top-level (test_expect_success)
+# but is a prefix that can be used in the test script, like:
+#
+#      test_expect_success 'complain and die' '
+#           do something &&
+#           do something else &&
+#          test_must_fail git checkout ../outerspace
+#      '
+#
+# Writing this as "! git checkout ../outerspace" is wrong, because
+# the failure could be due to a segv.  We want a controlled failure.
+
+test_must_fail () {
+       "$@"
+       test $? -gt 0 -a $? -le 129 -o $? -gt 192
+}
+
+# test_cmp is a helper function to compare actual and expected output.
+# You can use it like:
+#
+#      test_expect_success 'foo works' '
+#              echo expected >expected &&
+#              foo >actual &&
+#              test_cmp expected actual
+#      '
+#
+# This could be written as either "cmp" or "diff -u", but:
+# - cmp's output is not nearly as easy to read as diff -u
+# - not all diff versions understand "-u"
+
+test_cmp() {
+       $GIT_TEST_CMP "$@"
+}
+
+# This function can be used to schedule some commands to be run
+# unconditionally at the end of the test to restore sanity:
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              test_when_finished "git config --unset core.capslock" &&
+#              hello world
+#      '
+#
+# That would be roughly equivalent to
+#
+#      test_expect_success 'test core.capslock' '
+#              git config core.capslock true &&
+#              hello world
+#              git config --unset core.capslock
+#      '
+#
+# except that the greeting and config --unset must both succeed for
+# the test to pass.
+
+test_when_finished () {
+       test_cleanup="{ $*
+               } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
+}
+
+test_done () {
+       GIT_EXIT_OK=t
+       test_results_dir="$TEST_DIRECTORY/test-results"
+       mkdir -p "$test_results_dir"
+       test_results_path="$test_results_dir/$this_test"
+
+       echo "total $test_count" >> $test_results_path
+       echo "success $test_success" >> $test_results_path
+       echo "fixed $test_fixed" >> $test_results_path
+       echo "broken $test_broken" >> $test_results_path
+       echo "failed $test_failure" >> $test_results_path
+       echo "" >> $test_results_path
+
+       [ -n "$EMACS_SERVER" ] && test_emacs '(kill-emacs)'
+
+       if [ "$test_failure" = "0" ]; then
+           if [ "$test_broken" = "0" ]; then
+               rm -rf "$remove_tmp"
+           fi
+           exit 0
+       else
+           exit 1
+       fi
+}
+
+emacs_generate_script () {
+       # Construct a little test script here for the benefit of the user,
+       # (who can easily run "run_emacs" to get the same emacs environment
+       # for investigating any failures).
+       cat <<EOF >"$TMP_DIRECTORY/run_emacs"
+#!/bin/sh
+export PATH=$PATH
+export NOTMUCH_CONFIG=$NOTMUCH_CONFIG
+
+# Here's what we are using here:
+#
+# --quick              Use minimal customization. This implies --no-init-file,
+#                     --no-site-file and (emacs 24) --no-site-lisp
+#
+# --directory          Ensure that the local elisp sources are found
+#
+# --load               Force loading of notmuch.el and test-lib.el
+
+exec ${TEST_EMACS} --quick \
+       --directory "$NOTMUCH_SRCDIR/emacs" --load notmuch.el \
+       --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
+       "\$@"
+EOF
+       chmod a+x "$TMP_DIRECTORY/run_emacs"
+}
+
+test_emacs () {
+       # test dependencies beforehand to avoid the waiting loop below
+       missing_dependencies=
+       test_require_external_prereq dtach || missing_dependencies=1
+       test_require_external_prereq emacs || missing_dependencies=1
+       test_require_external_prereq ${TEST_EMACSCLIENT} || missing_dependencies=1
+       test -z "$missing_dependencies" || return
+
+       if [ -z "$EMACS_SERVER" ]; then
+               emacs_tests="$NOTMUCH_SRCDIR/test/${this_test_bare}.el"
+               if [ -f "$emacs_tests" ]; then
+                       load_emacs_tests="--eval '(load \"$emacs_tests\")'"
+               else
+                       load_emacs_tests=
+               fi
+               server_name="notmuch-test-suite-$$"
+               # start a detached session with an emacs server
+               # user's TERM (or 'vt100' in case user's TERM is known dumb
+               # or unknown) is given to dtach which assumes a minimally
+               # VT100-compatible terminal -- and emacs inherits that
+               TERM=$SMART_TERM dtach -n "$TEST_TMPDIR/emacs-dtach-socket.$$" \
+                       sh -c "stty rows 24 cols 80; exec '$TMP_DIRECTORY/run_emacs' \
+                               --no-window-system \
+                               $load_emacs_tests \
+                               --eval '(setq server-name \"$server_name\")' \
+                               --eval '(server-start)' \
+                               --eval '(orphan-watchdog $$)'" || return
+               EMACS_SERVER="$server_name"
+               # wait until the emacs server is up
+               until test_emacs '()' >/dev/null 2>/dev/null; do
+                       sleep 1
+               done
+       fi
+
+       # Clear test-output output file.  Most Emacs tests end with a
+       # call to (test-output).  If the test code fails with an
+       # exception before this call, the output file won't get
+       # updated.  Since we don't want to compare against an output
+       # file from another test, so start out with an empty file.
+       rm -f OUTPUT
+       touch OUTPUT
+
+       ${TEST_EMACSCLIENT} --socket-name="$EMACS_SERVER" --eval "(notmuch-test-progn $@)"
+}
+
+test_python() {
+    # Note: if there is need to print debug information from python program,
+    # use stdout = os.fdopen(6, 'w') or stderr = os.fdopen(7, 'w')
+    PYTHONPATH="$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
+       $NOTMUCH_PYTHON -B - > OUTPUT
+}
+
+test_ruby() {
+    MAIL_DIR=$MAIL_DIR $NOTMUCH_RUBY -I $NOTMUCH_SRCDIR/bindings/ruby> OUTPUT
+}
+
+test_C () {
+    exec_file="test${test_count}"
+    test_file="${exec_file}.c"
+    cat > ${test_file}
+    ${TEST_CC} ${TEST_CFLAGS} -I${NOTMUCH_SRCDIR}/test -I${NOTMUCH_SRCDIR}/lib -o ${exec_file} ${test_file} -L${NOTMUCH_BUILDDIR}/lib/ -lnotmuch -ltalloc
+    echo "== stdout ==" > OUTPUT.stdout
+    echo "== stderr ==" > OUTPUT.stderr
+    ./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
+    notmuch_dir_sanitize OUTPUT.stdout OUTPUT.stderr > OUTPUT
+}
+
+
+# Creates a script that counts how much time it is executed and calls
+# notmuch.  $notmuch_counter_command is set to the path to the
+# generated script.  Use notmuch_counter_value() function to get the
+# current counter value.
+notmuch_counter_reset () {
+       notmuch_counter_command="$TMP_DIRECTORY/notmuch_counter"
+       if [ ! -x "$notmuch_counter_command" ]; then
+               notmuch_counter_state_path="$TMP_DIRECTORY/notmuch_counter.state"
+               cat >"$notmuch_counter_command" <<EOF || return
+#!/bin/sh
+
+read count < "$notmuch_counter_state_path"
+echo \$((count + 1)) > "$notmuch_counter_state_path"
+
+exec notmuch "\$@"
+EOF
+               chmod +x "$notmuch_counter_command" || return
+       fi
+
+       echo 0 > "$notmuch_counter_state_path"
+}
+
+# Returns the current notmuch counter value.
+notmuch_counter_value () {
+       if [ -r "$notmuch_counter_state_path" ]; then
+               read count < "$notmuch_counter_state_path"
+       else
+               count=0
+       fi
+       echo $count
+}
+
+test_reset_state_ () {
+       test -z "$test_init_done_" && test_init_
+
+       test_subtest_known_broken_=
+       test_subtest_missing_external_prereq_=()
+}
+
+# called once before the first subtest
+test_init_ () {
+       test_init_done_=t
+
+       # skip all tests if there were external prerequisites missing during init
+       test_check_missing_external_prereqs_ "all tests in $this_test" && test_done
+}
+
+
+# Where to run the tests
+TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
+
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
+
+if [ "${NOTMUCH_GMIME_MAJOR}" = 3 ]; then
+    test_subtest_broken_gmime_3 () {
+       test_subtest_known_broken
+    }
+    test_subtest_broken_gmime_2 () {
+       true
+    }
+else
+    test_subtest_broken_gmime_3 () {
+       true
+    }
+    test_subtest_broken_gmime_2 () {
+       test_subtest_known_broken
+    }
+fi
+
+emacs_generate_script
+
+
+# Use -P to resolve symlinks in our working directory so that the cwd
+# in subprocesses like git equals our $PWD (for pathname comparisons).
+cd -P "$TMP_DIRECTORY" || error "Cannot set up test environment"
+
+if test "$verbose" = "t"
+then
+       exec 4>&2 3>&1
+else
+       exec 4>test.output 3>&4
+fi
+
+for skp in $NOTMUCH_SKIP_TESTS
+do
+       to_skip=
+       for skp in $NOTMUCH_SKIP_TESTS
+       do
+               case "$this_test" in
+               $skp)
+                       to_skip=t
+                       break
+               esac
+               case "$this_test_bare" in
+               $skp)
+                       to_skip=t
+                       break
+               esac
+       done
+       case "$to_skip" in
+       t)
+               say_color skip >&3 "skipping test $this_test altogether"
+               say_color skip "skip all tests in $this_test"
+               test_done
+       esac
+done
+
+# Provide an implementation of the 'yes' utility
+yes () {
+       if test $# = 0
+       then
+               y=y
+       else
+               y="$*"
+       fi
+
+       while echo "$y"
+       do
+               :
+       done
+}
+
+# Fix some commands on Windows
+case $(uname -s) in
+*MINGW*)
+       # Windows has its own (incompatible) sort and find
+       sort () {
+               /usr/bin/sort "$@"
+       }
+       find () {
+               /usr/bin/find "$@"
+       }
+       sum () {
+               md5sum "$@"
+       }
+       # git sees Windows-style pwd
+       pwd () {
+               builtin pwd -W
+       }
+       # no POSIX permissions
+       # backslashes in pathspec are converted to '/'
+       # exec does not inherit the PID
+       ;;
+*)
+       test_set_prereq POSIXPERM
+       test_set_prereq BSLASHPSPEC
+       test_set_prereq EXECKEEPSPID
+       ;;
+esac
+
+test -z "$NO_PERL" && test_set_prereq PERL
+test -z "$NO_PYTHON" && test_set_prereq PYTHON
+
+# test whether the filesystem supports symbolic links
+ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
+rm -f y
+
+# convert variable from configure to more convenient form
+case "$NOTMUCH_DEFAULT_XAPIAN_BACKEND" in
+    glass)
+       db_ending=glass
+    ;;
+    chert)
+       db_ending=DB
+    ;;
+    *)
+       error "Unknown Xapian backend $NOTMUCH_DEFAULT_XAPIAN_BACKEND"
+esac
+# declare prerequisites for external binaries used in tests
+test_declare_external_prereq dtach
+test_declare_external_prereq emacs
+test_declare_external_prereq ${TEST_EMACSCLIENT}
+test_declare_external_prereq ${TEST_GDB}
+test_declare_external_prereq gpg
+test_declare_external_prereq openssl
+test_declare_external_prereq gpgsm
+test_declare_external_prereq ${NOTMUCH_PYTHON}
diff --git a/test/test-verbose b/test/test-verbose
new file mode 100755 (executable)
index 0000000..8af6d9a
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+test_description='the verbosity options of the test framework itself.'
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_begin_subtest 'print something in test_expect_success and pass'
+test_expect_success '
+  echo "hello stdout" &&
+  echo "hello stderr" >&2 &&
+  true
+'
+test_begin_subtest 'print something in test_expect_success and fail'
+test_expect_success '
+  echo "hello stdout" &&
+  echo "hello stderr" >&2 &&
+  false
+'
+test_begin_subtest 'print something between test_begin_subtest and test_expect_equal and pass'
+echo "hello stdout"
+echo "hello stderr" >&2
+test_expect_equal "a" "a"
+
+test_begin_subtest 'print something test_begin_subtest and test_expect_equal and fail'
+echo "hello stdout"
+echo "hello stderr" >&2
+test_expect_equal "a" "b"
+
+test_done
diff --git a/test/test.expected-output/test-verbose-no b/test/test.expected-output/test-verbose-no
new file mode 100644 (file)
index 0000000..1a2ff61
--- /dev/null
@@ -0,0 +1,21 @@
+
+test-verbose: Testing the verbosity options of the test framework itself.
+ PASS   print something in test_expect_success and pass
+ FAIL   print something in test_expect_success and fail
+       
+         echo "hello stdout" &&
+         echo "hello stderr" >&2 &&
+         false
+       
+hello stdout
+hello stderr
+ PASS   print something between test_begin_subtest and test_expect_equal and pass
+ FAIL   print something test_begin_subtest and test_expect_equal and fail
+       --- test-verbose.4.expected     2010-11-14 21:41:12.738189710 +0000
+       +++ test-verbose.4.output       2010-11-14 21:41:12.738189710 +0000
+       @@ -1 +1 @@
+       -b
+       +a
+hello stdout
+hello stderr
+
diff --git a/test/test.expected-output/test-verbose-yes b/test/test.expected-output/test-verbose-yes
new file mode 100644 (file)
index 0000000..d25466e
--- /dev/null
@@ -0,0 +1,25 @@
+
+test-verbose: Testing the verbosity options of the test framework itself.
+hello stdout
+hello stderr
+ PASS   print something in test_expect_success and pass
+hello stdout
+hello stderr
+ FAIL   print something in test_expect_success and fail
+       
+         echo "hello stdout" &&
+         echo "hello stderr" >&2 &&
+         false
+       
+hello stdout
+hello stderr
+ PASS   print something between test_begin_subtest and test_expect_equal and pass
+hello stdout
+hello stderr
+ FAIL   print something test_begin_subtest and test_expect_equal and fail
+       --- test-verbose.4.expected     2010-11-14 21:41:06.650023289 +0000
+       +++ test-verbose.4.output       2010-11-14 21:41:06.650023289 +0000
+       @@ -1 +1 @@
+       -b
+       +a
+
diff --git a/test/valgrind/suppressions b/test/valgrind/suppressions
new file mode 100644 (file)
index 0000000..6abf8b2
--- /dev/null
@@ -0,0 +1,6 @@
+{
+   zlib inflation uses uninitialize values
+   Memcheck:Cond
+   fun:inflateReset2
+   fun:inflateInit2_
+}
\ No newline at end of file
diff --git a/test/valgrind/valgrind.sh b/test/valgrind/valgrind.sh
new file mode 100755 (executable)
index 0000000..78700c0
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+base=$(basename "$0")
+
+TRACK_ORIGINS=
+
+VALGRIND_VERSION=$(valgrind --version)
+VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)')
+VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)')
+test 3 -gt "$VALGRIND_MAJOR" ||
+test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" ||
+TRACK_ORIGINS=--track-origins=yes
+
+exec valgrind -q --error-exitcode=126 \
+       --leak-check=no \
+       --suppressions="$GIT_VALGRIND/suppressions" \
+       --gen-suppressions=all \
+       $TRACK_ORIGINS \
+       --log-fd=4 \
+       --input-fd=4 \
+       $GIT_VALGRIND_OPTIONS \
+       "$GIT_VALGRIND"/../../"$base" "$@"
diff --git a/util/Makefile b/util/Makefile
new file mode 100644 (file)
index 0000000..fa25832
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C .. all
+
+.DEFAULT:
+       $(MAKE) -C .. $@
diff --git a/util/Makefile.local b/util/Makefile.local
new file mode 100644 (file)
index 0000000..ba03230
--- /dev/null
@@ -0,0 +1,16 @@
+# -*- makefile -*-
+
+dir := util
+extra_cflags += -I$(srcdir)/$(dir)
+
+libnotmuch_util_c_srcs := $(dir)/xutil.c $(dir)/error_util.c $(dir)/hex-escape.c \
+                 $(dir)/string-util.c $(dir)/talloc-extra.c $(dir)/zlib-extra.c \
+               $(dir)/util.c $(dir)/gmime-extra.c $(dir)/crypto.c
+
+libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o)
+
+$(dir)/libnotmuch_util.a: $(libnotmuch_util_modules)
+       $(call quiet,AR) rcs $@ $^
+
+SRCS := $(SRCS) $(libnotmuch_util_c_srcs)
+CLEAN := $(CLEAN) $(libnotmuch_util_modules) $(dir)/libnotmuch_util.a
diff --git a/util/crypto.c b/util/crypto.c
new file mode 100644 (file)
index 0000000..9d3b6da
--- /dev/null
@@ -0,0 +1,221 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Jameson Rollins
+ *
+ * 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: Jameson Rollins <jrollins@finestructure.net>
+ */
+
+#include "crypto.h"
+#include <strings.h>
+#define unused(x) x __attribute__ ((unused))
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+#if (GMIME_MAJOR_VERSION < 3)
+/* Create or pass on a GPG context (GMime 2.6) */
+static notmuch_status_t
+get_gpg_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+    if (ctx == NULL || crypto == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (crypto->gpgctx) {
+       *ctx = crypto->gpgctx;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* TODO: GMimePasswordRequestFunc */
+    crypto->gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
+    if (! crypto->gpgctx) {
+       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+    }
+
+    g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) crypto->gpgctx, true);
+    g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) crypto->gpgctx, false);
+
+    *ctx = crypto->gpgctx;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Create or pass on a PKCS7 context (GMime 2.6) */
+static notmuch_status_t
+get_pkcs7_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+    if (ctx == NULL || crypto == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (crypto->pkcs7ctx) {
+       *ctx = crypto->pkcs7ctx;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    /* TODO: GMimePasswordRequestFunc */
+    crypto->pkcs7ctx = g_mime_pkcs7_context_new (NULL);
+    if (! crypto->pkcs7ctx) {
+       return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+    }
+
+    g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) crypto->pkcs7ctx,
+                                          false);
+
+    *ctx = crypto->pkcs7ctx;
+    return NOTMUCH_STATUS_SUCCESS;
+}
+static const struct {
+    const char *protocol;
+    notmuch_status_t (*get_context) (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx);
+} protocols[] = {
+    {
+       .protocol = "application/pgp-signature",
+       .get_context = get_gpg_context,
+    },
+    {
+       .protocol = "application/pgp-encrypted",
+       .get_context = get_gpg_context,
+    },
+    {
+       .protocol = "application/pkcs7-signature",
+       .get_context = get_pkcs7_context,
+    },
+    {
+       .protocol = "application/x-pkcs7-signature",
+       .get_context = get_pkcs7_context,
+    },
+};
+
+/* for the specified protocol return the context pointer (initializing
+ * if needed) */
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+                                           const char *protocol,
+                                           GMimeCryptoContext **ctx)
+{
+    if (! protocol)
+       return NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL;
+
+    /* As per RFC 1847 section 2.1: "the [protocol] value token is
+     * comprised of the type and sub-type tokens of the Content-Type".
+     * As per RFC 1521 section 2: "Content-Type values, subtypes, and
+     * parameter names as defined in this document are
+     * case-insensitive."  Thus, we use strcasecmp for the protocol.
+     */
+    for (size_t i = 0; i < ARRAY_SIZE (protocols); i++) {
+       if (strcasecmp (protocol, protocols[i].protocol) == 0)
+           return protocols[i].get_context (crypto, ctx);
+    }
+
+    return NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL;
+}
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto)
+{
+    if (crypto->gpgctx) {
+       g_object_unref (crypto->gpgctx);
+       crypto->gpgctx = NULL;
+    }
+
+    if (crypto->pkcs7ctx) {
+       g_object_unref (crypto->pkcs7ctx);
+       crypto->pkcs7ctx = NULL;
+    }
+}
+#else
+void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
+{
+}
+#endif
+
+GMimeObject *
+_notmuch_crypto_decrypt (bool *attempted,
+                        notmuch_decryption_policy_t decrypt,
+                        notmuch_message_t *message,
+                        g_mime_3_unused(GMimeCryptoContext* crypto_ctx),
+                        GMimeMultipartEncrypted *part,
+                        GMimeDecryptResult **decrypt_result,
+                        GError **err)
+{
+    GMimeObject *ret = NULL;
+    if (decrypt == NOTMUCH_DECRYPT_FALSE)
+       return NULL;
+
+    /* the versions of notmuch that can support session key decryption */
+#if HAVE_GMIME_SESSION_KEYS
+    if (message) {
+       notmuch_message_properties_t *list = NULL;
+
+       for (list = notmuch_message_get_properties (message, "session-key", TRUE);
+            notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
+           if (err && *err) {
+               g_error_free (*err);
+               *err = NULL;
+           }
+           if (attempted)
+               *attempted = true;
+#if (GMIME_MAJOR_VERSION < 3)
+           ret = g_mime_multipart_encrypted_decrypt_session (part,
+                                                             crypto_ctx,
+                                                             notmuch_message_properties_value (list),
+                                                             decrypt_result, err);
+#else
+           ret = g_mime_multipart_encrypted_decrypt (part,
+                                                     GMIME_DECRYPT_NONE,
+                                                     notmuch_message_properties_value (list),
+                                                     decrypt_result, err);
+#endif
+           if (ret)
+               break;
+       }
+       if (list)
+           notmuch_message_properties_destroy (list);
+       if (ret)
+           return ret;
+    }
+#endif
+
+    if (err && *err) {
+       g_error_free (*err);
+       *err = NULL;
+    }
+
+    if (decrypt == NOTMUCH_DECRYPT_AUTO)
+       return ret;
+
+    if (attempted)
+       *attempted = true;
+#if (GMIME_MAJOR_VERSION < 3)
+#if HAVE_GMIME_SESSION_KEYS
+    gboolean oldgetsk = g_mime_crypto_context_get_retrieve_session_key (crypto_ctx);
+    gboolean newgetsk = (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result);
+    if (newgetsk != oldgetsk)
+       /* This could return an error, but we can't do anything about it, so ignore it */
+       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, newgetsk, NULL);
+#endif
+    ret = g_mime_multipart_encrypted_decrypt(part, crypto_ctx,
+                                            decrypt_result, err);
+#if HAVE_GMIME_SESSION_KEYS
+    if (newgetsk != oldgetsk)
+       g_mime_crypto_context_set_retrieve_session_key (crypto_ctx, oldgetsk, NULL);
+#endif
+#else
+    GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
+    if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result)
+       flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
+    ret = g_mime_multipart_encrypted_decrypt(part, flags, NULL,
+                                            decrypt_result, err);
+#endif
+    return ret;
+}
diff --git a/util/crypto.h b/util/crypto.h
new file mode 100644 (file)
index 0000000..c384601
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef _CRYPTO_H
+#define _CRYPTO_H
+
+#include <stdbool.h>
+#include "gmime-extra.h"
+#include "notmuch.h"
+
+typedef struct _notmuch_crypto {
+    bool verify;
+    notmuch_decryption_policy_t decrypt;
+#if (GMIME_MAJOR_VERSION < 3)
+    GMimeCryptoContext* gpgctx;
+    GMimeCryptoContext* pkcs7ctx;
+    const char *gpgpath;
+#endif
+} _notmuch_crypto_t;
+
+GMimeObject *
+_notmuch_crypto_decrypt (bool *attempted,
+                        notmuch_decryption_policy_t decrypt,
+                        notmuch_message_t *message,
+                        GMimeCryptoContext* crypto_ctx,
+                        GMimeMultipartEncrypted *part,
+                        GMimeDecryptResult **decrypt_result,
+                        GError **err);
+
+#if (GMIME_MAJOR_VERSION < 3)
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+                                           const char *protocol,
+                                           GMimeCryptoContext **ctx);
+#endif
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto);
+
+#endif
diff --git a/util/error_util.c b/util/error_util.c
new file mode 100644 (file)
index 0000000..e64162c
--- /dev/null
@@ -0,0 +1,40 @@
+/* error_util.c - internal error utilities for notmuch.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "error_util.h"
+
+void
+_internal_error (const char *format, ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    fprintf (stderr, "Internal error: ");
+    vfprintf (stderr, format, va_args);
+
+    va_end (va_args);
+    exit (1);
+}
+
diff --git a/util/error_util.h b/util/error_util.h
new file mode 100644 (file)
index 0000000..4bb338a
--- /dev/null
@@ -0,0 +1,47 @@
+/* error_util.h - Provide the INTERNAL_ERROR macro
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef ERROR_UTIL_H
+#define ERROR_UTIL_H
+
+#include <talloc.h>
+
+#include "function-attributes.h"
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that PRINTF_ATTRIBUTE comes from talloc.h
+ */
+void
+_internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2) NORETURN_ATTRIBUTE;
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that __location__ comes from talloc.h.
+ */
+#define INTERNAL_ERROR(format, ...)                    \
+    _internal_error (format " (%s).\n",                        \
+                    ##__VA_ARGS__, __location__)
+
+#endif
diff --git a/util/gmime-extra.c b/util/gmime-extra.c
new file mode 100644 (file)
index 0000000..bc1e3c4
--- /dev/null
@@ -0,0 +1,227 @@
+#include "gmime-extra.h"
+#include <string.h>
+
+GMimeStream *
+g_mime_stream_stdout_new()
+{
+    GMimeStream *stream_stdout = NULL;
+    GMimeStream *stream_buffered = NULL;
+
+    stream_stdout = g_mime_stream_pipe_new (STDOUT_FILENO);
+    if (!stream_stdout)
+       return NULL;
+
+    g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE (stream_stdout), FALSE);
+
+    stream_buffered = g_mime_stream_buffer_new (stream_stdout, GMIME_STREAM_BUFFER_BLOCK_WRITE);
+
+    g_object_unref (stream_stdout);
+
+    return stream_buffered;
+}
+
+/**
+ * copy a glib string into a talloc context, and free it.
+ */
+static char*
+g_string_talloc_strdup (void *ctx, char *g_string)
+{
+    char *new_str = talloc_strdup (ctx, g_string);
+    g_free (g_string);
+    return new_str;
+}
+
+#if (GMIME_MAJOR_VERSION < 3)
+
+const char *
+g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
+{
+    /* output user id only if validity is FULL or ULTIMATE. */
+    /* note that gmime 2.6 is using the term "trust" here, which
+     * is WRONG.  It's actually user id "validity". */
+    const char *name = g_mime_certificate_get_name (cert);
+    if (name == NULL)
+       return name;
+    GMimeCertificateTrust trust = g_mime_certificate_get_trust (cert);
+    if (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE)
+       return name;
+    return NULL;
+}
+
+char *
+g_mime_message_get_address_string (GMimeMessage *message, GMimeRecipientType type)
+{
+    InternetAddressList *list = g_mime_message_get_recipients (message, type);
+    return internet_address_list_to_string (list, 0);
+}
+
+inline InternetAddressList *
+g_mime_message_get_addresses (GMimeMessage *message, GMimeRecipientType type)
+{
+    return g_mime_message_get_recipients (message, type);
+}
+
+char *
+g_mime_message_get_date_string (void *ctx, GMimeMessage *message)
+{
+    char *date = g_mime_message_get_date_as_string (message);
+    return g_string_talloc_strdup (ctx, date);
+}
+
+InternetAddressList *
+g_mime_message_get_from (GMimeMessage *message)
+{
+    return internet_address_list_parse_string (g_mime_message_get_sender (message));
+}
+
+const char *
+g_mime_message_get_from_string (GMimeMessage *message) {
+    return  g_mime_message_get_sender (message);
+}
+
+InternetAddressList *
+g_mime_message_get_reply_to_list (GMimeMessage *message)
+{
+    const char *reply_to;
+
+    reply_to = g_mime_message_get_reply_to (message);
+    if (reply_to && *reply_to)
+       return internet_address_list_parse_string (reply_to);
+    else
+       return NULL;
+}
+
+/**
+ * return talloc allocated reply-to string
+ */
+char *
+g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message)
+{
+    return talloc_strdup(ctx, g_mime_message_get_reply_to (message));
+}
+
+gboolean
+g_mime_signature_status_good (GMimeSignatureStatus status) {
+    return (status == GMIME_SIGNATURE_STATUS_GOOD);
+}
+
+gboolean
+g_mime_signature_status_bad (GMimeSignatureStatus status) {
+    return (status == GMIME_SIGNATURE_STATUS_BAD);
+}
+
+gboolean
+g_mime_signature_status_error (GMimeSignatureError error) {
+    return (error != GMIME_SIGNATURE_ERROR_NONE);
+}
+
+gint64
+g_mime_utils_header_decode_date_unix (const char *date) {
+    return (gint64) g_mime_utils_header_decode_date (date, NULL);
+}
+
+#else /* GMime >= 3.0 */
+
+const char *
+g_mime_certificate_get_valid_userid (GMimeCertificate *cert)
+{
+    /* output user id only if validity is FULL or ULTIMATE. */
+    const char *uid = g_mime_certificate_get_user_id (cert);
+    if (uid == NULL)
+       return uid;
+    GMimeValidity validity = g_mime_certificate_get_id_validity (cert);
+    if (validity == GMIME_VALIDITY_FULL || validity == GMIME_VALIDITY_ULTIMATE)
+       return uid;
+    return NULL;
+}
+
+const char*
+g_mime_certificate_get_fpr16 (GMimeCertificate *cert) {
+    const char *fpr = g_mime_certificate_get_fingerprint (cert);
+    if (!fpr || strlen (fpr) < 16)
+       return fpr;
+
+    return fpr + (strlen (fpr) - 16);
+}
+
+char *
+g_mime_message_get_address_string (GMimeMessage *message, GMimeAddressType type)
+{
+    InternetAddressList *list = g_mime_message_get_addresses (message, type);
+    return internet_address_list_to_string (list, NULL, 0);
+}
+
+char *
+g_mime_message_get_date_string (void *ctx, GMimeMessage *message)
+{
+    GDateTime* parsed_date = g_mime_message_get_date (message);
+    if (parsed_date) {
+       char *date = g_mime_utils_header_format_date (parsed_date);
+       return g_string_talloc_strdup (ctx, date);
+    } else {
+       return talloc_strdup(ctx, "Thu, 01 Jan 1970 00:00:00 +0000");
+    }
+}
+
+InternetAddressList *
+g_mime_message_get_reply_to_list(GMimeMessage *message)
+{
+    return g_mime_message_get_reply_to (message);
+}
+
+const char *
+g_mime_message_get_from_string (GMimeMessage *message)
+{
+    return g_mime_object_get_header (GMIME_OBJECT (message), "From");
+}
+
+char *
+g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message)
+{
+    InternetAddressList *list = g_mime_message_get_reply_to (message);
+    return g_string_talloc_strdup (ctx, internet_address_list_to_string (list, NULL, 0));
+}
+
+void
+g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag)
+{
+    g_mime_parser_set_format (parser, flag ? GMIME_FORMAT_MBOX : GMIME_FORMAT_MESSAGE);
+}
+
+/* In GMime 3.0, status GOOD and VALID both imply something about the
+ * validity of the UIDs attached to the signing key. This forces us to
+ * use following somewhat relaxed definition of a "good" signature to
+ * preserve current notmuch semantics.
+ */
+
+gboolean
+g_mime_signature_status_good (GMimeSignatureStatus status) {
+    return ((status  & (GMIME_SIGNATURE_STATUS_RED | GMIME_SIGNATURE_STATUS_ERROR_MASK)) == 0);
+}
+
+gboolean
+g_mime_signature_status_bad (GMimeSignatureStatus status) {
+    return (status & GMIME_SIGNATURE_STATUS_RED);
+}
+
+gboolean
+g_mime_signature_status_error (GMimeSignatureStatus status) {
+    return (status & GMIME_SIGNATURE_STATUS_ERROR_MASK);
+}
+
+gint64
+g_mime_utils_header_decode_date_unix (const char *date) {
+    GDateTime* parsed_date = g_mime_utils_header_decode_date (date);
+    time_t ret;
+
+    if (parsed_date) {
+       ret = g_date_time_to_unix (parsed_date);
+       g_date_time_unref (parsed_date);
+    } else {
+       ret = 0;
+    }
+
+    return ret;
+}
+
+#endif
diff --git a/util/gmime-extra.h b/util/gmime-extra.h
new file mode 100644 (file)
index 0000000..ca822b8
--- /dev/null
@@ -0,0 +1,103 @@
+#ifndef _GMIME_EXTRA_H
+#define _GMIME_EXTRA_H
+#include <gmime/gmime.h>
+
+GMimeStream *g_mime_stream_stdout_new(void);
+
+#include <talloc.h>
+
+
+#if (GMIME_MAJOR_VERSION < 3)
+
+#define GMIME_ADDRESS_TYPE_TO GMIME_RECIPIENT_TYPE_TO
+#define GMIME_ADDRESS_TYPE_CC GMIME_RECIPIENT_TYPE_CC
+#define GMIME_ADDRESS_TYPE_BCC GMIME_RECIPIENT_TYPE_BCC
+
+#define g_mime_2_6_unref(obj) g_object_unref (obj)
+#define g_mime_3_unused(arg) arg
+#define g_mime_certificate_get_fpr16(cert) g_mime_certificate_get_key_id (cert)
+#else /* GMime >= 3.0 */
+
+#define GMIME_ENABLE_RFC_2047_WORKAROUNDS 0xdeadbeef
+#define g_mime_content_type_to_string(c) g_mime_content_type_get_mime_type (c)
+#define g_mime_filter_crlf_new(encode,dots) g_mime_filter_dos2unix_new (FALSE)
+#define g_mime_gpg_context_new(func,path) g_mime_gpg_context_new ()
+#define g_mime_gpg_context_set_use_agent(ctx,val) /*ignore*/
+#define g_mime_gpg_context_set_always_trust(ctx,val) /*ignore*/
+#define g_mime_init(flags) g_mime_init()
+#define g_mime_message_add_recipient(m,t,n,a) g_mime_message_add_mailbox (m,t,n,a)
+#define g_mime_message_set_subject(m,s) g_mime_message_set_subject(m,s,NULL)
+#define g_mime_multipart_signed_verify(mps,ctx,err) g_mime_multipart_signed_verify(mps, GMIME_ENCRYPT_NONE, err)
+#define g_mime_object_write_to_stream(o,s) g_mime_object_write_to_stream (o,NULL,s)
+#define g_mime_object_set_header(o,h,v) g_mime_object_set_header (o,h,v,NULL)
+#define g_mime_parser_construct_message(p) g_mime_parser_construct_message (p, g_mime_parser_options_get_default ())
+#define g_mime_part_get_content_object(p) g_mime_part_get_content (p)
+#define g_mime_pkcs7_context_new(arg) g_mime_pkcs7_context_new()
+#define g_mime_pkcs7_context_set_always_trust(ctx,val) /*ignore*/
+#define g_mime_signature_get_errors(sig) g_mime_signature_get_status (sig)
+#define g_mime_utils_header_decode_text(txt) g_mime_utils_header_decode_text (NULL, txt)
+#define internet_address_to_string(ia,encode) internet_address_to_string (ia,NULL,encode)
+#define internet_address_list_parse_string(str) internet_address_list_parse (NULL,str)
+
+typedef GMimeAddressType GMimeRecipientType;
+
+typedef GMimeSignatureStatus GMimeSignatureError;
+
+#define g_mime_2_6_unref(obj) /*ignore*/
+#define g_mime_3_unused(arg) unused(arg)
+#endif
+
+/**
+ * Get last 16 hex digits of fingerprint ("keyid")
+ */
+const char *g_mime_certificate_get_fpr16 (GMimeCertificate *cert);
+/**
+ * Return the contents of the appropriate address header as a string
+ * Should be freed using g_free
+ */
+char *g_mime_message_get_address_string (GMimeMessage *message, GMimeRecipientType type);
+
+InternetAddressList * g_mime_message_get_addresses (GMimeMessage *message, GMimeRecipientType type);
+
+/**
+ * return talloc allocated date string
+ */
+
+char *g_mime_message_get_date_string (void *ctx, GMimeMessage *message);
+
+/**
+ * glib allocated list of From: addresses
+ */
+
+InternetAddressList * g_mime_message_get_from (GMimeMessage *message);
+
+
+/**
+ * return string for From: address
+ * (owned by gmime)
+ */
+const char * g_mime_message_get_from_string (GMimeMessage *message);
+
+InternetAddressList * g_mime_message_get_reply_to_list (GMimeMessage *message);
+
+/**
+ * return talloc allocated reply-to string
+ */
+char * g_mime_message_get_reply_to_string (void *ctx, GMimeMessage *message);
+
+void g_mime_parser_set_scan_from (GMimeParser *parser, gboolean flag);
+
+gboolean g_mime_signature_status_good (GMimeSignatureStatus status);
+
+gboolean g_mime_signature_status_bad (GMimeSignatureStatus status);
+
+gboolean g_mime_signature_status_error (GMimeSignatureError status);
+
+gint64 g_mime_utils_header_decode_date_unix (const char *date);
+
+/**
+ * Return string for valid User ID (or NULL if no valid User ID exists)
+ */
+const char * g_mime_certificate_get_valid_userid (GMimeCertificate *cert);
+
+#endif
diff --git a/util/hex-escape.c b/util/hex-escape.c
new file mode 100644 (file)
index 0000000..8883ff9
--- /dev/null
@@ -0,0 +1,159 @@
+/* hex-escape.c -  Manage encoding and decoding of byte strings into path names
+ *
+ * Copyright (c) 2011 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <talloc.h>
+#include <ctype.h>
+#include "error_util.h"
+#include "hex-escape.h"
+
+static const char *output_charset =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_@=.,";
+
+static const char escape_char = '%';
+
+static int
+is_output (char c)
+{
+    return (strchr (output_charset, c) != NULL);
+}
+
+static int
+maybe_realloc (void *ctx, size_t needed, char **out, size_t *out_size)
+{
+    if (*out_size < needed) {
+
+       if (*out == NULL)
+           *out = talloc_size (ctx, needed);
+       else
+           *out = talloc_realloc (ctx, *out, char, needed);
+
+       if (*out == NULL)
+           return 0;
+
+       *out_size = needed;
+    }
+    return 1;
+}
+
+hex_status_t
+hex_encode (void *ctx, const char *in, char **out, size_t *out_size)
+{
+
+    const char *p;
+    char *q;
+
+    size_t needed = 1;  /* for the NUL */
+
+    assert (ctx); assert (in); assert (out); assert (out_size);
+
+    for (p = in; *p; p++) {
+       needed += is_output (*p) ? 1 : 3;
+    }
+
+    if (*out == NULL)
+       *out_size = 0;
+
+    if (!maybe_realloc (ctx, needed, out, out_size))
+       return HEX_OUT_OF_MEMORY;
+
+    q = *out;
+    p = in;
+
+    while (*p) {
+       if (is_output (*p)) {
+           *q++ = *p++;
+       } else {
+           sprintf (q, "%%%02x", (unsigned char)*p++);
+           q += 3;
+       }
+    }
+
+    *q = '\0';
+    return HEX_SUCCESS;
+}
+
+/* Hex decode 'in' to 'out'.
+ *
+ * This must succeed for in == out to support hex_decode_inplace().
+ */
+static hex_status_t
+hex_decode_internal (const char *in, unsigned char *out)
+{
+    char buf[3];
+
+    while (*in) {
+       if (*in == escape_char) {
+           char *endp;
+
+           /* This also handles unexpected end-of-string. */
+           if (!isxdigit ((unsigned char) in[1]) ||
+               !isxdigit ((unsigned char) in[2]))
+               return HEX_SYNTAX_ERROR;
+
+           buf[0] = in[1];
+           buf[1] = in[2];
+           buf[2] = '\0';
+
+           *out = strtoul (buf, &endp, 16);
+
+           if (endp != buf + 2)
+               return HEX_SYNTAX_ERROR;
+
+           in += 3;
+           out++;
+       } else {
+           *out++ = *in++;
+       }
+    }
+
+    *out = '\0';
+
+    return HEX_SUCCESS;
+}
+
+hex_status_t
+hex_decode_inplace (char *s)
+{
+    /* A decoded string is never longer than the encoded one, so it is
+     * safe to decode a string onto itself. */
+    return hex_decode_internal (s, (unsigned char *) s);
+}
+
+hex_status_t
+hex_decode (void *ctx, const char *in, char **out, size_t * out_size)
+{
+    const char *p;
+    size_t needed = 1; /* for the NUL */
+
+    assert (ctx); assert (in); assert (out); assert (out_size);
+
+    for (p = in; *p; p++)
+       if ((p[0] == escape_char) && isxdigit (p[1]) && isxdigit (p[2]))
+           needed -= 1;
+       else
+           needed += 1;
+
+    if (!maybe_realloc (ctx, needed, out, out_size))
+       return HEX_OUT_OF_MEMORY;
+
+    return hex_decode_internal (in, (unsigned char *) *out);
+}
diff --git a/util/hex-escape.h b/util/hex-escape.h
new file mode 100644 (file)
index 0000000..5182042
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef _HEX_ESCAPE_H
+#define _HEX_ESCAPE_H
+
+typedef enum hex_status {
+    HEX_SUCCESS = 0,
+    HEX_SYNTAX_ERROR,
+    HEX_OUT_OF_MEMORY
+} hex_status_t;
+
+/*
+ * The API for hex_encode() and hex_decode() is modelled on that for
+ * getline.
+ *
+ * If 'out' points to a NULL pointer a char array of the appropriate
+ * size is allocated using talloc, and out_size is updated.
+ *
+ * If 'out' points to a non-NULL pointer, it assumed to describe an
+ * existing char array, with the size given in *out_size.  This array
+ * may be resized by talloc_realloc if needed; in this case *out_size
+ * will also be updated.
+ *
+ * Note that it is an error to pass a NULL pointer for any parameter
+ * of these routines.
+ */
+
+hex_status_t
+hex_encode (void *talloc_ctx, const char *in, char **out,
+            size_t *out_size);
+
+hex_status_t
+hex_decode (void *talloc_ctx, const char *in, char **out,
+            size_t *out_size);
+
+/*
+ * Non-allocating hex decode to decode 's' in-place. The length of the
+ * result is always equal to or shorter than the length of the
+ * original.
+ */
+hex_status_t
+hex_decode_inplace (char *s);
+#endif
diff --git a/util/string-util.c b/util/string-util.c
new file mode 100644 (file)
index 0000000..fc2058e
--- /dev/null
@@ -0,0 +1,270 @@
+/* string-util.c -  Extra or enhanced routines for null terminated strings.
+ *
+ * Copyright (c) 2012 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/ .
+ *
+ * Author: Jani Nikula <jani@nikula.org>
+ */
+
+
+#include "string-util.h"
+#include "talloc.h"
+
+#include <ctype.h>
+#include <errno.h>
+
+char *
+strtok_len (char *s, const char *delim, size_t *len)
+{
+    /* skip initial delims */
+    s += strspn (s, delim);
+
+    /* length of token */
+    *len = strcspn (s, delim);
+
+    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)
+{
+    char *out, *loop;
+
+    if (! str)
+       return NULL;
+
+    out = talloc_strdup (ctx, str);
+    if (! out)
+       return NULL;
+
+    for (loop = out; *loop; loop++) {
+       if (*loop == '\t' || *loop == '\n')
+           *loop = ' ';
+       else if ((unsigned char)(*loop) < 32)
+           *loop = '?';
+    }
+
+    return out;
+}
+
+static int
+is_unquoted_terminator (unsigned char c)
+{
+    return c == 0 || c <= ' ' || c == ')';
+}
+
+int
+make_boolean_term (void *ctx, const char *prefix, const char *term,
+                  char **buf, size_t *len)
+{
+    const char *in;
+    char *out;
+    size_t needed = 3;
+    int need_quoting = 0;
+
+    /* Do we need quoting?  To be paranoid, we quote anything
+     * containing a quote or '(', even though these only matter at the
+     * beginning, and anything containing non-ASCII text. */
+    if (! term[0])
+       need_quoting = 1;
+    for (in = term; *in && !need_quoting; in++)
+       if (is_unquoted_terminator (*in) || *in == '"' || *in == '('
+           || (unsigned char)*in > 127)
+           need_quoting = 1;
+
+    if (need_quoting)
+       for (in = term; *in; in++)
+           needed += (*in == '"') ? 2 : 1;
+    else
+       needed = strlen (term) + 1;
+
+    /* Reserve space for the prefix */
+    if (prefix)
+       needed += strlen (prefix) + 1;
+
+    if ((*buf == NULL) || (needed > *len)) {
+       *len = 2 * needed;
+       *buf = talloc_realloc (ctx, *buf, char, *len);
+    }
+
+    if (! *buf) {
+       errno = ENOMEM;
+       return -1;
+    }
+
+    out = *buf;
+
+    /* Copy in the prefix */
+    if (prefix) {
+       strcpy (out, prefix);
+       out += strlen (prefix);
+       *out++ = ':';
+    }
+
+    if (! need_quoting) {
+       strcpy (out, term);
+       return 0;
+    }
+
+    /* Quote term by enclosing it in double quotes and doubling any
+     * internal double quotes. */
+    *out++ = '"';
+    in = term;
+    while (*in) {
+       if (*in == '"')
+           *out++ = '"';
+       *out++ = *in++;
+    }
+    *out++ = '"';
+    *out = '\0';
+
+    return 0;
+}
+
+const char*
+skip_space (const char *str)
+{
+    while (*str && isspace ((unsigned char) *str))
+       ++str;
+    return str;
+}
+
+int
+parse_boolean_term (void *ctx, const char *str,
+                   char **prefix_out, char **term_out)
+{
+    int err = EINVAL;
+    *prefix_out = *term_out = NULL;
+
+    /* Parse prefix */
+    str = skip_space (str);
+    const char *pos = strchr (str, ':');
+    if (! pos || pos == str)
+       goto FAIL;
+    *prefix_out = talloc_strndup (ctx, str, pos - str);
+    if (! *prefix_out) {
+       err = ENOMEM;
+       goto FAIL;
+    }
+    ++pos;
+
+    /* Implement de-quoting compatible with make_boolean_term. */
+    if (*pos == '"') {
+       char *out = talloc_array (ctx, char, strlen (pos));
+       int closed = 0;
+       if (! out) {
+           err = ENOMEM;
+           goto FAIL;
+       }
+       *term_out = out;
+       /* Skip the opening quote, find the closing quote, and
+        * un-double doubled internal quotes. */
+       for (++pos; *pos; ) {
+           if (*pos == '"') {
+               ++pos;
+               if (*pos != '"') {
+                   /* Found the closing quote. */
+                   closed = 1;
+                   pos = skip_space (pos);
+                   break;
+               }
+           }
+           *out++ = *pos++;
+       }
+       /* Did the term terminate without a closing quote or is there
+        * trailing text after the closing quote? */
+       if (!closed || *pos)
+           goto FAIL;
+       *out = '\0';
+    } else {
+       const char *start = pos;
+       /* Check for text after the boolean term. */
+       while (! is_unquoted_terminator (*pos))
+           ++pos;
+       if (*skip_space (pos)) {
+           err = EINVAL;
+           goto FAIL;
+       }
+       /* No trailing text; dup the string so the caller can free
+        * it. */
+       *term_out = talloc_strndup (ctx, start, pos - start);
+       if (! *term_out) {
+           err = ENOMEM;
+           goto FAIL;
+       }
+    }
+    return 0;
+
+ FAIL:
+    talloc_free (*prefix_out);
+    talloc_free (*term_out);
+    errno = err;
+    return -1;
+}
+
+int
+strcmp_null (const char *s1, const char *s2)
+{
+    if (s1 && s2)
+       return strcmp (s1, s2);
+    else if (! s1 && ! s2)
+       return 0;
+    else if (s1)
+       return 1;       /* s1 (non-NULL) is greater than s2 (NULL) */
+    else
+       return -1;      /* s1 (NULL) is less than s2 (non-NULL) */
+}
+
+int
+strcase_equal (const void *a, const void *b)
+{
+    return strcasecmp (a, b) == 0;
+}
+
+unsigned int
+strcase_hash (const void *ptr)
+{
+    const char *s = ptr;
+
+    /* This is the djb2 hash. */
+    unsigned int hash = 5381;
+    while (s && *s) {
+       hash = ((hash << 5) + hash) + tolower (*s);
+       s++;
+    }
+
+    return hash;
+}
+
+void
+strip_trailing (char *str, char ch)
+{
+    int i;
+
+    for (i = strlen (str) - 1; i >= 0; i--) {
+       if (str[i] == ch)
+           str[i] = '\0';
+       else
+           break;
+    }
+}
diff --git a/util/string-util.h b/util/string-util.h
new file mode 100644 (file)
index 0000000..4c110a2
--- /dev/null
@@ -0,0 +1,86 @@
+#ifndef _STRING_UTIL_H
+#define _STRING_UTIL_H
+
+#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.
+ *
+ * Usage pattern:
+ *
+ * const char *tok = input;
+ * const char *delim = " \t";
+ * size_t tok_len = 0;
+ *
+ * while ((tok = strtok_len (tok + tok_len, delim, &tok_len)) != NULL) {
+ *     // do stuff with string tok of length tok_len
+ * }
+ */
+
+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,
+ * non-printable characters with question marks.
+ */
+char *sanitize_string (const void *ctx, const char *str);
+
+/* Construct a boolean term query with the specified prefix (e.g.,
+ * "id") and search term, quoting term as necessary.  Specifically, if
+ * term contains any non-printable ASCII characters, non-ASCII
+ * characters, close parenthesis or double quotes, it will be enclosed
+ * in double quotes and any internal double quotes will be doubled
+ * (e.g. a"b -> "a""b").  The result will be a valid notmuch query and
+ * can be parsed by parse_boolean_term.
+ *
+ * Output is into buf; it may be talloc_realloced.
+ * Return: 0 on success, -1 on error.  errno will be set to ENOMEM if
+ * there is an allocation failure.
+ */
+int make_boolean_term (void *talloc_ctx, const char *prefix, const char *term,
+                      char **buf, size_t *len);
+
+/* Parse a boolean term query consisting of a prefix, a colon, and a
+ * term that may be quoted as described for make_boolean_term.  If the
+ * term is not quoted, then it ends at the first whitespace or close
+ * parenthesis.  str may containing leading or trailing whitespace,
+ * but anything else is considered a parse error.  This is compatible
+ * with anything produced by make_boolean_term, and supports a subset
+ * of the quoting styles supported by Xapian (and hence notmuch).
+ * *prefix_out and *term_out will be talloc'd with context ctx.
+ *
+ * Return: 0 on success, -1 on error.  errno will be set to EINVAL if
+ * there is a parse error or ENOMEM if there is an allocation failure.
+ */
+int
+parse_boolean_term (void *ctx, const char *str,
+                   char **prefix_out, char **term_out);
+
+/* strcmp that handles NULL strings; in strcmp terms a NULL string is
+ * considered to be less than a non-NULL string.
+ */
+int strcmp_null (const char *s1, const char *s2);
+
+/* GLib GEqualFunc compatible strcasecmp wrapper */
+int strcase_equal (const void *a, const void *b);
+
+/* GLib GHashFunc compatible case insensitive hash function */
+unsigned int strcase_hash (const void *ptr);
+
+void strip_trailing (char *str, char ch);
+
+const char* skip_space (const char *str);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/util/talloc-extra.c b/util/talloc-extra.c
new file mode 100644 (file)
index 0000000..9626247
--- /dev/null
@@ -0,0 +1,14 @@
+#include <string.h>
+#include "talloc-extra.h"
+
+char *
+talloc_strndup_named_const (void *ctx, const char *str,
+                           size_t len, const char *name)
+{
+    char *ptr = talloc_strndup (ctx, str, len);
+
+    if (ptr)
+       talloc_set_name_const (ptr, name);
+
+    return ptr;
+}
diff --git a/util/talloc-extra.h b/util/talloc-extra.h
new file mode 100644 (file)
index 0000000..eac5dc0
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef _TALLOC_EXTRA_H
+#define _TALLOC_EXTRA_H
+
+#include <talloc.h>
+
+/* Like talloc_strndup, but take an extra parameter for the internal talloc
+ * name (for debugging) */
+
+char *
+talloc_strndup_named_const (void *ctx, const char *str,
+                           size_t len, const char *name);
+
+/* use the __location__ macro from talloc.h to name a string according to its
+ * source location */
+
+#define talloc_strndup_debug(ctx, str, len) talloc_strndup_named_const (ctx, str, len, __location__)
+
+#endif
diff --git a/util/util.c b/util/util.c
new file mode 100644 (file)
index 0000000..06659b3
--- /dev/null
@@ -0,0 +1,24 @@
+#include "util.h"
+#include "error_util.h"
+#include <string.h>
+#include <errno.h>
+
+const char *
+util_error_string (util_status_t errnum)
+{
+    switch (errnum) {
+    case UTIL_SUCCESS:
+       return "success";
+    case UTIL_OUT_OF_MEMORY:
+       return "out of memory";
+    case UTIL_EOF:
+       return "end of file";
+    case UTIL_ERRNO:
+       return strerror (errno);
+    case UTIL_GZERROR:
+       /* we lack context to be more informative here */
+       return "zlib error";
+    default:
+       INTERNAL_ERROR("unexpected error status %d", errnum);
+    }
+}
diff --git a/util/util.h b/util/util.h
new file mode 100644 (file)
index 0000000..b24860a
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef _UTIL_H
+#define _UTIL_H
+
+typedef enum util_status {
+    /**
+     * No error occurred.
+     */
+    UTIL_SUCCESS = 0,
+    /**
+     * Out of memory.
+     */
+    UTIL_OUT_OF_MEMORY,
+    /**
+     * End of stream reached while attempting to read.
+     */
+    UTIL_EOF,
+    /**
+     * Low level error occurred, consult errno.
+     */
+    UTIL_ERRNO,
+    /**
+     * Zlib error occurred, call gzerror for details.
+     */
+    UTIL_GZERROR
+} util_status_t;
+
+const char *
+util_error_string (util_status_t status);
+#endif
diff --git a/util/xutil.c b/util/xutil.c
new file mode 100644 (file)
index 0000000..f211eaa
--- /dev/null
@@ -0,0 +1,139 @@
+/* xutil.c - Various wrapper functions to abort on error.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "xutil.h"
+#include "error_util.h"
+
+void *
+xcalloc (size_t nmemb, size_t size)
+{
+    void *ret;
+
+    ret = calloc (nmemb, size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+void *
+xmalloc (size_t size)
+{
+    void *ret;
+
+    ret = malloc (size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+void *
+xrealloc (void *ptr, size_t size)
+{
+    void *ret;
+
+    ret = realloc (ptr, size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+char *
+xstrdup (const char *s)
+{
+    char *ret;
+
+    ret = strdup (s);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+char *
+xstrndup (const char *s, size_t n)
+{
+    char *ret;
+
+    if (strlen (s) <= n)
+       n = strlen (s);
+
+    ret = malloc (n + 1);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+    memcpy (ret, s, n);
+    ret[n] = '\0';
+
+    return ret;
+}
+
+int
+xregcomp (regex_t *preg, const char *regex, int cflags)
+{
+    int rerr;
+
+    rerr = regcomp (preg, regex, cflags);
+    if (rerr) {
+       size_t error_size = regerror (rerr, preg, NULL, 0);
+       char *error = xmalloc (error_size);
+
+       regerror (rerr, preg, error, error_size);
+       fprintf (stderr, "compiling regex %s: %s\n",
+                       regex, error);
+       free (error);
+       return 1;
+    }
+    return 0;
+}
+
+int
+xregexec (const regex_t *preg, const char *string,
+         size_t nmatch, regmatch_t pmatch[], int eflags)
+{
+    unsigned int i;
+    int rerr;
+
+    rerr = regexec (preg, string, nmatch, pmatch, eflags);
+    if (rerr)
+       return rerr;
+
+    for (i = 0; i < nmatch; i++) {
+       if (pmatch[i].rm_so == -1)
+           INTERNAL_ERROR ("matching regex against %s: Sub-match %d not found\n",
+                           string, i);
+    }
+
+    return 0;
+}
diff --git a/util/xutil.h b/util/xutil.h
new file mode 100644 (file)
index 0000000..4829f33
--- /dev/null
@@ -0,0 +1,52 @@
+/* xutil.h - Various wrapper functions to abort on error.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * 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/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_XUTIL_H
+#define NOTMUCH_XUTIL_H
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <regex.h>
+
+/* xutil.c */
+void *
+xcalloc (size_t nmemb, size_t size);
+
+void *
+xmalloc (size_t size);
+
+void *
+xrealloc (void *ptrr, size_t size);
+
+char *
+xstrdup (const char *s);
+
+char *
+xstrndup (const char *s, size_t n);
+
+/* Returns 0 for successful compilation, 1 otherwise */
+int
+xregcomp (regex_t *preg, const char *regex, int cflags);
+
+int
+xregexec (const regex_t *preg, const char *string,
+         size_t nmatch, regmatch_t pmatch[], int eflags);
+
+#endif
diff --git a/util/zlib-extra.c b/util/zlib-extra.c
new file mode 100644 (file)
index 0000000..2b2cd8f
--- /dev/null
@@ -0,0 +1,85 @@
+/* zlib-extra.c -  Extra or enhanced routines for compressed I/O.
+ *
+ * Copyright (c) 2014 David Bremner
+ *
+ * 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/ .
+ *
+ * Author: David Bremner <david@tethera.net>
+ */
+
+#include "zlib-extra.h"
+#include <talloc.h>
+#include <stdio.h>
+#include <string.h>
+
+/* mimic POSIX/glibc getline, but on a zlib gzFile stream, and using talloc */
+util_status_t
+gz_getline (void *talloc_ctx, char **bufptr, ssize_t *bytes_read, gzFile stream)
+{
+    char *buf = *bufptr;
+    unsigned int len;
+    size_t offset = 0;
+
+    if (buf) {
+       len = talloc_array_length (buf);
+    } else {
+       /* same as getdelim from gnulib */
+       len = 120;
+       buf = talloc_array (talloc_ctx, char, len);
+       if (buf == NULL)
+           return UTIL_OUT_OF_MEMORY;
+    }
+
+    while (1) {
+       if (! gzgets (stream, buf + offset, len - offset)) {
+           /* Null indicates EOF or error */
+           int zlib_status = 0;
+           (void) gzerror (stream, &zlib_status);
+           switch (zlib_status) {
+           case Z_OK:
+               /* no data read before EOF */
+               if (offset == 0)
+                   return UTIL_EOF;
+               else
+                   goto SUCCESS;
+           case Z_ERRNO:
+               return UTIL_ERRNO;
+           default:
+               return UTIL_GZERROR;
+           }
+       }
+
+       offset += strlen (buf + offset);
+
+       if (buf[offset - 1] == '\n')
+           goto SUCCESS;
+
+       len *= 2;
+       buf = talloc_realloc (talloc_ctx, buf, char, len);
+       if (buf == NULL)
+           return UTIL_OUT_OF_MEMORY;
+    }
+ SUCCESS:
+    *bufptr = buf;
+    *bytes_read = offset;
+    return UTIL_SUCCESS;
+}
+
+const char *gz_error_string (util_status_t status, gzFile file)
+{
+    if (status == UTIL_GZERROR)
+       return gzerror (file, NULL);
+    else
+       return util_error_string (status);
+}
diff --git a/util/zlib-extra.h b/util/zlib-extra.h
new file mode 100644 (file)
index 0000000..aedfd48
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef _ZLIB_EXTRA_H
+#define _ZLIB_EXTRA_H
+
+#include "util.h"
+#include <zlib.h>
+
+/* Like getline, but read from a gzFile. Allocation is with talloc.
+ * Returns:
+ *
+ *   UTIL_SUCCESS, UTIL_OUT_OF_MEMORY, UTIL_ERRNO, UTIL_GZERROR
+ *                     Consult util.h for description
+ *
+ *   UTIL_EOF          End of file encountered before
+ *                     any characters read
+ */
+util_status_t
+gz_getline (void *ctx, char **lineptr, ssize_t *bytes_read, gzFile stream);
+
+/* return a suitable error string based on the return status
+ *  from gz_readline
+ */
+
+const char *
+gz_error_string (util_status_t status, gzFile stream);
+#endif
diff --git a/version b/version
new file mode 100644 (file)
index 0000000..a37255a
--- /dev/null
+++ b/version
@@ -0,0 +1 @@
+0.28.2
diff --git a/vim/Makefile b/vim/Makefile
new file mode 100644 (file)
index 0000000..b6f9db7
--- /dev/null
@@ -0,0 +1,15 @@
+prefix = $(HOME)/.vim
+
+INSTALL = install -v -D -m644
+D = $(DESTDIR)
+
+all:
+       @echo "Nothing to build"
+
+install:
+       $(INSTALL) $(CURDIR)/notmuch.vim $(D)$(prefix)/plugin/notmuch.vim
+       $(INSTALL) $(CURDIR)/notmuch.txt $(D)$(prefix)/doc/notmuch.txt
+       @$(foreach file,$(wildcard syntax/*), \
+               $(INSTALL) $(CURDIR)/$(file) $(D)$(prefix)/$(file);)
+
+.PHONY: all install
diff --git a/vim/README b/vim/README
new file mode 100644 (file)
index 0000000..c137bac
--- /dev/null
@@ -0,0 +1,62 @@
+== notmuch vim ruby ==
+
+This is a vim plug-in that provides a fully usable mail client interface,
+utilizing the notmuch framework, through it's ruby bindings.
+
+== install ==
+
+Simply run 'make install'. However, check that you have the dependencies below.
+
+=== vim +ruby ===
+
+Make sure your vim version has ruby support: check for +ruby in 'vim --version'
+features.
+
+=== ruby bindings ===
+
+Check if you are able to run the following command cleanly:
+
+ % ruby -e "require 'notmuch'"
+
+If you don't see any errors, it means it's working and you can go to the next
+section.
+
+If it's not, you would need to compile them. Go to the 'bindings/ruby'
+directory in the notmuch source tree.
+
+=== mail gem ===
+
+Since libnotmuch library concentrates on things other than handling mail, we
+need a library to do that, and for Ruby the best library for that is called
+'mail'. The easiest way to install it is with ruby's gem. In most distro's the
+package is called 'rubygems'.
+
+Once you have gem, run:
+
+ % gem install mail
+
+In some systems gems are installed on a per-user basis by default, so make sure
+you are running as the same user as the one that installed them.
+
+This gem is not mandatory, but it's extremely recommended.
+
+== Running ==
+
+Simple:
+
+ % gvim -c ':NotMuch'
+
+Enjoy ;)
+
+== More stuff ==
+
+As an example to configure a key mapping to add the tag 'to-do' and archive,
+this is what I use:
+
+let g:notmuch_rb_custom_search_maps = {
+       \ 't':          'search_tag("+to-do -inbox")',
+       \ }
+
+let g:notmuch_rb_custom_show_maps = {
+       \ 't':          'show_tag("+to-do -inbox")',
+       \ }
diff --git a/vim/notmuch.txt b/vim/notmuch.txt
new file mode 100644 (file)
index 0000000..4374102
--- /dev/null
@@ -0,0 +1,153 @@
+*notmuch.txt*  Plug-in to make vim a nice email client using notmuch
+
+Author: Felipe Contreras <felipe.contreras@gmail.com>
+
+Overview                                       |notmuch-intro|
+Usage                                          |notmuch-usage|
+Mappings                                       |notmuch-mappings|
+Configuration                                  |notmuch-config|
+
+==============================================================================
+OVERVIEW                                       *notmuch-intro*
+
+This is a vim plug-in that provides a fully usable mail client interface,
+utilizing the notmuch framework.
+
+It has three main views: folders, search, and thread. In the folder view you
+can find a summary of saved searches, In the search view you can see all the
+threads that comprise the selected search, and in the thread view you can read
+every mail in the thread.
+
+==============================================================================
+USAGE                                          *notmuch-usage*
+
+To use it, simply run the `:NotMuch` command.
+
+By default you start in the folder view which shows you default searches and
+the number of threads that match those:
+>
+       10 new                  (tag:inbox and tag:unread)
+       20 inbox                (tag:inbox)
+       30 unread               (tag:unread)
+<
+You can see the threads of each by clicking `enter`, which sends you to the
+search view. In both the search and folder views you can type `s` to type a
+new search, or `=` to refresh. To see a thread, type `enter` again.
+
+To exit a view, click `q`.
+
+Also, you can specify a search directly:
+>
+       :NotMuch is:inbox and date:yesterday..
+<
+==============================================================================
+MAPPINGS                                       *notmuch-mappings*
+
+------------------------------------------------------------------------------
+Folder view~
+
+<enter>        Show selected search
+s      Enter a new search
+=      Refresh
+c      Compose a new mail
+
+------------------------------------------------------------------------------
+Search view~
+
+q      Quit view
+<enter>        Show selected search
+<space>        Show selected search with filter
+A      Archive (-inbox -unread)
+I      Mark as read (-unread)
+t      Tag (prompted)
+s      Search
+=      Refresh
+?      Show search information
+c      Compose a new mail
+>
+------------------------------------------------------------------------------
+Thread view~
+
+q      Quit view
+A      Archive (-inbox -unread)
+I      Mark as read (-unread)
+t      Tag (prompted)
+s      Search
+p      Save patches
+r      Reply
+?      Show thread information
+<tab>  Show next message
+c      Compose a new mail
+
+------------------------------------------------------------------------------
+Compose view~
+
+q      Quit view
+s      Send
+
+==============================================================================
+CONFIGURATION                                  *notmuch-config*
+
+You can add the following configurations to your `.vimrc`, or
+`~/.vim/plugin/notmuch.vim`.
+
+                                               *g:notmuch_folders*
+
+The first thing you might want to do is set your custom searches.
+>
+       let g:notmuch_folders = [
+               \ [ 'new', 'tag:inbox and tag:unread' ],
+               \ [ 'inbox', 'tag:inbox' ],
+               \ [ 'unread', 'tag:unread' ],
+               \ [ 'to-do', 'tag:to-do' ],
+               \ [ 'to-me', 'to:john.doe and tag:new' ],
+               \ ]
+<
+
+                                               *g:notmuch_custom_search_maps*
+                                               *g:notmuch_custom_show_maps*
+
+You can also configure the keyboard mappings for the different views:
+>
+       let g:notmuch_custom_search_maps = {
+               \ 't':          'search_tag("+to-do -inbox")',
+               \ 'd':          'search_tag("+deleted -inbox -unread")',
+               \ }
+
+       let g:notmuch_custom_show_maps = {
+               \ 't':          'show_tag("+to-do -inbox")',
+               \ 'd':          'show_tag("+deleted -inbox -unread")',
+               \ }
+<
+
+                                               *g:notmuch_date_format*
+
+To configure the date format you want in the search view:
+>
+       let g:notmuch_date_format = '%d.%m.%y'
+<
+
+                                               *g:notmuch_datetime_format*
+
+You can do the same for the thread view:
+>
+       let g:notmuch_datetime_format = '%d.%m.%y %H:%M:%S'
+<
+
+                                               *g:notmuch_folders_count_threads*
+
+If you want to count the threads instead of the messages in the folder view:
+>
+       let g:notmuch_folders_count_threads = 0
+<
+
+                                               *g:notmuch_reader*
+                                               *g:notmuch_sendmail*
+
+You can also configure your externail mail reader and sendemail program:
+>
+       let g:notmuch_reader = 'mutt -f %s'
+       let g:notmuch_sendmail = 'sendmail'
+<
+
+vim:tw=78:ts=8:noet:ft=help:
diff --git a/vim/notmuch.vim b/vim/notmuch.vim
new file mode 100644 (file)
index 0000000..ad8b7c8
--- /dev/null
@@ -0,0 +1,970 @@
+if exists("g:loaded_notmuch")
+       finish
+endif
+
+if !has("ruby") || version < 700
+       finish
+endif
+
+let g:loaded_notmuch = "yep"
+
+let g:notmuch_folders_maps = {
+       \ '<Enter>':    'folders_show_search()',
+       \ 's':          'folders_search_prompt()',
+       \ '=':          'folders_refresh()',
+       \ 'c':          'compose()',
+       \ }
+
+let g:notmuch_search_maps = {
+       \ 'q':          'kill_this_buffer()',
+       \ '<Enter>':    'search_show_thread(1)',
+       \ '<Space>':    'search_show_thread(2)',
+       \ 'A':          'search_tag("-inbox -unread")',
+       \ 'I':          'search_tag("-unread")',
+       \ 't':          'search_tag("")',
+       \ 's':          'search_search_prompt()',
+       \ '=':          'search_refresh()',
+       \ '?':          'search_info()',
+       \ 'c':          'compose()',
+       \ }
+
+let g:notmuch_show_maps = {
+       \ 'q':          'kill_this_buffer()',
+       \ 'A':          'show_tag("-inbox -unread")',
+       \ 'I':          'show_tag("-unread")',
+       \ 't':          'show_tag("")',
+       \ 'o':          'show_open_msg()',
+       \ 'e':          'show_extract_msg()',
+       \ 's':          'show_save_msg()',
+       \ 'p':          'show_save_patches()',
+       \ 'r':          'show_reply()',
+       \ '?':          'show_info()',
+       \ '<Tab>':      'show_next_msg()',
+       \ 'c':          'compose()',
+       \ }
+
+let g:notmuch_compose_maps = {
+       \ ',s':         'compose_send()',
+       \ ',q':         'compose_quit()',
+       \ }
+
+let s:notmuch_folders_default = [
+       \ [ 'new', 'tag:inbox and tag:unread' ],
+       \ [ 'inbox', 'tag:inbox' ],
+       \ [ 'unread', 'tag:unread' ],
+       \ ]
+
+let s:notmuch_date_format_default = '%d.%m.%y'
+let s:notmuch_datetime_format_default = '%d.%m.%y %H:%M:%S'
+let s:notmuch_reader_default = 'mutt -f %s'
+let s:notmuch_sendmail_default = 'sendmail'
+let s:notmuch_folders_count_threads_default = 0
+let s:notmuch_compose_start_insert_default = 1
+
+function! s:new_file_buffer(type, fname)
+       exec printf('edit %s', a:fname)
+       execute printf('set filetype=notmuch-%s', a:type)
+       execute printf('set syntax=notmuch-%s', a:type)
+       ruby $curbuf.init(VIM::evaluate('a:type'))
+endfunction
+
+function! s:on_compose_delete()
+       if b:compose_done
+               return
+       endif
+       if input('[s]end/[q]uit? ') =~ '^s'
+               call s:compose_send()
+       endif
+endfunction
+
+"" actions
+
+function! s:compose_quit()
+       let b:compose_done = 1
+       call s:kill_this_buffer()
+endfunction
+
+function! s:compose_send()
+       let b:compose_done = 1
+       let fname = expand('%')
+       let lines = getline(5, '$')
+
+ruby << EOF
+       # Generate proper mail to send
+       text = VIM::evaluate('lines').join("\n")
+       fname = VIM::evaluate('fname')
+       transport = Mail.new(text)
+       transport.message_id = generate_message_id
+       transport.charset = 'utf-8'
+       File.write(fname, transport.to_s)
+EOF
+
+       let cmdtxt = g:notmuch_sendmail . ' -t -f ' . s:reply_from . ' < ' . fname
+       let out = system(cmdtxt)
+       let err = v:shell_error
+       if err
+               echohl Error
+               echo 'Eeek! unable to send mail'
+               echo out
+               echohl None
+               return
+       endif
+       call delete(fname)
+       echo 'Mail sent successfully.'
+       call s:kill_this_buffer()
+endfunction
+
+function! s:show_next_msg()
+ruby << EOF
+       r, c = $curwin.cursor
+       n = $curbuf.line_number
+       i = $messages.index { |m| n >= m.start && n <= m.end }
+       m = $messages[i + 1]
+       if m
+               r = m.body_start + 1
+               VIM::command("normal #{m.start}zt")
+               $curwin.cursor = r, c
+       end
+EOF
+endfunction
+
+function! s:show_reply()
+       ruby open_reply get_message.mail
+       let b:compose_done = 0
+       call s:set_map(g:notmuch_compose_maps)
+       autocmd BufDelete <buffer> call s:on_compose_delete()
+       if g:notmuch_compose_start_insert
+               startinsert!
+       end
+endfunction
+
+function! s:compose()
+       ruby open_compose
+       let b:compose_done = 0
+       call s:set_map(g:notmuch_compose_maps)
+       autocmd BufDelete <buffer> call s:on_compose_delete()
+       if g:notmuch_compose_start_insert
+               startinsert!
+       end
+endfunction
+
+function! s:show_info()
+       ruby vim_puts get_message.inspect
+endfunction
+
+function! s:show_extract_msg()
+ruby << EOF
+       m = get_message
+       m.mail.attachments.each do |a|
+               File.open(a.filename, 'w') do |f|
+                       f.write a.body.decoded
+                       print "Extracted '#{a.filename}'"
+               end
+       end
+EOF
+endfunction
+
+function! s:show_open_msg()
+ruby << EOF
+       m = get_message
+       mbox = File.expand_path('~/.notmuch/vim_mbox')
+       cmd = VIM::evaluate('g:notmuch_reader') % mbox
+       system "notmuch show --format=mbox id:#{m.message_id} > #{mbox} && #{cmd}"
+EOF
+endfunction
+
+function! s:show_save_msg()
+       let file = input('File name: ')
+ruby << EOF
+       file = VIM::evaluate('file')
+       m = get_message
+       system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
+EOF
+endfunction
+
+function! s:show_save_patches()
+ruby << EOF
+       q = $curbuf.query($cur_thread)
+       t = q.search_threads.first
+       n = 0
+       t.toplevel_messages.first.replies.each do |m|
+               next if not m['subject'] =~ /^\[PATCH.*\]/
+               file = "%04d.patch" % [n += 1]
+               system "notmuch show --format=mbox id:#{m.message_id} > #{file}"
+       end
+       vim_puts "Saved #{n} patches"
+EOF
+endfunction
+
+function! s:show_tag(intags)
+       if empty(a:intags)
+               let tags = input('tags: ')
+       else
+               let tags = a:intags
+       endif
+       ruby do_tag(get_cur_view, VIM::evaluate('l:tags'))
+       call s:show_next_thread()
+endfunction
+
+function! s:search_search_prompt()
+       let text = input('Search: ')
+       if text == ""
+         return
+       endif
+       setlocal modifiable
+ruby << EOF
+       $cur_search = VIM::evaluate('text')
+       $curbuf.reopen
+       search_render($cur_search)
+EOF
+       setlocal nomodifiable
+endfunction
+
+function! s:search_info()
+       ruby vim_puts get_thread_id
+endfunction
+
+function! s:search_refresh()
+       setlocal modifiable
+       ruby $curbuf.reopen
+       ruby search_render($cur_search)
+       setlocal nomodifiable
+endfunction
+
+function! s:search_tag(intags)
+       if empty(a:intags)
+               let tags = input('tags: ')
+       else
+               let tags = a:intags
+       endif
+       ruby do_tag(get_thread_id, VIM::evaluate('l:tags'))
+       norm j
+endfunction
+
+function! s:folders_search_prompt()
+       let text = input('Search: ')
+       call s:search(text)
+endfunction
+
+function! s:folders_refresh()
+       setlocal modifiable
+       ruby $curbuf.reopen
+       ruby folders_render()
+       setlocal nomodifiable
+endfunction
+
+"" basic
+
+function! s:show_cursor_moved()
+ruby << EOF
+       if $render.is_ready?
+               VIM::command('setlocal modifiable')
+               $render.do_next
+               VIM::command('setlocal nomodifiable')
+       end
+EOF
+endfunction
+
+function! s:show_next_thread()
+       call s:kill_this_buffer()
+       if line('.') != line('$')
+               norm j
+               call s:search_show_thread(0)
+       else
+               echo 'No more messages.'
+       endif
+endfunction
+
+function! s:kill_this_buffer()
+ruby << EOF
+       $curbuf.close
+       VIM::command("bdelete!")
+EOF
+endfunction
+
+function! s:set_map(maps)
+       nmapclear <buffer>
+       for [key, code] in items(a:maps)
+               let cmd = printf(":call <SID>%s<CR>", code)
+               exec printf('nnoremap <buffer> %s %s', key, cmd)
+       endfor
+endfunction
+
+function! s:new_buffer(type)
+       enew
+       setlocal buftype=nofile bufhidden=hide
+       keepjumps 0d
+       execute printf('set filetype=notmuch-%s', a:type)
+       execute printf('set syntax=notmuch-%s', a:type)
+       ruby $curbuf.init(VIM::evaluate('a:type'))
+endfunction
+
+function! s:set_menu_buffer()
+       setlocal nomodifiable
+       setlocal cursorline
+       setlocal nowrap
+endfunction
+
+"" main
+
+function! s:show(thread_id)
+       call s:new_buffer('show')
+       setlocal modifiable
+ruby << EOF
+       thread_id = VIM::evaluate('a:thread_id')
+       $cur_thread = thread_id
+       $messages.clear
+       $curbuf.render do |b|
+               q = $curbuf.query(get_cur_view)
+               q.sort = Notmuch::SORT_OLDEST_FIRST
+               msgs = q.search_messages
+               msgs.each do |msg|
+                       m = Mail.read(msg.filename)
+                       part = m.find_first_text
+                       nm_m = Message.new(msg, m)
+                       $messages << nm_m
+                       date_fmt = VIM::evaluate('g:notmuch_datetime_format')
+                       date = Time.at(msg.date).strftime(date_fmt)
+                       nm_m.start = b.count
+                       b << "%s %s (%s)" % [msg['from'], date, msg.tags]
+                       b << "Subject: %s" % [msg['subject']]
+                       b << "To: %s" % msg['to']
+                       b << "Cc: %s" % msg['cc']
+                       b << "Date: %s" % msg['date']
+                       nm_m.body_start = b.count
+                       b << "--- %s ---" % part.mime_type
+                       part.convert.each_line do |l|
+                               b << l.chomp
+                       end
+                       b << ""
+                       nm_m.end = b.count
+               end
+               b.delete(b.count)
+       end
+       $messages.each_with_index do |msg, i|
+               VIM::command("syntax region nmShowMsg#{i}Desc start='\\%%%il' end='\\%%%il' contains=@nmShowMsgDesc" % [msg.start, msg.start + 1])
+               VIM::command("syntax region nmShowMsg#{i}Head start='\\%%%il' end='\\%%%il' contains=@nmShowMsgHead" % [msg.start + 1, msg.body_start])
+               VIM::command("syntax region nmShowMsg#{i}Body start='\\%%%il' end='\\%%%dl' contains=@nmShowMsgBody" % [msg.body_start, msg.end])
+       end
+EOF
+       setlocal nomodifiable
+       call s:set_map(g:notmuch_show_maps)
+endfunction
+
+function! s:search_show_thread(mode)
+ruby << EOF
+       mode = VIM::evaluate('a:mode')
+       id = get_thread_id
+       case mode
+       when 0;
+       when 1; $cur_filter = nil
+       when 2; $cur_filter = $cur_search
+       end
+       VIM::command("call s:show('#{id}')")
+EOF
+endfunction
+
+function! s:search(search)
+       call s:new_buffer('search')
+ruby << EOF
+       $cur_search = VIM::evaluate('a:search')
+       search_render($cur_search)
+EOF
+       call s:set_menu_buffer()
+       call s:set_map(g:notmuch_search_maps)
+       autocmd CursorMoved <buffer> call s:show_cursor_moved()
+endfunction
+
+function! s:folders_show_search()
+ruby << EOF
+       n = $curbuf.line_number
+       s = $searches[n - 1]
+       VIM::command("call s:search('#{s}')")
+EOF
+endfunction
+
+function! s:folders()
+       call s:new_buffer('folders')
+       ruby folders_render()
+       call s:set_menu_buffer()
+       call s:set_map(g:notmuch_folders_maps)
+endfunction
+
+"" root
+
+function! s:set_defaults()
+       if !exists('g:notmuch_date_format')
+               if exists('g:notmuch_rb_date_format')
+                       let g:notmuch_date_format = g:notmuch_rb_date_format
+               else
+                       let g:notmuch_date_format = s:notmuch_date_format_default
+               endif
+       endif
+
+       if !exists('g:notmuch_datetime_format')
+               if exists('g:notmuch_rb_datetime_format')
+                       let g:notmuch_datetime_format = g:notmuch_rb_datetime_format
+               else
+                       let g:notmuch_datetime_format = s:notmuch_datetime_format_default
+               endif
+       endif
+
+       if !exists('g:notmuch_reader')
+               if exists('g:notmuch_rb_reader')
+                       let g:notmuch_reader = g:notmuch_rb_reader
+               else
+                       let g:notmuch_reader = s:notmuch_reader_default
+               endif
+       endif
+
+       if !exists('g:notmuch_sendmail')
+               if exists('g:notmuch_rb_sendmail')
+                       let g:notmuch_sendmail = g:notmuch_rb_sendmail
+               else
+                       let g:notmuch_sendmail = s:notmuch_sendmail_default
+               endif
+       endif
+
+       if !exists('g:notmuch_folders_count_threads')
+               if exists('g:notmuch_rb_count_threads')
+                       let g:notmuch_count_threads = g:notmuch_rb_count_threads
+               else
+                       let g:notmuch_folders_count_threads = s:notmuch_folders_count_threads_default
+               endif
+       endif
+
+       if !exists('g:notmuch_compose_start_insert')
+               let g:notmuch_compose_start_insert = s:notmuch_compose_start_insert_default
+       endif
+
+       if !exists('g:notmuch_custom_search_maps') && exists('g:notmuch_rb_custom_search_maps')
+               let g:notmuch_custom_search_maps = g:notmuch_rb_custom_search_maps
+       endif
+
+       if !exists('g:notmuch_custom_show_maps') && exists('g:notmuch_rb_custom_show_maps')
+               let g:notmuch_custom_show_maps = g:notmuch_rb_custom_show_maps
+       endif
+
+       if exists('g:notmuch_custom_search_maps')
+               call extend(g:notmuch_search_maps, g:notmuch_custom_search_maps)
+       endif
+
+       if exists('g:notmuch_custom_show_maps')
+               call extend(g:notmuch_show_maps, g:notmuch_custom_show_maps)
+       endif
+
+       if !exists('g:notmuch_folders')
+               if exists('g:notmuch_rb_folders')
+                       let g:notmuch_folders = g:notmuch_rb_folders
+               else
+                       let g:notmuch_folders = s:notmuch_folders_default
+               endif
+       endif
+endfunction
+
+function! s:NotMuch(...)
+       call s:set_defaults()
+
+ruby << EOF
+       require 'notmuch'
+       require 'rubygems'
+       require 'tempfile'
+       require 'socket'
+       begin
+               require 'mail'
+       rescue LoadError
+       end
+
+       $db_name = nil
+       $email = $email_name = $email_address = nil
+       $exclude_tags = []
+       $searches = []
+       $threads = []
+       $messages = []
+       $mail_installed = defined?(Mail)
+
+       def get_config_item(item)
+               result = ''
+               IO.popen(['notmuch', 'config', 'get', item]) { |out|
+                       result = out.read
+               }
+               return result.rstrip
+       end
+
+       def get_config
+               $db_name = get_config_item('database.path')
+               $email_name = get_config_item('user.name')
+               $email_address = get_config_item('user.primary_email')
+               $email_name = get_config_item('user.name')
+               $email = "%s <%s>" % [$email_name, $email_address]
+               ignore_tags = get_config_item('search.exclude_tags')
+               $exclude_tags = ignore_tags.split("\n")
+       end
+
+       def vim_puts(s)
+               VIM::command("echo '#{s.to_s}'")
+       end
+
+       def vim_p(s)
+               VIM::command("echo '#{s.inspect}'")
+       end
+
+       def author_filter(a)
+               # TODO email format, aliases
+               a.strip!
+               a.gsub!(/[\.@].*/, '')
+               a.gsub!(/^ext /, '')
+               a.gsub!(/ \(.*\)/, '')
+               a
+       end
+
+       def get_thread_id
+               n = $curbuf.line_number - 1
+               return "thread:%s" % $threads[n]
+       end
+
+       def get_message
+               n = $curbuf.line_number
+               return $messages.find { |m| n >= m.start && n <= m.end }
+       end
+
+       def get_cur_view
+               if $cur_filter
+                       return "#{$cur_thread} and (#{$cur_filter})"
+               else
+                       return $cur_thread
+               end
+       end
+
+       def generate_message_id
+               t = Time.now
+               random_tag = sprintf('%x%x_%x%x%x',
+                       t.to_i, t.tv_usec,
+                       $$, Thread.current.object_id.abs, rand(255))
+               return "<#{random_tag}@#{Socket.gethostname}.notmuch>"
+       end
+
+       def open_compose_helper(lines, cur)
+               help_lines = [
+                       'Notmuch-Help: Type in your message here; to help you use these bindings:',
+                       'Notmuch-Help:   ,s    - send the message (Notmuch-Help lines will be removed)',
+                       'Notmuch-Help:   ,q    - abort the message',
+                       ]
+
+               dir = File.expand_path('~/.notmuch/compose')
+               FileUtils.mkdir_p(dir)
+               Tempfile.open(['nm-', '.mail'], dir) do |f|
+                       f.puts(help_lines)
+                       f.puts
+                       f.puts(lines)
+
+                       sig_file = File.expand_path('~/.signature')
+                       if File.exists?(sig_file)
+                               f.puts("-- ")
+                               f.write(File.read(sig_file))
+                       end
+
+                       f.flush
+
+                       cur += help_lines.size + 1
+
+                       VIM::command("let s:reply_from='%s'" % $email_address)
+                       VIM::command("call s:new_file_buffer('compose', '#{f.path}')")
+                       VIM::command("call cursor(#{cur}, 0)")
+               end
+       end
+
+       def open_reply(orig)
+               reply = orig.reply do |m|
+                       # fix headers
+                       if not m[:reply_to]
+                               m.to = [orig[:from].to_s, orig[:to].to_s]
+                       end
+                       m.cc = orig[:cc]
+                       m.from = $email
+                       m.charset = 'utf-8'
+               end
+
+               lines = []
+
+               body_lines = []
+               if $mail_installed
+                       addr = Mail::Address.new(orig[:from].value)
+                       name = addr.name
+                       name = addr.local + "@" if name.nil? && !addr.local.nil?
+               else
+                       name = orig[:from]
+               end
+               name = "somebody" if name.nil?
+
+               body_lines << "%s wrote:" % name
+               part = orig.find_first_text
+               part.convert.each_line do |l|
+                       body_lines << "> %s" % l.chomp
+               end
+               body_lines << ""
+               body_lines << ""
+               body_lines << ""
+
+               reply.body = body_lines.join("\n")
+
+               lines += reply.present.lines.map { |e| e.chomp }
+               lines << ""
+
+               cur = lines.count - 1
+
+               open_compose_helper(lines, cur)
+       end
+
+       def open_compose()
+               lines = []
+
+               lines << "From: #{$email}"
+               lines << "To: "
+               cur = lines.count
+
+               lines << "Cc: "
+               lines << "Bcc: "
+               lines << "Subject: "
+               lines << ""
+               lines << ""
+               lines << ""
+
+               open_compose_helper(lines, cur)
+       end
+
+       def folders_render()
+               $curbuf.render do |b|
+                       folders = VIM::evaluate('g:notmuch_folders')
+                       count_threads = VIM::evaluate('g:notmuch_folders_count_threads') == 1
+                       $searches.clear
+                       folders.each do |name, search|
+                               q = $curbuf.query(search)
+                               $exclude_tags.each { |t|
+                                       q.add_tag_exclude(t)
+                               }
+                               $searches << search
+                               count = count_threads ? q.count_threads : q.count_messages
+                               b << "%9d %-20s (%s)" % [count, name, search]
+                       end
+               end
+       end
+
+       def search_render(search)
+               date_fmt = VIM::evaluate('g:notmuch_date_format')
+               q = $curbuf.query(search)
+               q.sort = Notmuch::SORT_NEWEST_FIRST
+               $exclude_tags.each { |t|
+                       q.add_tag_exclude(t)
+               }
+               $threads.clear
+               t = q.search_threads
+
+               $render = $curbuf.render_staged(t) do |b, items|
+                       items.each do |e|
+                               authors = e.authors.to_utf8.split(/[,|]/).map { |a| author_filter(a) }.join(",")
+                               date = Time.at(e.newest_date).strftime(date_fmt)
+                               subject = e.messages.first['subject']
+                               if $mail_installed
+                                       subject = Mail::Field.new("Subject: " + subject).to_s
+                               else
+                                       subject = subject.force_encoding('utf-8')
+                               end
+                               b << "%-12s %3s %-20.20s | %s (%s)" % [date, e.matched_messages, authors, subject, e.tags]
+                               $threads << e.thread_id
+                       end
+               end
+       end
+
+       def do_tag(filter, tags)
+               $curbuf.do_write do |db|
+                       q = db.query(filter)
+                       q.search_messages.each do |e|
+                               e.freeze
+                               tags.split.each do |t|
+                                       case t
+                                       when /^-(.*)/
+                                               e.remove_tag($1)
+                                       when /^\+(.*)/
+                                               e.add_tag($1)
+                                       when /^([^\+^-].*)/
+                                               e.add_tag($1)
+                                       end
+                               end
+                               e.thaw
+                               e.tags_to_maildir_flags
+                       end
+                       q.destroy!
+               end
+       end
+
+       module DbHelper
+               def init(name)
+                       @name = name
+                       @db = Notmuch::Database.new($db_name)
+                       @queries = []
+               end
+
+               def query(*args)
+                       q = @db.query(*args)
+                       @queries << q
+                       q
+               end
+
+               def close
+                       @queries.delete_if { |q| ! q.destroy! }
+                       @db.close
+               end
+
+               def reopen
+                       close if @db
+                       @db = Notmuch::Database.new($db_name)
+               end
+
+               def do_write
+                       db = Notmuch::Database.new($db_name, :mode => Notmuch::MODE_READ_WRITE)
+                       begin
+                               yield db
+                       ensure
+                               db.close
+                       end
+               end
+       end
+
+       class Message
+               attr_accessor :start, :body_start, :end
+               attr_reader :message_id, :filename, :mail
+
+               def initialize(msg, mail)
+                       @message_id = msg.message_id
+                       @filename = msg.filename
+                       @mail = mail
+                       @start = 0
+                       @end = 0
+                       mail.import_headers(msg) if not $mail_installed
+               end
+
+               def to_s
+                       "id:%s" % @message_id
+               end
+
+               def inspect
+                       "id:%s, file:%s" % [@message_id, @filename]
+               end
+       end
+
+       class StagedRender
+               def initialize(buffer, enumerable, block)
+                       @b = buffer
+                       @enumerable = enumerable
+                       @block = block
+                       @last_render = 0
+
+                       @b.render { do_next }
+               end
+
+               def is_ready?
+                       @last_render - @b.line_number <= $curwin.height
+               end
+
+               def do_next
+                       items = @enumerable.take($curwin.height * 2)
+                       return if items.empty?
+                       @block.call @b, items
+                       @last_render = @b.count
+               end
+       end
+
+       class VIM::Buffer
+               include DbHelper
+
+               def <<(a)
+                       append(count(), a)
+               end
+
+               def render_staged(enumerable, &block)
+                       StagedRender.new(self, enumerable, block)
+               end
+
+               def render
+                       old_count = count
+                       yield self
+                       (1..old_count).each do
+                               delete(1)
+                       end
+               end
+       end
+
+       class Notmuch::Tags
+               def to_s
+                       to_a.join(" ")
+               end
+       end
+
+       class Notmuch::Message
+               def to_s
+                       "id:%s" % message_id
+               end
+       end
+
+       # workaround for bug in vim's ruby
+       class Object
+               def flush
+               end
+       end
+
+       module SimpleMessage
+               class Header < Array
+                       def self.parse(string)
+                               return nil if string.empty?
+                               return Header.new(string.split(/,\s+/))
+                       end
+
+                       def to_s
+                               self.join(', ')
+                       end
+               end
+
+               def initialize(string = nil)
+                       @raw_source = string
+                       @body = nil
+                       @headers = {}
+
+                       return if not string
+
+                       if string =~ /(.*?(\r\n|\n))\2/m
+                               head, body = $1, $' || '', $2
+                       else
+                               head, body = string, ''
+                       end
+                       @body = body
+               end
+
+               def [](name)
+                       @headers[name.to_sym]
+               end
+
+               def []=(name, value)
+                       @headers[name.to_sym] = value
+               end
+
+               def format_header(value)
+                       value.to_s.tr('_', '-').gsub(/(\w+)/) { $1.capitalize }
+               end
+
+               def to_s
+                       buffer = ''
+                       @headers.each do |key, value|
+                               buffer << "%s: %s\r\n" %
+                                       [format_header(key), value]
+                       end
+                       buffer << "\r\n"
+                       buffer << @body
+                       buffer
+               end
+
+               def body=(value)
+                       @body = value
+               end
+
+               def from
+                       @headers[:from]
+               end
+
+               def decoded
+                       @body
+               end
+
+               def mime_type
+                       'text/plain'
+               end
+
+               def multipart?
+                       false
+               end
+
+               def reply
+                       r = Mail::Message.new
+                       r[:from] = self[:to]
+                       r[:to] = self[:from]
+                       r[:cc] = self[:cc]
+                       r[:in_reply_to] = self[:message_id]
+                       r[:references] = self[:references]
+                       r
+               end
+
+               HEADERS = [ :from, :to, :cc, :references, :in_reply_to, :reply_to, :message_id ]
+
+               def import_headers(m)
+                       HEADERS.each do |e|
+                               dashed = format_header(e)
+                               @headers[e] = Header.parse(m[dashed])
+                       end
+               end
+       end
+
+       module Mail
+
+               if not $mail_installed
+                       puts "WARNING: Install the 'mail' gem, without it support is limited"
+
+                       def self.read(filename)
+                               Message.new(File.open(filename, 'rb') { |f| f.read })
+                       end
+
+                       class Message
+                               include SimpleMessage
+                       end
+               end
+
+               class Message
+
+                       def find_first_text
+                               return self if not multipart?
+                               return text_part || html_part
+                       end
+
+                       def convert
+                               if mime_type != "text/html"
+                                       text = decoded
+                               else
+                                       IO.popen(VIM::evaluate('exists("g:notmuch_html_converter") ? ' +
+                                                       'g:notmuch_html_converter : "elinks --dump"'), "w+") do |pipe|
+                                               pipe.write(decode_body)
+                                               pipe.close_write
+                                               text = pipe.read
+                                       end
+                               end
+                               text
+                       end
+
+                       def present
+                               buffer = ''
+                               header.fields.each do |f|
+                                       buffer << "%s: %s\r\n" % [f.name, f.to_s]
+                               end
+                               buffer << "\r\n"
+                               buffer << body.to_s
+                               buffer
+                       end
+               end
+       end
+
+       class String
+               def to_utf8
+                       RUBY_VERSION >= "1.9" ? force_encoding('utf-8') : self
+               end
+       end
+
+       get_config
+EOF
+       if a:0
+         call s:search(join(a:000))
+       else
+         call s:folders()
+       endif
+endfunction
+
+command -nargs=* NotMuch call s:NotMuch(<f-args>)
+
+" vim: set noexpandtab:
diff --git a/vim/notmuch.yaml b/vim/notmuch.yaml
new file mode 100644 (file)
index 0000000..6f3b705
--- /dev/null
@@ -0,0 +1,10 @@
+addon: notmuch
+description: "notmuch mail user interface"
+files:
+  - plugin/notmuch.vim
+  - doc/notmuch.txt
+  - syntax/notmuch-compose.vim
+  - syntax/notmuch-folders.vim
+  - syntax/notmuch-git-diff.vim
+  - syntax/notmuch-search.vim
+  - syntax/notmuch-show.vim
diff --git a/vim/syntax/notmuch-compose.vim b/vim/syntax/notmuch-compose.vim
new file mode 100644 (file)
index 0000000..19adb75
--- /dev/null
@@ -0,0 +1,7 @@
+runtime! syntax/mail.vim
+
+syntax region nmComposeHelp          contains=nmComposeHelpLine start='^Notmuch-Help:\%1l' end='^\(Notmuch-Help:\)\@!'
+syntax match  nmComposeHelpLine      /Notmuch-Help:/ contained
+
+highlight link nmComposeHelp        Include
+highlight link nmComposeHelpLine    Error
diff --git a/vim/syntax/notmuch-folders.vim b/vim/syntax/notmuch-folders.vim
new file mode 100644 (file)
index 0000000..9477f86
--- /dev/null
@@ -0,0 +1,12 @@
+" notmuch folders mode syntax file
+
+syntax region nmFoldersCount     start='^' end='\%10v'
+syntax region nmFoldersName      start='\%11v' end='\%31v'
+syntax match  nmFoldersSearch    /([^()]\+)$/
+
+highlight link nmFoldersCount     Statement
+highlight link nmFoldersName      Type
+highlight link nmFoldersSearch    String
+
+highlight CursorLine term=reverse cterm=reverse gui=reverse
+
diff --git a/vim/syntax/notmuch-git-diff.vim b/vim/syntax/notmuch-git-diff.vim
new file mode 100644 (file)
index 0000000..6f15fdc
--- /dev/null
@@ -0,0 +1,26 @@
+syn match diffRemoved  "^-.*"
+syn match diffAdded    "^+.*"
+
+syn match diffSeparator        "^---$"
+syn match diffSubname  " @@..*"ms=s+3 contained
+syn match diffLine     "^@.*" contains=diffSubname
+
+syn match diffFile     "^diff .*"
+syn match diffNewFile  "^+++ .*"
+syn match diffOldFile  "^--- .*"
+
+hi def link diffOldFile                diffFile
+hi def link diffNewFile                diffFile
+
+hi def link diffFile           Type
+hi def link diffRemoved                Special
+hi def link diffAdded          Identifier
+hi def link diffLine           Statement
+hi def link diffSubname                PreProc
+
+syntax match gitDiffStatLine /^ .\{-}\zs[+-]\+$/ contains=gitDiffStatAdd,gitDiffStatDelete
+syntax match gitDiffStatAdd    /+/ contained
+syntax match gitDiffStatDelete /-/ contained
+
+hi def link gitDiffStatAdd diffAdded
+hi def link gitDiffStatDelete diffRemoved
diff --git a/vim/syntax/notmuch-search.vim b/vim/syntax/notmuch-search.vim
new file mode 100644 (file)
index 0000000..f458d77
--- /dev/null
@@ -0,0 +1,12 @@
+syntax region nmSearch         start=/^/ end=/$/               oneline contains=nmSearchDate
+syntax match nmSearchDate      /^.\{-13}/                      contained nextgroup=nmSearchNum
+syntax match nmSearchNum       /.\{-4}/                        contained nextgroup=nmSearchFrom
+syntax match nmSearchFrom      /.\{-21}/                       contained nextgroup=nmSearchSubject
+syntax match nmSearchSubject   /.\{0,}\(([^()]\+)$\)\@=/       contained nextgroup=nmSearchTags
+syntax match nmSearchTags      /.\+$/                          contained
+
+highlight link nmSearchDate    Statement
+highlight link nmSearchNum     Type
+highlight link nmSearchFrom    Include
+highlight link nmSearchSubject Normal
+highlight link nmSearchTags    String
diff --git a/vim/syntax/notmuch-show.vim b/vim/syntax/notmuch-show.vim
new file mode 100644 (file)
index 0000000..c3a98b7
--- /dev/null
@@ -0,0 +1,24 @@
+" notmuch show mode syntax file
+
+syntax cluster nmShowMsgDesc contains=nmShowMsgDescWho,nmShowMsgDescDate,nmShowMsgDescTags
+syntax match   nmShowMsgDescWho /[^)]\+)/ contained
+syntax match   nmShowMsgDescDate / ([^)]\+[0-9]) / contained
+syntax match   nmShowMsgDescTags /([^)]\+)$/ contained
+
+syntax cluster nmShowMsgHead contains=nmShowMsgHeadKey,nmShowMsgHeadVal
+syntax match   nmShowMsgHeadKey /^[^:]\+: / contained
+syntax match   nmShowMsgHeadVal /^\([^:]\+: \)\@<=.*/ contained
+
+syntax cluster nmShowMsgBody contains=@nmShowMsgBodyMail,@nmShowMsgBodyGit
+syntax include @nmShowMsgBodyMail syntax/mail.vim
+
+silent! syntax include @nmShowMsgBodyGit syntax/notmuch-git-diff.vim
+
+highlight nmShowMsgDescWho term=reverse cterm=reverse gui=reverse
+highlight link nmShowMsgDescDate Type
+highlight link nmShowMsgDescTags String
+
+highlight link nmShowMsgHeadKey  Macro
+"highlight link nmShowMsgHeadVal  NONE
+
+highlight Folded term=reverse ctermfg=LightGrey ctermbg=Black guifg=LightGray guibg=Black