diff options
| author | David Bremner <david@tethera.net> | 2018-10-20 14:24:33 -0300 |
|---|---|---|
| committer | David Bremner <david@tethera.net> | 2018-10-20 14:24:33 -0300 |
| commit | c201ee2193f4868fca082d3fe094a8ad04ad58cc (patch) | |
| tree | ff64dc83845a7b73c7d61d15353c9173857202c6 | |
| parent | 0ff3786ac03684245488f09d59be98325877656b (diff) | |
| parent | 175f80c4c1826b7a77417bfbc804348988eb85d3 (diff) | |
Merge tag 'debian/0.28-2' into debian/stretch-backports
notmuch release 0.28-2 for unstable (sid) [dgit]
[dgit distro=debian]
64 files changed, 1285 insertions, 197 deletions
@@ -1,3 +1,66 @@ +Notmuch 0.28 (2018-10-12) +========================= + +General +------- + +Improve threading + + 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. + +Handle mislabelled Windows-1252 parts + + Messages that contain Windows-1252 are apparently frequently + mislabelled as ISO 8859-1. Use GMime functionality to apply the + correct encoding for such messages. + +Command Line Interface +---------------------- + +Support relative database paths + + Database paths (i.e. parameters to `notmuch config set + database.path`) without a leading `/` are now interpreted relative + to $HOME of the invoking user. + +Emacs +----- + +Improve stderr handling + + Add a real sentinel process to clean up stderr buffer. This is + needed on e.g. macOS. + +Call `notmuch-mua-send-hook` hooks when sending a message + + This hook was documented, but not functional for a very long time. + +Completion +---------- + +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) ========================= @@ -41,7 +104,7 @@ Notmuch 0.26.2 (2018-04-28) Library Changes --------------- -Work around Xapian bug with `get_mset(0,0, x)`. +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 @@ -178,11 +241,11 @@ Change of return value of `notmuch_thread_get_authors` 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. + 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 ---------- diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index d242097a..de0fb415 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -482,7 +482,7 @@ class Message(Python3StringMixIn): if status != 0: raise NotmuchError(status) - return value.value.decode('utf-8') + 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 diff --git a/bindings/python/notmuch/version.py b/bindings/python/notmuch/version.py index fbb282e3..6a513a25 100644 --- a/bindings/python/notmuch/version.py +++ b/bindings/python/notmuch/version.py @@ -1,3 +1,3 @@ # this file should be kept in sync with ../../../version -__VERSION__ = '0.27' +__VERSION__ = '0.28' SOVERSION = '5' diff --git a/completion/Makefile.local b/completion/Makefile.local index dfc12713..8e86c9d2 100644 --- a/completion/Makefile.local +++ b/completion/Makefile.local @@ -6,7 +6,7 @@ dir := completion # 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_script := $(srcdir)/$(dir)/notmuch-completion.zsh +zsh_scripts := $(srcdir)/$(dir)/zsh/_notmuch $(srcdir)/$(dir)/zsh/_email-notmuch install: install-$(dir) @@ -18,5 +18,5 @@ ifeq ($(WITH_BASH),1) endif ifeq ($(WITH_ZSH),1) mkdir -p "$(DESTDIR)$(zsh_completion_dir)" - install -m0644 $(zsh_script) "$(DESTDIR)$(zsh_completion_dir)/_notmuch" + install -m0644 $(zsh_scripts) "$(DESTDIR)$(zsh_completion_dir)" endif diff --git a/completion/README b/completion/README index 89805c72..900e1c98 100644 --- a/completion/README +++ b/completion/README @@ -11,6 +11,6 @@ notmuch-completion.bash [1] https://github.com/scop/bash-completion -notmuch-completion.zsh +zsh - Command-line completion for the zsh shell. + Command-line completions for the zsh shell. diff --git a/completion/notmuch-completion.zsh b/completion/notmuch-completion.zsh deleted file mode 100644 index 208a5503..00000000 --- a/completion/notmuch-completion.zsh +++ /dev/null @@ -1,88 +0,0 @@ -#compdef notmuch - -# ZSH completion for `notmuch` -# Copyright © 2009 Ingmar Vanhassel <ingmar@exherbo.org> - -_notmuch_commands() -{ - 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' - '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' - ) - - _describe -t command 'command' notmuch_commands -} - -_notmuch_dump() -{ - _files -} - -_notmuch_help_topics() -{ - local -a notmuch_help_topics - notmuch_help_topics=( - 'search-terms:show common search-terms syntax' - ) - _describe -t notmuch-help-topics 'topic' notmuch_help_topics -} - -_notmuch_help() -{ - _alternative \ - _notmuch_commands \ - _notmuch_help_topics -} - -_notmuch_restore() -{ - _files -} - -_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))' -} - -_notmuch_address() -{ - _arguments -s : \ - '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \ - '--output=[select what to output]:output:((sender recipients count))' -} - -_notmuch() -{ - if (( CURRENT > 2 )) ; then - local cmd=${words[2]} - curcontext="${curcontext%:*:*}:notmuch-$cmd" - (( CURRENT-- )) - shift words - _call_function ret _notmuch_$cmd - return ret - else - _notmuch_commands - fi -} - -_notmuch "$@" - -# vim: set sw=2 sts=2 ts=2 et ft=zsh : diff --git a/completion/zsh/_email-notmuch b/completion/zsh/_email-notmuch new file mode 100644 index 00000000..1cd0d78f --- /dev/null +++ b/completion/zsh/_email-notmuch @@ -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 index 00000000..e920f10b --- /dev/null +++ b/completion/zsh/_notmuch @@ -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 "$@" @@ -53,6 +53,8 @@ fi # Set several defaults (optionally specified by the user in # environment variables) +BASH=${BASH:-bash} +PERL=${PERL:-perl} CC=${CC:-cc} CXX=${CXX:-c++} CFLAGS=${CFLAGS:--g -O2} @@ -557,6 +559,26 @@ else errors=$((errors + 1)) fi +printf "Checking for bash... " +if command -v ${BASH} > /dev/null; then + have_bash=1 + bash_absolute=$(command -v ${BASH}) + printf "Yes (%s).\n" "$bash_absolute" +else + have_bash=0 + printf "No. (%s not found)\n" "${BASH}" +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 @@ -1081,6 +1103,14 @@ emacslispdir=${EMACSLISPDIR} # 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} @@ -1262,6 +1292,14 @@ 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} diff --git a/contrib/notmuch-mutt/Makefile b/contrib/notmuch-mutt/Makefile index 87f9031c..855438be 100644 --- a/contrib/notmuch-mutt/Makefile +++ b/contrib/notmuch-mutt/Makefile @@ -1,5 +1,11 @@ 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) @@ -8,5 +14,12 @@ $(NAME).1: $(NAME) 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/debian/changelog b/debian/changelog index 64ddeb4f..c1a2e530 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,37 @@ +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~bpo9+2) stretch-backports; urgency=medium * Disable test T460-emacs-tree on arm64. The failures seems to be an upstream diff --git a/debian/compat b/debian/compat index f599e28b..b4de3947 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -10 +11 diff --git a/debian/control b/debian/control index 79073265..e3e7ef70 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,7 @@ Uploaders: Build-Conflicts: ruby1.8, gdb-minimal, gdb [ia64 mips mips64el] Build-Depends: dpkg-dev (>= 1.17.14), - debhelper (>= 10~), + debhelper (>= 11~), pkg-config, libxapian-dev, libgmime-3.0-dev (>= 3.0.3~) | libgmime-2.6-dev (>= 2.6.7~), @@ -21,17 +21,18 @@ Build-Depends: 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~) | - emacs23-nox | emacs23 (>=23~) | emacs23-lucid (>=23~), + 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~) + bash-completion (>=1.9.0~), + texinfo Standards-Version: 4.1.3 Homepage: https://notmuchmail.org/ -Vcs-Git: git://notmuchmail.org/git/notmuch -b debian/stretch-backports +Vcs-Git: https://git.notmuchmail.org/git/notmuch -b release Vcs-Browser: https://git.notmuchmail.org/git/notmuch Package: notmuch diff --git a/debian/notmuch-mutt.install b/debian/notmuch-mutt.install index 9141c26f..9b468bdb 100644 --- a/debian/notmuch-mutt.install +++ b/debian/notmuch-mutt.install @@ -1,2 +1,2 @@ -contrib/notmuch-mutt/notmuch-mutt usr/bin -contrib/notmuch-mutt/notmuch-mutt.rc etc/Muttrc.d +usr/bin/notmuch-mutt +etc/Muttrc.d/notmuch-mutt.rc diff --git a/debian/notmuch-mutt.manpages b/debian/notmuch-mutt.manpages index 3f6b8abd..a14ed69b 100644 --- a/debian/notmuch-mutt.manpages +++ b/debian/notmuch-mutt.manpages @@ -1 +1 @@ -contrib/notmuch-mutt/notmuch-mutt.1 +usr/share/man/man1/notmuch-mutt.1 diff --git a/debian/notmuch.examples b/debian/notmuch.examples deleted file mode 100644 index 524e0f4b..00000000 --- a/debian/notmuch.examples +++ /dev/null @@ -1 +0,0 @@ -completion/notmuch-completion.zsh diff --git a/debian/notmuch.install b/debian/notmuch.install index 31b9a37e..2514ca6c 100644 --- a/debian/notmuch.install +++ b/debian/notmuch.install @@ -1,3 +1,4 @@ -usr/bin -usr/share/man +usr/bin/notmuch +usr/bin/notmuch-emacs-mua usr/share/bash-completion +usr/share/zsh/vendor-completions diff --git a/debian/notmuch.manpages b/debian/notmuch.manpages new file mode 100644 index 00000000..f9fcb54a --- /dev/null +++ b/debian/notmuch.manpages @@ -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/rules b/debian/rules index abd5d8cf..4979ec3f 100755 --- a/debian/rules +++ b/debian/rules @@ -11,12 +11,13 @@ endif dh $@ --with python2,python3,elpa override_dh_auto_configure: - ./configure --prefix=/usr \ + BASH=/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: @@ -36,4 +37,5 @@ 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/options b/debian/source/options index 7e95ec71..cc76e919 100644 --- a/debian/source/options +++ b/debian/source/options @@ -1,3 +1,3 @@ single-debian-patch -tar-ignore +tar-ignore=.git tar-ignore=performance-test/download/*.tar.xz diff --git a/devel/nmbug/notmuch-report b/devel/nmbug/notmuch-report index 5789c5f4..eaceb2ce 100755 --- a/devel/nmbug/notmuch-report +++ b/devel/nmbug/notmuch-report @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # # Copyright (c) 2011-2012 David Bremner <david@tethera.net> # diff --git a/devel/printmimestructure b/devel/printmimestructure index 34d12930..70e0a5c0 100755 --- a/devel/printmimestructure +++ b/devel/printmimestructure @@ -19,10 +19,12 @@ # 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 test(z, prefix=''): +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') @@ -33,8 +35,23 @@ def test(z, prefix=''): 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 prefix + '┬╴' + z.get_content_type() + cset + disposition + fname, z.as_string().__len__().__str__() + ' bytes' + print_part(z, prefix+'┬╴') if prefix.endswith('└'): prefix = prefix.rpartition('└')[0] + ' ' if prefix.endswith('├'): @@ -47,6 +64,6 @@ def test(z, prefix=''): test(parts[i], prefix + '└') # FIXME: show epilogue? else: - print prefix + '─╴'+ z.get_content_type() + cset + disposition + fname, z.get_payload().__len__().__str__(), 'bytes' + print_part(z, prefix+'─╴') test(email.message_from_file(sys.stdin), '└') diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst index c00d7d74..12d86e89 100644 --- a/doc/man1/notmuch-address.rst +++ b/doc/man1/notmuch-address.rst @@ -32,8 +32,8 @@ Supported options for **address** include ``--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. + neither ``--output=sender`` nor ``--output=recipients`` is + given, ``--output=sender`` is implied. **sender** Output all addresses from the *From* header. @@ -63,19 +63,19 @@ Supported options for **address** include **no** Output all occurrences of addresses in the matching - messages. This is not applicable with --output=count. + 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 + 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 + matching messages. If ``--output=count`` is specified, include all variants in the count. ``--sort=``\ (**newest-first**\ \|\ **oldest-first**) @@ -86,7 +86,7 @@ Supported options for **address** include 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 + However, if either ``--output=count`` or ``--deduplicate=address`` is specified, this option is ignored and the order of the results is unspecified. diff --git a/doc/man1/notmuch-dump.rst b/doc/man1/notmuch-dump.rst index f8ec4868..ec6335b2 100644 --- a/doc/man1/notmuch-dump.rst +++ b/doc/man1/notmuch-dump.rst @@ -21,7 +21,7 @@ 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 database will be generated. A ``--`` argument instructs notmuch that the remaining arguments are search terms. Supported options for **dump** include diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst index c893ba04..5c64c4a6 100644 --- a/doc/man1/notmuch-reply.rst +++ b/doc/man1/notmuch-reply.rst @@ -75,7 +75,7 @@ Supported options for **reply** include 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 + with ``--format=json`` and ``--format=sexp``), and on successful decryption the multipart/encrypted part will be replaced by the decrypted content. diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst index e42da2ae..654c5f2c 100644 --- a/doc/man1/notmuch-search.rst +++ b/doc/man1/notmuch-search.rst @@ -47,25 +47,25 @@ Supported options for **search** include **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). + 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). + 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). + 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 + 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 @@ -73,9 +73,9 @@ Supported options for **search** include **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). + 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 diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index b2667537..8bfa87c6 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -71,7 +71,7 @@ Supported options for **show** include http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/mail-mbox-formats.html - **raw** (default if --part is given) + **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. @@ -105,16 +105,16 @@ Supported options for **show** include ``--verify`` Compute and report the validity of any MIME cryptographic - signatures found in the selected content (ie. "multipart/signed" + 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 + 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 (i.e. "multipart/encrypted" parts). Status of + 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 + with ``--format=json`` and ``--format=sexp``) and on successful decryption the multipart/encrypted part will be replaced by the decrypted content. @@ -166,7 +166,7 @@ Supported options for **show** include 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 + 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.`` @@ -184,7 +184,7 @@ Supported options for **show** include ``--include-html`` Include "text/html" parts as part of the output (currently only - supported with --format=json and --format=sexp). By default, + 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. diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst index 8a5eeb18..f7a39ceb 100644 --- a/doc/man7/notmuch-search-terms.rst +++ b/doc/man7/notmuch-search-terms.rst @@ -7,7 +7,7 @@ SYNOPSIS **notmuch** **count** [option ...] <*search-term*> ... -**notmuch** **dump** [--format=(batch-tag|sup)] [--] [--output=<*file*>] [--] [<*search-term*> ...] +**notmuch** **dump** [--gzip] [--format=(batch-tag|sup)] [--output=<*file*>] [--] [<*search-term*> ...] **notmuch** **reindex** [option ...] <*search-term*> ... @@ -150,7 +150,7 @@ 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 + conjunction with the ``--uuid`` argument to **notmuch search** to find messages that have changed since an earlier query. query:<name> diff --git a/emacs/Makefile.local b/emacs/Makefile.local index 1b3ef584..5e4ae1bd 100644 --- a/emacs/Makefile.local +++ b/emacs/Makefile.local @@ -110,7 +110,10 @@ endif mkdir -p "$(DESTDIR)$(emacsetcdir)" install -m0644 $(emacs_images) "$(DESTDIR)$(emacsetcdir)" mkdir -p "$(DESTDIR)$(prefix)/bin/" - install $(emacs_mua) "$(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) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index a7e02710..25d83fd6 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -909,7 +909,7 @@ 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 + (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)))) @@ -926,11 +926,13 @@ status." :buffer buffer :command (cons command args) :connection-type 'pipe - :stderr err-buffer)) + :stderr err-buffer) + err-proc (get-buffer-process err-buffer)) (process-put proc 'err-buffer err-buffer) - ;; Silence "Process NAME stderr finished" in stderr by adding a - ;; no-op sentinel to the fake stderr process object - (set-process-sentinel (get-buffer-process err-buffer) #'ignore)) + + (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 @@ -990,9 +992,16 @@ status." ;; Emacs behaves strangely if an error escapes from a sentinel, ;; so turn errors into messages. (message "%s" (error-message-string err)))) - (when err-buffer (kill-buffer err-buffer)) (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) diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index fc8ac687..df2ac447 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -543,6 +543,7 @@ 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)) diff --git a/lib/add-message.cc b/lib/add-message.cc index f5fac8be..da37032c 100644 --- a/lib/add-message.cc +++ b/lib/add-message.cc @@ -227,7 +227,7 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, const char **thread_id) { GHashTable *parents = NULL; - const char *refs, *in_reply_to, *in_reply_to_message_id; + 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; @@ -242,14 +242,24 @@ _notmuch_database_link_message_to_parents (notmuch_database_t *notmuch, 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 the last message ID of the - * References header, if available. If not, fall back to the - * first message ID in the In-Reply-To header. */ - if (last_ref_message_id) { + /* 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) { diff --git a/lib/message-id.c b/lib/message-id.c index d7541d50..e71ce9f4 100644 --- a/lib/message-id.c +++ b/lib/message-id.c @@ -1,4 +1,5 @@ #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 @@ -94,3 +95,32 @@ _notmuch_message_id_parse (void *ctx, const char *message_id, const char **next) 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.cc b/lib/message.cc index 153e4bed..6f2f6345 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -32,6 +32,7 @@ struct _notmuch_message { 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; @@ -41,6 +42,7 @@ struct _notmuch_message { 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 @@ -117,6 +119,9 @@ _notmuch_message_create_for_document (const void *talloc_owner, /* 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; @@ -129,6 +134,7 @@ _notmuch_message_create_for_document (const void *talloc_owner, 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)) { @@ -349,6 +355,7 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) *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 @@ -413,6 +420,14 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message, void *field) _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) @@ -588,6 +603,84 @@ _notmuch_message_add_reply (notmuch_message_t *message, _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) { diff --git a/lib/messages.c b/lib/messages.c index a88f974f..04fa19f8 100644 --- a/lib/messages.c +++ b/lib/messages.c @@ -56,6 +56,15 @@ _notmuch_message_list_add_message (notmuch_message_list_t *list, 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) { @@ -101,6 +110,18 @@ notmuch_messages_valid (notmuch_messages_t *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) { diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 3764a6a9..df32d39c 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -56,6 +56,7 @@ NOTMUCH_BEGIN_DECLS #ifdef DEBUG # define DEBUG_DATABASE_SANITY 1 +# define DEBUG_THREADING 1 # define DEBUG_QUERY 1 #endif @@ -476,6 +477,9 @@ struct _notmuch_messages { 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); @@ -483,6 +487,9 @@ _notmuch_message_list_add_message (notmuch_message_list_t *list, notmuch_messages_t * _notmuch_messages_create (notmuch_message_list_t *list); +bool +_notmuch_messages_has_next (notmuch_messages_t *messages); + /* query.cc */ bool @@ -526,6 +533,20 @@ _notmuch_query_count_documents (notmuch_query_t *query, 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 */ @@ -539,6 +560,15 @@ _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 * @@ -580,6 +610,9 @@ _notmuch_string_list_append (notmuch_string_list_t *list, 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; diff --git a/lib/thread.cc b/lib/thread.cc index e961c76b..47c90664 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -24,6 +24,12 @@ #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 { @@ -387,27 +393,84 @@ _thread_add_matched_message (notmuch_thread_t *thread, _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, *parent; - const char *in_reply_to; + 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; - in_reply_to = _notmuch_message_get_in_reply_to (message); - if (in_reply_to && strlen (in_reply_to) && - g_hash_table_lookup_extended (thread->message_hash, - in_reply_to, NULL, - (void **) &parent)) - _notmuch_message_add_reply (parent, message); - else - _notmuch_message_list_add_message (thread->toplevel_list, message); + if (! _parent_via_in_reply_to (thread, message)) + _notmuch_message_list_add_message (maybe_toplevel_list, message); } /* @@ -418,27 +481,42 @@ _resolve_thread_relationships (notmuch_thread_t *thread) */ if (first_node) { message = first_node->message; - in_reply_to = _notmuch_message_get_in_reply_to (message); - if (thread->toplevel_list->head && - in_reply_to && strlen (in_reply_to) && - g_hash_table_lookup_extended (thread->message_hash, - in_reply_to, NULL, - (void **) &parent)) - _notmuch_message_add_reply (parent, 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: After scanning through the entire list looking for parents - * via "In-Reply-To", we should do a second pass that looks at the - * list of messages IDs in the "References" header instead. (And - * for this the parent would be the "deepest" message of all the - * messages found in the "References" list.) - * - * Doing this will allow messages and sub-threads to be positioned - * correctly in the thread even when an intermediate message is - * missing from the thread. + /* 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 diff --git a/notmuch-config.c b/notmuch-config.c index e1b16609..bf77cc9d 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -660,7 +660,19 @@ _config_set_list (notmuch_config_t *config, const char * notmuch_config_get_database_path (notmuch_config_t *config) { - return _config_get (config, &config->database_path, "database", "path"); + 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 diff --git a/notmuch-show.c b/notmuch-show.c index 1072ea55..c3a3783a 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -272,6 +272,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, 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; @@ -282,13 +283,37 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, 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); - 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"); @@ -313,11 +338,12 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, } } - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); 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* diff --git a/test/Makefile.local b/test/Makefile.local index 1a0ab813..1cf09778 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -15,6 +15,9 @@ 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) @@ -50,7 +53,8 @@ test_main_srcs=$(dir)/arg-test.c \ $(dir)/smtp-dummy.c \ $(dir)/symbol-test.cc \ $(dir)/make-db-version.cc \ - $(dir)/ghost-report.cc + $(dir)/ghost-report.cc \ + $(dir)/message-id-parse.c test_srcs=$(test_main_srcs) $(dir)/database-test.c diff --git a/test/T030-config.sh b/test/T030-config.sh index e91c3659..f36695c6 100755 --- a/test/T030-config.sh +++ b/test/T030-config.sh @@ -99,4 +99,14 @@ test_expect_equal "$(notmuch --config=alt-config-link config get user.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/T150-tagging.sh b/test/T150-tagging.sh index 6140c676..208b4b98 100755 --- a/test/T150-tagging.sh +++ b/test/T150-tagging.sh @@ -130,6 +130,19 @@ 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 diff --git a/test/T300-encoding.sh b/test/T300-encoding.sh index 2c656a1e..1e9d2a3d 100755 --- a/test/T300-encoding.sh +++ b/test/T300-encoding.sh @@ -44,4 +44,26 @@ 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 +message{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX +header{ +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 +header} +body{ +part{ ID: 1, Content-type: text/plain +This text contains “Windows-1252” character codes. +part} +body} +message} +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 index 9bf68b48..5935819f 100755 --- a/test/T310-emacs.sh +++ b/test/T310-emacs.sh @@ -1106,4 +1106,29 @@ output=$(test_emacs "(mapcar 'notmuch-escape-boolean-term (list \"\\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/T510-thread-replies.sh b/test/T510-thread-replies.sh index 6837ff17..5d6bea7e 100755 --- a/test/T510-thread-replies.sh +++ b/test/T510-thread-replies.sh @@ -45,10 +45,10 @@ expected='[[[{"id": "foo@one.com", expected=`echo "$expected" | notmuch_json_show_sanitize` test_expect_equal_json "$output" "$expected" -test_begin_subtest "Prefer References to In-Reply-To" +test_begin_subtest "Prefer References to dodgy In-Reply-To" add_message '[id]="foo@two.com"' \ '[subject]=two' -add_message '[in-reply-to]="<bar@baz.com>"' \ +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) @@ -101,12 +101,12 @@ expected='[[[{"id": "foo@three.com", "match": true, "excluded": false, expected=`echo "$expected" | notmuch_json_show_sanitize` test_expect_equal_json "$output" "$expected" -test_begin_subtest "Use last Reference" +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>"' \ +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) @@ -164,5 +164,62 @@ expected='[[[{"id": "XXXXX", "match": true, "excluded": false, 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/T585-thread-subquery.sh b/test/T585-thread-subquery.sh index 71ced149..bf9894d3 100755 --- a/test/T585-thread-subquery.sh +++ b/test/T585-thread-subquery.sh @@ -14,6 +14,9 @@ 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) @@ -21,6 +24,9 @@ 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) @@ -28,6 +34,9 @@ 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) @@ -35,6 +44,9 @@ 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 diff --git a/test/T670-duplicate-mid.sh b/test/T670-duplicate-mid.sh index bf8cc3a8..fd7df057 100755 --- a/test/T670-duplicate-mid.sh +++ b/test/T670-duplicate-mid.sh @@ -48,7 +48,11 @@ notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUT test_expect_equal_file EXPECTED OUTPUT test_begin_subtest 'Regexp search for second subject' -test_subtest_known_broken +# 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 diff --git a/test/T710-message-id.sh b/test/T710-message-id.sh new file mode 100755 index 00000000..e73d6ba9 --- /dev/null +++ b/test/T710-message-id.sh @@ -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-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 "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/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S b/test/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S new file mode 100644 index 00000000..62bf98db --- /dev/null +++ b/test/corpora/threading/ghost-root/1529425589.M615261P21663.len:2,S @@ -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 index 00000000..b79eaf7a --- /dev/null +++ b/test/corpora/threading/ghost-root/1532672447.R3166642290392477575.len:2,S @@ -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 index 00000000..343a855e --- /dev/null +++ b/test/corpora/threading/ghost-root/1532672447.R6968667928580738175.len:2,S @@ -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 index 00000000..4c36af95 --- /dev/null +++ b/test/corpora/threading/ghost-root/child @@ -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 index 00000000..a698185d --- /dev/null +++ b/test/corpora/threading/ghost-root/fake-root @@ -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 index 00000000..5f77ac36 --- /dev/null +++ b/test/corpora/threading/ghost-root/grand-child @@ -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 index 00000000..59682a95 --- /dev/null +++ b/test/corpora/threading/ghost-root/grand-child2 @@ -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 index 00000000..287a8954 --- /dev/null +++ b/test/corpora/threading/ghost-root/great-grand-child @@ -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 index 00000000..f1b16a0c --- /dev/null +++ b/test/corpora/threading/ghost-root/real-root @@ -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 index 00000000..23ee6495 --- /dev/null +++ b/test/corpora/threading/parent-priority/cur/child @@ -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 index 00000000..028371d4 --- /dev/null +++ b/test/corpora/threading/parent-priority/cur/grand-child @@ -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 index 00000000..3990843d --- /dev/null +++ b/test/corpora/threading/parent-priority/cur/root @@ -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/message-id-parse.c b/test/message-id-parse.c new file mode 100644 index 00000000..752eb1fd --- /dev/null +++ b/test/message-id-parse.c @@ -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/util/string-util.c b/util/string-util.c index b0108811..fc2058e0 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -141,7 +141,7 @@ make_boolean_term (void *ctx, const char *prefix, const char *term, return 0; } -static const char* +const char* skip_space (const char *str) { while (*str && isspace ((unsigned char) *str)) diff --git a/util/string-util.h b/util/string-util.h index 97770614..4c110a20 100644 --- a/util/string-util.h +++ b/util/string-util.h @@ -77,6 +77,8 @@ unsigned int strcase_hash (const void *ptr); void strip_trailing (char *str, char ch); +const char* skip_space (const char *str); + #ifdef __cplusplus } #endif @@ -1 +1 @@ -0.27 +0.28 |
