+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)
=========================
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
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
----------
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
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.27'
+__VERSION__ = '0.28'
SOVERSION = '5'
# 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)
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
[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.
+++ /dev/null
-#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 :
--- /dev/null
+#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
--- /dev/null
+#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 "$@"
# 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}
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
# 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}
# do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx))
+# Whether bash exists, and if so where
+NOTMUCH_HAVE_BASH=${have_bash}
+NOTMUCH_BASH_ABSOLUTE=${bash_absolute}
+
+# Whether perl exists, and if so where
+NOTMUCH_HAVE_PERL=${have_perl}
+NOTMUCH_PERL_ABSOLUTE=${perl_absolute}
+
# Name of python interpreter
NOTMUCH_PYTHON=${python}
NAME = 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)
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
--- /dev/null
+/tmp/
+/libnotmuch-dev/
+/libnotmuch*/
+/notmuch-emacs/
+/elpa-notmuch/
+/notmuch/
+/notmuch-mutt/
+/notmuch-vim/
+/ruby-notmuch/
+/python*-notmuch/
+/*.debhelper
+/*.debhelper.log
+/*.substvars
+/files
+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
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~),
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
-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
-contrib/notmuch-mutt/notmuch-mutt.1
+usr/share/man/man1/notmuch-mutt.1
+++ /dev/null
-completion/notmuch-completion.zsh
-usr/bin
-usr/share/man
+usr/bin/notmuch
+usr/bin/notmuch-emacs-mua
usr/share/bash-completion
+usr/share/zsh/vendor-completions
--- /dev/null
+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
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:
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
single-debian-patch
-tar-ignore
+tar-ignore=.git
tar-ignore=performance-test/download/*.tar.xz
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Copyright (c) 2011-2012 David Bremner <david@tethera.net>
#
# 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')
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('├'):
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), '└')
``--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.
**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**)
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.
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
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.
**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
**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
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.
``--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.
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.``
``--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.
**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*> ...
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>
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)
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))))
: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
;; 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)
(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))
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;
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) {
#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
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);
+}
int frozen;
char *message_id;
char *thread_id;
+ size_t thread_depth;
char *in_reply_to;
notmuch_string_list_t *tag_list;
notmuch_string_list_t *filename_term_list;
notmuch_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
/* the message is initially not synchronized with Xapian */
message->last_view = 0;
+ /* Calculated after the thread structure is computed */
+ message->thread_depth = 0;
+
/* Each of these will be lazily created as needed. */
message->message_id = NULL;
message->thread_id = NULL;
message->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)) {
*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
_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)
_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)
{
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)
{
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)
{
#ifdef DEBUG
# define DEBUG_DATABASE_SANITY 1
+# define DEBUG_THREADING 1
# define DEBUG_QUERY 1
#endif
notmuch_message_list_t *
_notmuch_message_list_create (const void *ctx);
+bool
+_notmuch_message_list_empty (notmuch_message_list_t *list);
+
void
_notmuch_message_list_add_message (notmuch_message_list_t *list,
notmuch_message_t *message);
notmuch_messages_t *
_notmuch_messages_create (notmuch_message_list_t *list);
+bool
+_notmuch_messages_has_next (notmuch_messages_t *messages);
+
/* query.cc */
bool
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 */
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 *
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;
#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 {
_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);
}
/*
*/
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
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
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
GMimeStream *stream_filter = NULL;
GMimeFilter *crlf_filter = NULL;
+ GMimeFilter *windows_filter = NULL;
GMimeDataWrapper *wrapper;
const char *charset;
if (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");
}
}
- 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*
$(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)
$(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
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
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
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
\"\\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
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)
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)
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
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)
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)
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)
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
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
--- /dev/null
+#!/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
--- /dev/null
+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
+
--- /dev/null
+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
+
+
+
+
--- /dev/null
+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.
+
--- /dev/null
+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
+
+
--- /dev/null
+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
--- /dev/null
+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
+
+
--- /dev/null
+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
+
+
--- /dev/null
+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
+
+
--- /dev/null
+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
--- /dev/null
+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.
+
--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+#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);
+}
return 0;
}
-static const char*
+const char*
skip_space (const char *str)
{
while (*str && isspace ((unsigned char) *str))
void strip_trailing (char *str, char ch);
+const char* skip_space (const char *str);
+
#ifdef __cplusplus
}
#endif