]> git.notmuchmail.org Git - notmuch/commitdiff
Declare fast forward from 0.27-1~bpo9+2 debian/stretch-backports archive/debian/0.28-2_bpo9+1 debian/0.28-2_bpo9+1
authorDavid Bremner <bremner@debian.org>
Sat, 20 Oct 2018 17:25:09 +0000 (14:25 -0300)
committerDavid Bremner <bremner@debian.org>
Sat, 20 Oct 2018 17:25:09 +0000 (14:25 -0300)
[dgit --overwrite]

65 files changed:
NEWS
bindings/python/notmuch/message.py
bindings/python/notmuch/version.py
completion/Makefile.local
completion/README
completion/notmuch-completion.zsh [deleted file]
completion/zsh/_email-notmuch [new file with mode: 0644]
completion/zsh/_notmuch [new file with mode: 0644]
configure
contrib/notmuch-mutt/Makefile
debian/.gitignore [new file with mode: 0644]
debian/changelog
debian/compat
debian/control
debian/notmuch-mutt.install
debian/notmuch-mutt.manpages
debian/notmuch.examples [deleted file]
debian/notmuch.install
debian/notmuch.manpages [new file with mode: 0644]
debian/rules
debian/source/options
devel/nmbug/notmuch-report
devel/printmimestructure
doc/man1/notmuch-address.rst
doc/man1/notmuch-dump.rst
doc/man1/notmuch-reply.rst
doc/man1/notmuch-search.rst
doc/man1/notmuch-show.rst
doc/man7/notmuch-search-terms.rst
emacs/Makefile.local
emacs/notmuch-lib.el
emacs/notmuch-mua.el
lib/add-message.cc
lib/message-id.c
lib/message.cc
lib/messages.c
lib/notmuch-private.h
lib/thread.cc
notmuch-config.c
notmuch-show.c
test/Makefile.local
test/T030-config.sh
test/T150-tagging.sh
test/T300-encoding.sh
test/T310-emacs.sh
test/T510-thread-replies.sh
test/T585-thread-subquery.sh
test/T670-duplicate-mid.sh
test/T710-message-id.sh [new file with mode: 0755]
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/message-id-parse.c [new file with mode: 0644]
util/string-util.c
util/string-util.h
version

diff --git a/NEWS b/NEWS
index fc77f5323ea96f55094e62074dbb109fb2c25a91..ca3ba99ecca2f1301309ac9fe2f508f15227e697 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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
 ----------
index d242097a8299eff8885db7c93a64e9b3b1de8b27..de0fb4151e52e50c3f3eb684932b65820d74cf82 100644 (file)
@@ -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
index fbb282e32e4a9bbcc3712808d4d866d3b7bee6e3..6a513a25e780f16f2e80ea4533233cc3ea2ddeaa 100644 (file)
@@ -1,3 +1,3 @@
 # this file should be kept in sync with ../../../version
-__VERSION__ = '0.27'
+__VERSION__ = '0.28'
 SOVERSION = '5'
index dfc12713301dc7891008e0000a3e26db78519557..8e86c9d231d958287b8b69ad58f16f81956a60d2 100644 (file)
@@ -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
index 89805c726f32424941b890a8e6fd76e9b56ab7c9..900e1c984d50e7dd8493d2f08ab94f5a3adaeab3 100644 (file)
@@ -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 (file)
index 208a550..0000000
+++ /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 (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 "$@"
index ab7e1610a0c58c83f942bb5754c14b2657384657..b2200be08a03069957c76af3a59f3fbbffd360d6 100755 (executable)
--- a/configure
+++ b/configure
@@ -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}
 
index 87f9031cb4777b27f863da8d33b2a2dbc4c176e7..855438be35779a8497a9a7b4981892fe66af05e3 100644 (file)
@@ -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/.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
index 64ddeb4f75c203c5d195581b2be6dcb6a629034d..b32175bbff4068505f82a097096e168ffe16c50f 100644 (file)
@@ -1,3 +1,43 @@
+notmuch (0.28-2~bpo9+1) stretch-backports; urgency=medium
+
+  * Rebuild for stretch-backports.
+
+ -- David Bremner <bremner@debian.org>  Sat, 20 Oct 2018 14:25:09 -0300
+
+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
index f599e28b8ab0d8c9c57a486c89c4a5132dcbd3b2..b4de3947675361a7770d29b8982c407b0ec6b2a0 100644 (file)
@@ -1 +1 @@
-10
+11
index 79073265a5a0b19709dc811b1e1519047c90df85..e3e7ef70dd7becd013c039c444ecea429b1fe6ae 100644 (file)
@@ -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
index 9141c26f284a45932393c856e5bd4a9f99f106fd..9b468bdb0fecec74097a034dbb26242e263ff435 100644 (file)
@@ -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
index 3f6b8abdae2f5cff6747e22b8de1393244747bb7..a14ed69b36d730b31ab60de64c8fc8886a2e38ee 100644 (file)
@@ -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 (file)
index 524e0f4..0000000
+++ /dev/null
@@ -1 +0,0 @@
-completion/notmuch-completion.zsh
index 31b9a37eb18f10b004b2a95b67e7e1c676515209..2514ca6c4d10724e2220a8b3c82857ac3be02e4c 100644 (file)
@@ -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 (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
index abd5d8cf9cebc6d20e99db2ea18c15b7675b90dc..4979ec3f659c4317c766312aee6c1a75f350e770 100755 (executable)
@@ -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
index 7e95ec71c11716c84fd576948826748f09074ac3..cc76e9193ea89ee624034b1b8f9f13194a993da5 100644 (file)
@@ -1,3 +1,3 @@
 single-debian-patch
-tar-ignore
+tar-ignore=.git
 tar-ignore=performance-test/download/*.tar.xz
index 5789c5f442c351e545f353318360f213130ba221..eaceb2ce47123941aecaca2c888e5b855232d4b5 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 #
 # Copyright (c) 2011-2012 David Bremner <david@tethera.net>
 #
index 34d12930b4c986b82252689b567517b729c6155b..70e0a5c0fc1c94216e50d732aeba1711959e0335 100755 (executable)
 # 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), '└')
index c00d7d743e3e45a0b6111457b28b00140dedc4a8..12d86e8952c5e70f5959097930b5a3607094597b 100644 (file)
@@ -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.
 
index f8ec486871c120a729810b6a07d8585e0c0a0635..ec6335b2febc97099249c01c02002cf6044192f6 100644 (file)
@@ -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
index c893ba048b9c2b7abc48d926b506cce4d6b7ad60..5c64c4a63b106ca5ec60c099de5ca013c0be22fd 100644 (file)
@@ -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.
 
index e42da2aec65407a69c069995a2172569d728ea2e..654c5f2cfbcab5cdc6f798cd873110ec1813a8c2 100644 (file)
@@ -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
index b2667537c220d88905d5c486af18016064644b13..8bfa87c664f98124bbbe5a763cfb45e0c6603589 100644 (file)
@@ -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.
index 8a5eeb189179a41220139f634b6499172285d379..f7a39ceb9df4bc6efb9ae598d70150701374dc48 100644 (file)
@@ -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>
index 1b3ef5847a4060a0bb4503356d90caf1a11a9d18..5e4ae1bda257f61cac0f296ee5a9ff2bec80f4f9 100644 (file)
@@ -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)
index a7e027101c487c996a45942bb29a6e061a60c446..25d83fd61b49ca01aaa129de9f3ead93bec30ae6 100644 (file)
@@ -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)
index fc8ac687e8208b64d95c9032e7e3e22ea989e5ca..df2ac4470f5200fcfb183f5c1cf01c5c84a15c24 100644 (file)
@@ -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))
index f5fac8be751fa2706d09383c07390652e73dc679..da37032c5979cb0eeb1d67621dc9d57bff80a32f 100644 (file)
@@ -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) {
index d7541d50102d671ca6fd465ff047ceb4c220753a..e71ce9f4674465c13f06eee32c4266d8821b3a26 100644 (file)
@@ -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);
+}
index 153e4bed01e370021ec508e76fe7746fc91bcddd..6f2f634512453770a358b374ff1e3a3073888d0e 100644 (file)
@@ -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)
 {
index a88f974ff5949bd8d54b886c10949e538ff81909..04fa19f8ec16e4c140e506e1f39f084ba47e09dc 100644 (file)
@@ -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)
 {
index 3764a6a996fd64448d99446d38b472cf3084f0d7..df32d39cb7a38a1025de40a52a889f720d0021ff 100644 (file)
@@ -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;
index e961c76bf37a8d3cbee2a4041da48ef92340b03a..47c9066406e084c51735b0dc6960055904763659 100644 (file)
 #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
index e1b166094ef317f137342356028f4317fe7f532d..bf77cc9d43831460c63625a8fcef47a9f6464612 100644 (file)
@@ -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
index 1072ea558dfbcc04b91b4aa9041c3f7dd8692adf..c3a3783a4094c952f61b567907b87f658a1381ab 100644 (file)
@@ -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*
index 1a0ab813f996a620ef3c2d99b90fe6add01c4bde..1cf09778d5b5e44914a81dcdc06ff8799c9310c4 100644 (file)
@@ -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
 
index e91c36597e3c1601fc4c9b2346adb587cc7fbea0..f36695c66d78bccc5d586a1b762e063c4c53cd4d 100755 (executable)
@@ -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
index 6140c67686305f8959bc7398ad819212cfb38ee4..208b4b9806405e5c4e90b8d7f1af56afe6a4e412 100755 (executable)
@@ -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
index 2c656a1e0950fdcfc72e8599a9b605b047dbe324..1e9d2a3da010a7c5c5d27fad4e3ddbb4993154cc 100755 (executable)
@@ -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
+\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
index 9bf68b480591ece6825991a47096642cbbffbb81..5935819f4ba4404eb14802d921ff88c3cd2ea1db 100755 (executable)
@@ -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
index 6837ff17663002cfb8c4f9f627f2715ed6d2d154..5d6bea7ed7ab7fdd3b9d06b4c8002195da9282ab 100755 (executable)
@@ -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
index 71ced1491568d5514307c29b9e9035205c585bf3..bf9894d3cb457cbb4e312327c5dfa3814ad6ae3b 100755 (executable)
@@ -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
index bf8cc3a816b65e8cc858411979fcd22340ac2e2d..fd7df05774c4600ade48746e8fa49e0a226791dc 100755 (executable)
@@ -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 (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/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/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);
+}
index b0108811903b27153dcaece1fa10b893234c3b9f..fc2058e03483da596c72946f1fe29f6ecfa60b44 100644 (file)
@@ -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))
index 97770614adf1ab6930e529cd148dea47c71547f6..4c110a205ccfb5b2684183e79ca2147d1c891b69 100644 (file)
@@ -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
diff --git a/version b/version
index 5a9e6bda259d91234404902623fbf7367f54f350..4950f07e409ddbcd7d285c57685faacf4cbad328 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-0.27
+0.28