Merge remote-tracking branch 'origin/master' into nmweb

This commit is contained in:
David Bremner 2023-09-18 06:16:47 -03:00
commit 1129cf890e
111 changed files with 1480 additions and 374 deletions

72
NEWS
View file

@ -1,3 +1,75 @@
Notmuch 0.38 (2023-09-12)
=========================
General
-------
Support relative lastmod queries (see notmuch-sexp-queries(7) and
notmuch-search-terms(7) for details).
Support indexing of designated attachments as text (see
notmuch-config(1) for details).
CLI
---
Add options --offset and --limit to notmuch-show(1).
Emacs
-----
New commands notmuch-search-edit-search and notmuch-tree-edit-search.
Introduce notmuch-tree-outline-mode.
Some compatibility fixes for Emacs 29. At least one issue (hiding
images) remains in 0.38.
Support completion when piping to external command.
Fix regression in updating tag display introduced by 0.37.
Library
-------
Fix bug creating database when database.path is not set.
Incremental performance improvements for message deletion.
Catch Xapian exceptions when deleting messages.
Sync removed message properties to the database.
Replace use of thread-unsafe Query::MatchAll in the infix query
parser.
Notmuch-Mutt
------------
Be more careful when clearing the results directory.
Ruby
----
Use `database_open_with_config`, and provide compatible path search
semantics.
Bugfix for query.get_sort
Test Suite
----------
Support testing installed version of notmuch.
Adapt to some breaking changes in glib handling of init files.
Replace OpenPGP key used in test suite.
Performance Tests
-----------------
Update signatures for performance test corpus.
Notmuch 0.37 (2022-08-21)
=========================

View file

@ -34,3 +34,7 @@ CLEAN += $(patsubst %,$(dir)/ruby/%, \
CLEAN += bindings/ruby/.vendorarchdir.time $(dir)/ruby.stamp
CLEAN += bindings/python-cffi/build $(dir)/python-cffi.stamp
CLEAN += bindings/python-cffi/__pycache__
DISTCLEAN += bindings/python-cffi/_notmuch_config.py \
bindings/python-cffi/notmuch2.egg-info

View file

@ -47,7 +47,10 @@ if sys.version_info[0] == 2:
return value
else:
from configparser import SafeConfigParser
from configparser import ConfigParser as SafeConfigParser
if not hasattr(SafeConfigParser, 'readfp'): # py >= 3.12
SafeConfigParser.readfp = SafeConfigParser.read_file
class Python3StringMixIn(object):
def __str__(self):

View file

@ -1,3 +1,3 @@
# this file should be kept in sync with ../../../version
__VERSION__ = '0.37'
__VERSION__ = '0.38'
SOVERSION = '5'

View file

@ -58,40 +58,56 @@ notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE self)
notmuch_database_t *database;
notmuch_status_t ret;
/* Check arguments */
rb_scan_args (argc, argv, "11", &pathv, &hashv);
path = NULL;
create = 0;
mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
SafeStringValue (pathv);
path = RSTRING_PTR (pathv);
/* Check arguments */
rb_scan_args (argc, argv, "02", &pathv, &hashv);
if (!NIL_P (pathv)) {
SafeStringValue (pathv);
path = RSTRING_PTR (pathv);
}
if (!NIL_P (hashv)) {
Check_Type (hashv, T_HASH);
create = RTEST (rb_hash_aref (hashv, ID2SYM (ID_db_create)));
modev = rb_hash_aref (hashv, ID2SYM (ID_db_mode));
if (NIL_P (modev))
mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
else if (!FIXNUM_P (modev))
rb_raise (rb_eTypeError, ":mode isn't a Fixnum");
else {
mode = FIX2INT (modev);
switch (mode) {
case NOTMUCH_DATABASE_MODE_READ_ONLY:
case NOTMUCH_DATABASE_MODE_READ_WRITE:
break;
default:
rb_raise ( rb_eTypeError, "Invalid mode");
VALUE rmode, rcreate;
VALUE kwargs[2];
static ID keyword_ids[2];
if (!keyword_ids[0]) {
keyword_ids[0] = rb_intern_const ("mode");
keyword_ids[1] = rb_intern_const ("create");
}
rb_get_kwargs (hashv, keyword_ids, 0, 2, kwargs);
rmode = kwargs[0];
rcreate = kwargs[1];
if (rmode != Qundef) {
if (!FIXNUM_P (rmode))
rb_raise (rb_eTypeError, ":mode isn't a Fixnum");
else {
mode = FIX2INT (rmode);
switch (mode) {
case NOTMUCH_DATABASE_MODE_READ_ONLY:
case NOTMUCH_DATABASE_MODE_READ_WRITE:
break;
default:
rb_raise ( rb_eTypeError, "Invalid mode");
}
}
}
} else {
create = 0;
mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
if (rcreate != Qundef)
create = RTEST (rcreate);
}
rb_check_typeddata (self, &notmuch_rb_database_type);
if (create)
ret = notmuch_database_create (path, &database);
else
ret = notmuch_database_open (path, mode, &database);
ret = notmuch_database_open_with_config (path, mode, NULL, NULL, &database, NULL);
notmuch_rb_status_raise (ret);
DATA_PTR (self) = notmuch_rb_object_create (database, "notmuch_rb_database");
@ -408,7 +424,7 @@ notmuch_rb_database_get_all_tags (VALUE self)
rb_raise (notmuch_rb_eBaseError, "%s", msg);
}
return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
return notmuch_rb_tags_get (tags);
}
/*

View file

@ -33,7 +33,6 @@ extern VALUE notmuch_rb_cThreads;
extern VALUE notmuch_rb_cThread;
extern VALUE notmuch_rb_cMessages;
extern VALUE notmuch_rb_cMessage;
extern VALUE notmuch_rb_cTags;
extern VALUE notmuch_rb_eBaseError;
extern VALUE notmuch_rb_eDatabaseError;
@ -48,8 +47,6 @@ extern VALUE notmuch_rb_eUnbalancedFreezeThawError;
extern VALUE notmuch_rb_eUnbalancedAtomicError;
extern ID ID_call;
extern ID ID_db_create;
extern ID ID_db_mode;
/* RSTRING_PTR() is new in ruby-1.9 */
#if !defined(RSTRING_PTR)
@ -59,7 +56,6 @@ extern ID ID_db_mode;
extern const rb_data_type_t notmuch_rb_object_type;
extern const rb_data_type_t notmuch_rb_database_type;
extern const rb_data_type_t notmuch_rb_directory_type;
extern const rb_data_type_t notmuch_rb_filenames_type;
extern const rb_data_type_t notmuch_rb_query_type;
extern const rb_data_type_t notmuch_rb_threads_type;
extern const rb_data_type_t notmuch_rb_thread_type;
@ -92,9 +88,6 @@ extern const rb_data_type_t notmuch_rb_tags_type;
#define Data_Get_Notmuch_Directory(obj, ptr) \
Data_Get_Notmuch_Object ((obj), &notmuch_rb_directory_type, (ptr))
#define Data_Get_Notmuch_FileNames(obj, ptr) \
Data_Get_Notmuch_Object ((obj), &notmuch_rb_filenames_type, (ptr))
#define Data_Get_Notmuch_Query(obj, ptr) \
Data_Get_Notmuch_Object ((obj), &notmuch_rb_query_type, (ptr))
@ -226,10 +219,7 @@ notmuch_rb_directory_get_child_directories (VALUE self);
/* filenames.c */
VALUE
notmuch_rb_filenames_destroy (VALUE self);
VALUE
notmuch_rb_filenames_each (VALUE self);
notmuch_rb_filenames_get (notmuch_filenames_t *fnames);
/* query.c */
VALUE
@ -370,10 +360,7 @@ notmuch_rb_message_thaw (VALUE self);
/* tags.c */
VALUE
notmuch_rb_tags_destroy (VALUE self);
VALUE
notmuch_rb_tags_each (VALUE self);
notmuch_rb_tags_get (notmuch_tags_t *tags);
/* init.c */
void

View file

@ -87,7 +87,7 @@ notmuch_rb_directory_get_child_files (VALUE self)
fnames = notmuch_directory_get_child_files (dir);
return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
return notmuch_rb_filenames_get (fnames);
}
/*
@ -106,5 +106,5 @@ notmuch_rb_directory_get_child_directories (VALUE self)
fnames = notmuch_directory_get_child_directories (dir);
return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
return notmuch_rb_filenames_get (fnames);
}

View file

@ -1,53 +1,11 @@
/* The Ruby interface to the notmuch mail library
*
* Copyright © 2010 Ali Polatel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Ali Polatel <alip@exherbo.org>
*/
#include "defs.h"
/*
* call-seq: FILENAMES.destroy! => nil
*
* Destroys the filenames, freeing all resources allocated for it.
*/
VALUE
notmuch_rb_filenames_destroy (VALUE self)
notmuch_rb_filenames_get (notmuch_filenames_t *fnames)
{
notmuch_rb_object_destroy (self, &notmuch_rb_filenames_type);
return Qnil;
}
/*
* call-seq: FILENAMES.each {|item| block } => FILENAMES
*
* Calls +block+ once for each element in +self+, passing that element as a
* parameter.
*/
VALUE
notmuch_rb_filenames_each (VALUE self)
{
notmuch_filenames_t *fnames;
Data_Get_Notmuch_FileNames (self, fnames);
VALUE rb_array = rb_ary_new ();
for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames))
rb_yield (rb_str_new2 (notmuch_filenames_get (fnames)));
return self;
rb_ary_push (rb_array, rb_str_new2 (notmuch_filenames_get (fnames)));
return rb_array;
}

View file

@ -22,13 +22,11 @@
VALUE notmuch_rb_cDatabase;
VALUE notmuch_rb_cDirectory;
VALUE notmuch_rb_cFileNames;
VALUE notmuch_rb_cQuery;
VALUE notmuch_rb_cThreads;
VALUE notmuch_rb_cThread;
VALUE notmuch_rb_cMessages;
VALUE notmuch_rb_cMessage;
VALUE notmuch_rb_cTags;
VALUE notmuch_rb_eBaseError;
VALUE notmuch_rb_eDatabaseError;
@ -43,8 +41,6 @@ VALUE notmuch_rb_eUnbalancedFreezeThawError;
VALUE notmuch_rb_eUnbalancedAtomicError;
ID ID_call;
ID ID_db_create;
ID ID_db_mode;
const rb_data_type_t notmuch_rb_object_type = {
.wrap_struct_name = "notmuch_object",
@ -65,13 +61,11 @@ const rb_data_type_t notmuch_rb_object_type = {
define_type (database);
define_type (directory);
define_type (filenames);
define_type (query);
define_type (threads);
define_type (thread);
define_type (messages);
define_type (message);
define_type (tags);
/*
* Document-module: Notmuch
@ -86,13 +80,11 @@ define_type (tags);
* the user:
*
* - Notmuch::Database
* - Notmuch::FileNames
* - Notmuch::Query
* - Notmuch::Threads
* - Notmuch::Messages
* - Notmuch::Thread
* - Notmuch::Message
* - Notmuch::Tags
*/
void
@ -101,8 +93,6 @@ Init_notmuch (void)
VALUE mod;
ID_call = rb_intern ("call");
ID_db_create = rb_intern ("create");
ID_db_mode = rb_intern ("mode");
mod = rb_define_module ("Notmuch");
@ -297,17 +287,6 @@ Init_notmuch (void)
rb_define_method (notmuch_rb_cDirectory, "child_files", notmuch_rb_directory_get_child_files, 0); /* in directory.c */
rb_define_method (notmuch_rb_cDirectory, "child_directories", notmuch_rb_directory_get_child_directories, 0); /* in directory.c */
/*
* Document-class: Notmuch::FileNames
*
* Notmuch file names
*/
notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cObject);
rb_undef_method (notmuch_rb_cFileNames, "initialize");
rb_define_method (notmuch_rb_cFileNames, "destroy!", notmuch_rb_filenames_destroy, 0); /* in filenames.c */
rb_define_method (notmuch_rb_cFileNames, "each", notmuch_rb_filenames_each, 0); /* in filenames.c */
rb_include_module (notmuch_rb_cFileNames, rb_mEnumerable);
/*
* Document-class: Notmuch::Query
*
@ -395,15 +374,4 @@ Init_notmuch (void)
rb_define_method (notmuch_rb_cMessage, "tags_to_maildir_flags", notmuch_rb_message_tags_to_maildir_flags, 0); /* in message.c */
rb_define_method (notmuch_rb_cMessage, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */
rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 0); /* in message.c */
/*
* Document-class: Notmuch::Tags
*
* Notmuch tags
*/
notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cObject);
rb_undef_method (notmuch_rb_cTags, "initialize");
rb_define_method (notmuch_rb_cTags, "destroy!", notmuch_rb_tags_destroy, 0); /* in tags.c */
rb_define_method (notmuch_rb_cTags, "each", notmuch_rb_tags_each, 0); /* in tags.c */
rb_include_module (notmuch_rb_cTags, rb_mEnumerable);
}

View file

@ -120,7 +120,7 @@ notmuch_rb_message_get_filenames (VALUE self)
fnames = notmuch_message_get_filenames (message);
return Data_Wrap_Notmuch_Object (notmuch_rb_cFileNames, &notmuch_rb_filenames_type, fnames);
return notmuch_rb_filenames_get (fnames);
}
/*
@ -221,7 +221,7 @@ notmuch_rb_message_get_tags (VALUE self)
if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory");
return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
return notmuch_rb_tags_get (tags);
}
/*

View file

@ -71,5 +71,5 @@ notmuch_rb_messages_collect_tags (VALUE self)
if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory");
return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
return notmuch_rb_tags_get (tags);
}

View file

@ -45,7 +45,7 @@ notmuch_rb_query_get_sort (VALUE self)
Data_Get_Notmuch_Query (self, query);
return FIX2INT (notmuch_query_get_sort (query));
return INT2FIX (notmuch_query_get_sort (query));
}
/*

View file

@ -1,56 +1,13 @@
/* The Ruby interface to the notmuch mail library
*
* Copyright © 2010, 2011 Ali Polatel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Ali Polatel <alip@exherbo.org>
*/
#include "defs.h"
/*
* call-seq: TAGS.destroy! => nil
*
* Destroys the tags, freeing all resources allocated for it.
*/
VALUE
notmuch_rb_tags_destroy (VALUE self)
notmuch_rb_tags_get (notmuch_tags_t *tags)
{
notmuch_rb_object_destroy (self, &notmuch_rb_tags_type);
return Qnil;
}
/*
* call-seq: TAGS.each {|item| block } => TAGS
*
* Calls +block+ once for each element in +self+, passing that element as a
* parameter.
*/
VALUE
notmuch_rb_tags_each (VALUE self)
{
const char *tag;
notmuch_tags_t *tags;
Data_Get_Notmuch_Tags (self, tags);
VALUE rb_array = rb_ary_new ();
for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
tag = notmuch_tags_get (tags);
rb_yield (rb_str_new2 (tag));
const char *tag = notmuch_tags_get (tags);
rb_ary_push (rb_array, rb_str_new2 (tag));
}
return self;
return rb_array;
}

View file

@ -204,5 +204,5 @@ notmuch_rb_thread_get_tags (VALUE self)
if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory");
return Data_Wrap_Notmuch_Object (notmuch_rb_cTags, &notmuch_rb_tags_type, tags);
return notmuch_rb_tags_get (tags);
}

View file

@ -1,5 +1,6 @@
#define _GNU_SOURCE
#include <strings.h>
#include <strings.h> /* strcasecmp() in POSIX */
#include <string.h> /* strcasecmp() in *BSD */
int
main ()

View file

@ -530,7 +530,7 @@ _notmuch_show()
! $split &&
case "${cur}" in
-*)
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html ${_notmuch_shared_options}"
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html --limit= --offset= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;;

View file

@ -245,6 +245,8 @@ _notmuch_show() {
'--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]' \
'--limit=[limit the number of displayed results]:limit: ' \
'--offset=[skip displaying the first N results]:offset: ' \
'*::search term:_notmuch_search_term'
}

19
configure vendored
View file

@ -422,6 +422,18 @@ else
fi
unset test_cmdline
printf "C compiler supports thread sanitizer... "
test_cmdline="${CC} ${CFLAGS} ${CPPFLAGS} -fsanitize=thread minimal.c ${LDFLAGS} -o minimal"
if ${test_cmdline} >/dev/null 2>&1 && ./minimal
then
printf "Yes.\n"
have_tsan=1
else
printf "Nope, skipping those tests.\n"
have_tsan=0
fi
unset test_cmdline
printf "Reading libnotmuch version from source... "
cat > _libversion.c <<EOF
#include <stdio.h>
@ -541,8 +553,8 @@ version of GPGME.
Please try to rebuild your version of GMime against a more recent
version of GPGME (at least GPGME 1.8.0).
EOF
if command -v gpgme-config >/dev/null; then
printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)"
if GPGME_VERS="$(pkg-config --modversion gpgme || gpgme-config --version)"; then
printf 'Your current GPGME development version is: %s\n' "$GPGME_VERS"
else
printf 'You do not have the GPGME development libraries installed.\n'
fi
@ -1590,8 +1602,9 @@ NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
NOTMUCH_ZLIB_CFLAGS="${zlib_cflags}"
NOTMUCH_ZLIB_LDFLAGS="${zlib_ldflags}"
# Does the C compiler support the address sanitizer
# Does the C compiler support the sanitizers
NOTMUCH_HAVE_ASAN=${have_asan}
NOTMUCH_HAVE_TSAN=${have_tsan}
# do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx))

View file

@ -39,8 +39,6 @@ To *run* notmuch-mutt you will need Perl with the following libraries:
(Debian package: libmail-box-perl)
- Mail::Header <https://metacpan.org/pod/Mail::Header>
(Debian package: libmailtools-perl)
- String::ShellQuote <https://metacpan.org/pod/String::ShellQuote>
(Debian package: libstring-shellquote-perl)
- Term::ReadLine::Gnu <https://metacpan.org/pod/Term::ReadLine::Gnu>
(Debian package: libterm-readline-gnu-perl)

View file

@ -2,7 +2,7 @@
#
# notmuch-mutt - notmuch (of a) helper for Mutt
#
# Copyright: © 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
# Copyright: © 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
# License: GNU General Public License (GPL), version 3 or above
#
# See the bottom of this file for more documentation.
@ -13,11 +13,11 @@ use warnings;
use File::Path;
use File::Basename;
use File::Find;
use Getopt::Long qw(:config no_getopt_compat);
use Mail::Header;
use Mail::Box::Maildir;
use Pod::Usage;
use String::ShellQuote;
use Term::ReadLine;
use Digest::SHA;
@ -26,9 +26,53 @@ my $xdg_cache_dir = "$ENV{HOME}/.cache";
$xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME};
my $cache_dir = "$xdg_cache_dir/notmuch/mutt";
sub die_dir($$) {
my ($maildir, $error) = @_;
die "notmuch-mutt: search cache maildir $maildir $error\n".
"Please ensure that the notmuch-mutt search cache Maildir\n".
"contains no subfolders or real mail data, only symlinks to mail\n";
}
# create an empty maildir (if missing) or empty an existing maildir"
sub empty_maildir($) {
sub die_subdir($$$) {
my ($maildir, $subdir, $error) = @_;
die_dir($maildir, "subdir $subdir $error");
}
# check that the search cache maildir is that and not a real maildir
# otherwise there could be data loss when the search cache is emptied
sub check_search_cache_maildir($) {
my ($maildir) = (@_);
return unless -e $maildir;
-d $maildir or die_dir($maildir, 'is not a directory');
opendir(my $mdh, $maildir) or die_dir($maildir, "cannot be opened: $!");
my @contents = grep { !/^\.\.?$/ } readdir $mdh;
closedir $mdh;
my @required = ('cur', 'new', 'tmp');
foreach my $d (@required) {
-l "$maildir/$d" and die_dir($maildir, "contains symlink $d");
-e "$maildir/$d" or die_subdir($maildir, $d, 'is missing');
-d "$maildir/$d" or die_subdir($maildir, $d, 'is not a directory');
find(sub {
$_ eq '.' and return;
$_ eq '..' and return;
-l $_ or die_subdir($maildir, $d, "contains non-symlink $_");
}, "$maildir/$d");
}
my %required = map { $_ => 1 } @required;
foreach my $d (@contents) {
-l "$maildir/$d" and die_dir( $maildir, "contains symlink $d");
-d "$maildir/$d" or die_dir( $maildir, "contains non-directory $d");
exists($required[$d]) or die_dir( $maildir, "contains directory $d");
}
}
# create an empty search cache maildir (if missing) or empty existing one
sub empty_search_cache_maildir($) {
my ($maildir) = (@_);
rmtree($maildir) if (-d $maildir);
my $folder = new Mail::Box::Maildir(folder => $maildir,
@ -46,7 +90,8 @@ sub search($$$) {
push @args, "--duplicate=1" if $remove_dups;
push @args, $query;
empty_maildir($maildir);
check_search_cache_maildir($maildir);
empty_search_cache_maildir($maildir);
open my $pipe, '-|', @args or die "Running @args failed: $!\n";
while (<$pipe>) {
chomp;
@ -121,21 +166,23 @@ sub thread_action($$@) {
my $mid = get_message_id();
if (! defined $mid) {
empty_maildir($results_dir);
die "notmuch-mutt: cannot find Message-Id, abort.\n";
}
my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid");
my $tid = `$search_cmd`; # get thread id
chomp($tid);
search($results_dir, $remove_dups, $tid);
$mid =~ s/ //g; # notmuch strips spaces before storing Message-Id
$mid =~ s/"/""""/g; # escape all double quote characters twice
search($results_dir, $remove_dups, qq{thread:"{id:""$mid""}"});
}
sub tag_action(@) {
my $mid = get_message_id();
defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
system("notmuch", "tag", @_, "--", "id:$mid");
$mid =~ s/ //g; # notmuch strips spaces before storing Message-Id
$mid =~ s/"/""/g; # escape all double quote characters
system("notmuch", "tag", @_, "--", qq{id:"$mid"});
}
sub die_usage() {

39
debian/changelog vendored
View file

@ -1,3 +1,42 @@
notmuch (0.38-2) unstable; urgency=medium
* Restrict autopkgtests to amd64 and aarch64. There are failures in
remaining architectures, but the same tests pass at build time, so any
bugs are probably related to either the autopkgtest environment, or
the (new) upstream test runner for installed notmuch.
-- David Bremner <bremner@debian.org> Wed, 13 Sep 2023 19:55:00 -0300
notmuch (0.38-1) unstable; urgency=medium
* New upstream release
* Bug fix: "FTBFS: 6 tests failed.", thanks to Aurelien Jarno (Closes:
#1051111).
* Run most of upstream test suite as autopkgtests
-- David Bremner <bremner@debian.org> Tue, 12 Sep 2023 08:33:24 -0300
notmuch (0.38~rc2-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Sun, 03 Sep 2023 09:10:24 -0300
notmuch (0.38~rc1-1) experimental; urgency=medium
* New upstream release candidate
* Hopefully reduce/eliminate intermittent failures of T460 by
controlling Emacs native compilation.
* Disable T810-tsan on ppc64el
-- David Bremner <bremner@debian.org> Sat, 26 Aug 2023 08:31:21 -0300
notmuch (0.38~rc0-1) experimental; urgency=medium
* New upstream release candidate
-- David Bremner <bremner@debian.org> Thu, 24 Aug 2023 10:56:06 -0300
notmuch (0.37-1) unstable; urgency=medium
* New upstream release.

1
debian/control vendored
View file

@ -227,7 +227,6 @@ Architecture: all
Depends:
libmail-box-perl,
libmailtools-perl,
libstring-shellquote-perl,
libterm-readline-gnu-perl,
notmuch (>= 0.4),
${misc:Depends},

8
debian/rules vendored
View file

@ -1,4 +1,9 @@
#!/usr/bin/make -f
include /usr/share/dpkg/architecture.mk
ifeq ($(DEB_HOST_ARCH),ppc64el)
export NOTMUCH_SKIP_TESTS = T810-tsan
endif
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
@ -7,7 +12,7 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
override_dh_auto_configure:
BASHCMD=/bin/bash ./configure --prefix=/usr \
--libdir=/usr/lib/$$(dpkg-architecture -q DEB_TARGET_MULTIARCH) \
--libdir=/usr/lib/${DEB_TARGET_MULTIARCH} \
--includedir=/usr/include \
--mandir=/usr/share/man \
--infodir=/usr/share/info \
@ -24,7 +29,6 @@ override_dh_auto_build:
override_dh_auto_clean:
dh_auto_clean
PYBUILD_NAME=notmuch dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python
PYBUILD_NAME=notmuch2 dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python-cffi
dh_auto_clean --sourcedirectory bindings/ruby
$(MAKE) -C contrib/notmuch-mutt clean

18
debian/tests/control vendored Normal file
View file

@ -0,0 +1,18 @@
Test-command: env NOTMUCH_TEST_INSTALLED=1 TERM=dumb
NOTMUCH_HAVE_MAN=1 NOTMUCH_HAVE_SFSEXP=1 NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK=1
NOTMUCH_HAVE_PYTHON3_CFFI=1 NOTMUCH_HAVE_PYTHON3_PYTEST=1
NOTMUCH_HAVE_ASAN=1 NOTMUCH_HAVE_TSAN=1
./test/notmuch-test
Restrictions: allow-stderr
Architecture: amd64, arm64
Depends: @,
build-essential,
dtach,
emacs-nox,
gdb,
git,
gnupg,
gpgsm,
libtalloc-dev,
man,
xapian-tools

View file

@ -76,6 +76,7 @@ message = {
# (format_message_sprinter)
id: messageid,
match: bool,
excluded: bool,
filename: [string*],
timestamp: unix_time, # date header as unix time
date_relative: string, # user-friendly timestamp

View file

@ -159,3 +159,5 @@ $(dir)/config.dox: version.stamp
CLEAN := $(CLEAN) $(DOCBUILDDIR) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.stamp
CLEAN := $(CLEAN) $(MAN_GZIP_FILES) $(MAN_ROFF_FILES) $(dir)/conf.pyc $(dir)/config.dox
CLEAN := $(CLEAN) $(dir)/__pycache__

View file

@ -16,7 +16,7 @@ master_doc = 'index'
# General information about the project.
project = u'notmuch'
copyright = u'2009-2022, Carl Worth and many others'
copyright = u'2009-2023, Carl Worth and many others'
location = os.path.dirname(__file__)

View file

@ -122,6 +122,16 @@ paths are presumed relative to `$HOME` for items in section
Default tag prefix (filter) for :any:`notmuch-git`.
.. nmconfig:: index.as_text
List of regular expressions (without delimiters) for MIME types to
be indexed as text. Currently this applies only to attachments. By
default the regex matches anywhere in the content type; if the
user wants an anchored match, they should include anchors in their
regexes.
History: This configuration value was introduced in notmuch 0.38.
.. nmconfig:: index.decrypt
Policy for decrypting encrypted messages during indexing. Must be

View file

@ -325,11 +325,11 @@ If it is unset, 'default' is assumed.
.. envvar:: NOTMUCH_GIT_DIR
Default location of git repository. Overriden by :option:`--git-dir`.
Default location of git repository. Overridden by :option:`--git-dir`.
.. envvar:: NOTMUCH_GIT_PREFIX
Default tag prefix (filter). Overriden by :option:`--tag-prefix`.
Default tag prefix (filter). Overridden by :option:`--tag-prefix`.
SEE ALSO
========

View file

@ -43,7 +43,7 @@ Supported options for **search** include
.. option:: --output=(summary|threads|messages|files|tags)
summary
summary (default)
Output a summary of each thread with any message matching the
search terms. The summary includes the thread ID, date, the
number of messages in the thread (both the number matched and

View file

@ -130,6 +130,15 @@ Supported options for **show** include
By default, results will be displayed in reverse chronological
order, (that is, the newest results will be displayed first).
.. option:: --offset=[-]N
Skip displaying the first N results. With the leading '-', start
at the Nth result from the end.
.. option:: --limit=N
Limit the number of displayed results to N.
.. option:: --verify
Compute and report the validity of any MIME cryptographic

View file

@ -274,7 +274,7 @@ EXAMPLES
Matches any word starting with "prelim", inside a message subject.
``(subject (starts-wih quick) "brown fox")``
``(subject (starts-with quick) "brown fox")``
Match messages whose subject contains "quick brown fox", but also
"brown fox quicksand".
@ -336,7 +336,7 @@ user defined fields is permitted within a macro.
NOTES
=====
.. [#macro-details] Technically macros impliment lazy evaluation and
.. [#macro-details] Technically macros implement lazy evaluation and
lexical scope. There is one top level scope
containing all macro definitions, but all
parameter definitions are local to a given macro.
@ -347,10 +347,10 @@ NOTES
.. [#aka-bool] a.k.a. boolean prefixes
.. [#not-phrase] Due to the implemention of phrase fields in Xapian,
.. [#not-phrase] Due to the implementation of phrase fields in Xapian,
regex queries could only match individual words.
.. [#not-body] Due the the way ``body`` is implemented in notmuch,
.. [#not-body] Due to the way ``body`` is implemented in notmuch,
this modifier is not supported in the ``body`` field.
.. [#not-path] Due to the way recursive ``path`` queries are implemented

View file

@ -14,7 +14,7 @@ manual to refer to the Emacs interface to Notmuch. When this distinction
is important, well refer to the Emacs interface as
*notmuch-emacs*.
Notmuch-emacs is highly customizable via the the Emacs customization
Notmuch-emacs is highly customizable via the Emacs customization
framework (or just by setting the appropriate variables). We try to
point out relevant variables in this manual, but in order to avoid
duplication of information, you can usually find the most detailed
@ -493,7 +493,7 @@ in :ref:`notmuch-search`.
Dealing with duplicates
-----------------------
If there are are multiple files with the same :mailheader:`Message-ID`
If there are multiple files with the same :mailheader:`Message-ID`
(see :any:`duplicate-files`), then :any:`notmuch-show` displays the
number of duplicates and identifies the current duplicate. In the
following example duplicate 3 of 5 is displayed.
@ -606,6 +606,45 @@ can be controlled by the variable ``notmuch-search-oldest-first``.
See also :el:defcustom:`notmuch-search-result-format` and
:el:defcustom:`notmuch-unthreaded-result-format`.
.. _notmuch-tree-outline:
notmuch-tree-outline
--------------------
When this mode is set, each thread and subthread in the results
list is treated as a foldable section, with its first message as
its header.
The mode just makes available in the tree buffer all the
keybindings in info:emacs#Outline_Mode, and binds the following
additional keys:
.. el:define-key:: <tab>
Cycle visibility state of the current message's tree.
.. el:define-key:: <M-tab>
Cycle visibility state of all trees in the buffer.
The behaviour of this minor mode is affected by the following
customizable variables:
.. el:defcustom:: notmuch-tree-outline-enabled
|docstring::notmuch-tree-outline-enabled|
.. el:defcustom:: notmuch-tree-outline-visibility
|docstring::notmuch-tree-outline-visibility|
.. el:defcustom:: notmuch-tree-outline-auto-close
|docstring::notmuch-tree-outline-auto-close|
.. el:defcustom:: notmuch-tree-outline-open-on-next
|docstring::notmuch-tree-outline-open-on-next|
.. _notmuch-unthreaded:
@ -678,7 +717,7 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
notmuch-tag-undo
----------------
Each notmuch buffer supporting tagging operations (i.e buffers in
Each notmuch buffer supporting tagging operations (i.e. buffers in
:any:`notmuch-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
:any:`notmuch-unthreaded` mode) keeps a local stack of tagging
operations. These can be undone via :any:`notmuch-tag-undo`. By default

View file

@ -416,11 +416,6 @@ moved to the \"To:\" header."
(let ((user-agent (funcall notmuch-mua-user-agent-function)))
(unless (string-empty-p user-agent)
(push (cons 'User-Agent user-agent) other-headers))))
(unless (assq 'From other-headers)
(push (cons 'From (message-make-from
(notmuch-user-name)
(notmuch-user-primary-email)))
other-headers))
(notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
(or switch-function
(notmuch-mua-get-switch-function)))
@ -439,6 +434,11 @@ moved to the \"To:\" header."
;; Cause `message-setup-1' to do things relevant for mail,
;; such as observe `message-default-mail-headers'.
(message-this-is-mail t))
(unless (assq 'From headers)
(push (cons 'From (message-make-from
(notmuch-user-name)
(notmuch-user-primary-email)))
headers))
(message-setup-1 headers yank-action send-actions return-action))
(notmuch-fcc-header-setup)
(notmuch-mua--remove-dont-reply-to-names)

View file

@ -452,14 +452,19 @@ operation on the contents of the current buffer."
(defun notmuch-show-update-tags (tags)
"Update the displayed tags of the current message."
(save-excursion
(goto-char (notmuch-show-message-top))
(when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t)
(let ((inhibit-read-only t))
(replace-match (concat "("
(notmuch-tag-format-tags
tags
(notmuch-show-get-prop :orig-tags))
")"))))))
(let ((inhibit-read-only t)
(start (notmuch-show-message-top))
(depth (notmuch-show-get-prop :depth))
(orig-tags (notmuch-show-get-prop :orig-tags))
(props (notmuch-show-get-message-properties))
(extent (notmuch-show-message-extent)))
(goto-char start)
(notmuch-show-insert-headerline props depth tags orig-tags)
(put-text-property start (1+ start)
:notmuch-message-properties props)
(put-text-property (car extent) (cdr extent) :notmuch-message-extent extent)
;; delete original headerline, but do not save to kill ring
(delete-region (point) (1+ (line-end-position))))))
(defun notmuch-clean-address (address)
"Try to clean a single email ADDRESS for display. Return a cons
@ -530,11 +535,17 @@ Return unchanged ADDRESS if parsing fails."
(plist-put msg :height height)
height))))
(defun notmuch-show-insert-headerline (headers date tags depth duplicate file-count)
(defun notmuch-show-insert-headerline (msg-plist depth tags &optional orig-tags)
"Insert a notmuch style headerline based on HEADERS for a
message at DEPTH in the current thread."
(let ((start (point))
(from (notmuch-sanitize
(let* ((start (point))
(headers (plist-get msg-plist :headers))
(duplicate (or (plist-get msg-plist :duplicate) 0))
(file-count (length (plist-get msg-plist :filename)))
(date (or (and notmuch-show-relative-dates
(plist-get msg-plist :date_relative))
(plist-get headers :Date)))
(from (notmuch-sanitize
(notmuch-show-clean-address (plist-get headers :From)))))
(when (string-match "\\cR" from)
;; If the From header has a right-to-left character add
@ -549,7 +560,7 @@ message at DEPTH in the current thread."
" ("
date
") ("
(notmuch-tag-format-tags tags tags)
(notmuch-tag-format-tags tags (or orig-tags tags))
")")
(insert
(if (> file-count 1)
@ -1171,8 +1182,6 @@ is out of range."
(defun notmuch-show-insert-msg (msg depth)
"Insert the message MSG at depth DEPTH in the current thread."
(let* ((headers (plist-get msg :headers))
(duplicate (or (plist-get msg :duplicate) 0))
(files (length (plist-get msg :filename)))
;; Indentation causes the buffer offset of the start/end
;; points to move, so we must use markers.
message-start message-end
@ -1180,11 +1189,7 @@ is out of range."
headers-start headers-end
(bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
(setq message-start (point-marker))
(notmuch-show-insert-headerline headers
(or (and notmuch-show-relative-dates
(plist-get msg :date_relative))
(plist-get headers :Date))
(plist-get msg :tags) depth duplicate files)
(notmuch-show-insert-headerline msg depth (plist-get msg :tags))
(setq content-start (point-marker))
;; Set `headers-start' to point after the 'Subject:' header to be
;; compatible with the existing implementation. This just sets it

View file

@ -1014,7 +1014,10 @@ unchanged ADDRESS if parsing fails."
A message tree is another name for a single sub-thread: i.e., a
message together with all its descendents."
(let ((msg (car tree))
(replies (cadr tree)))
(replies (cadr tree))
;; outline level, computed from the message's depth and
;; whether or not it's the first message in the tree.
(level (1+ (if (and (eq 0 depth) (not first)) 1 depth))))
(cond
((and (< 0 depth) (not last))
(push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status))
@ -1034,6 +1037,7 @@ message together with all its descendents."
(setq msg (plist-put msg :first (and first (eq 0 depth))))
(setq msg (plist-put msg :tree-status tree-status))
(setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
(setq msg (plist-put msg :level level))
(notmuch-tree-goto-and-insert-msg msg)
(pop tree-status)
(pop tree-status)
@ -1080,7 +1084,8 @@ Complete list of currently available key bindings:
(setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
(hl-line-mode 1)
(setq buffer-read-only t)
(setq truncate-lines t))
(setq truncate-lines t)
(when notmuch-tree-outline-enabled (notmuch-tree-outline-mode 1)))
(defvar notmuch-tree-process-exit-functions nil
"Functions called when the process inserting a tree of results finishes.
@ -1278,6 +1283,180 @@ search results and that are also tagged with the given TAG."
nil
notmuch-search-oldest-first)))
;;; Tree outline mode
;;;; Custom variables
(defcustom notmuch-tree-outline-enabled nil
"Whether to automatically activate `notmuch-tree-outline-mode' in tree views."
:type 'boolean)
(defcustom notmuch-tree-outline-visibility 'hide-others
"Default state of the forest outline for `notmuch-tree-outline-mode'.
This variable controls the state of a forest initially and after
a movement command. If set to nil, all trees are displayed while
the symbol hide-all indicates that all trees in the forest should
be folded and hide-other that only the first one should be
unfolded."
:type '(choice (const :tag "Show all" nil)
(const :tag "Hide others" hide-others)
(const :tag "Hide all" hide-all)))
(defcustom notmuch-tree-outline-auto-close nil
"Close message and tree windows when moving past the last message."
:type 'boolean)
(defcustom notmuch-tree-outline-open-on-next nil
"Open new messages under point if they are closed when moving to next one.
When this flag is set, using the command
`notmuch-tree-outline-next' with point on a header for a new
message that is not shown will open its `notmuch-show' buffer
instead of moving point to next matching message."
:type 'boolean)
;;;; Helper functions
(defsubst notmuch-tree-outline--pop-at-end (pop-at-end)
(if notmuch-tree-outline-auto-close (not pop-at-end) pop-at-end))
(defun notmuch-tree-outline--set-visibility ()
(when (and notmuch-tree-outline-mode (> (point-max) (point-min)))
(cl-case notmuch-tree-outline-visibility
(hide-others (notmuch-tree-outline-hide-others))
(hide-all (outline-hide-body)))))
(defun notmuch-tree-outline--on-exit (proc)
(when (eq (process-status proc) 'exit)
(notmuch-tree-outline--set-visibility)))
(add-hook 'notmuch-tree-process-exit-functions #'notmuch-tree-outline--on-exit)
(defsubst notmuch-tree-outline--level (&optional props)
(or (plist-get (or props (notmuch-tree-get-message-properties)) :level) 0))
(defsubst notmuch-tree-outline--message-open-p ()
(and (buffer-live-p notmuch-tree-message-buffer)
(get-buffer-window notmuch-tree-message-buffer)
(let ((id (notmuch-tree-get-message-id)))
(and id
(with-current-buffer notmuch-tree-message-buffer
(string= (notmuch-show-get-message-id) id))))))
(defsubst notmuch-tree-outline--at-original-match-p ()
(and (notmuch-tree-get-prop :match)
(equal (notmuch-tree-get-prop :orig-tags)
(notmuch-tree-get-prop :tags))))
(defun notmuch-tree-outline--next (prev thread pop-at-end &optional open-new)
(cond (thread
(notmuch-tree-thread-top)
(if prev
(outline-backward-same-level 1)
(outline-forward-same-level 1))
(when (> (notmuch-tree-outline--level) 0) (outline-show-branches))
(notmuch-tree-outline--next nil nil pop-at-end t))
((and (or open-new notmuch-tree-outline-open-on-next)
(notmuch-tree-outline--at-original-match-p)
(not (notmuch-tree-outline--message-open-p)))
(notmuch-tree-outline-hide-others t))
(t (outline-next-visible-heading (if prev -1 1))
(unless (notmuch-tree-get-prop :match)
(notmuch-tree-matching-message prev pop-at-end))
(notmuch-tree-outline-hide-others t))))
;;;; User commands
(defun notmuch-tree-outline-hide-others (&optional and-show)
"Fold all threads except the one around point.
If AND-SHOW is t, make the current message visible if it's not."
(interactive)
(save-excursion
(while (and (not (bobp)) (> (notmuch-tree-outline--level) 1))
(outline-previous-heading))
(outline-hide-sublevels 1))
(when (> (notmuch-tree-outline--level) 0)
(outline-show-subtree)
(when and-show (notmuch-tree-show-message nil))))
(defun notmuch-tree-outline-next (&optional pop-at-end)
"Next matching message in a forest, taking care of thread visibility.
A prefix argument reverses the meaning of `notmuch-tree-outline-auto-close'."
(interactive "P")
(let ((pop (notmuch-tree-outline--pop-at-end pop-at-end)))
(if (null notmuch-tree-outline-visibility)
(notmuch-tree-matching-message nil pop)
(notmuch-tree-outline--next nil nil pop))))
(defun notmuch-tree-outline-previous (&optional pop-at-end)
"Previous matching message in forest, taking care of thread visibility.
With prefix, quit the tree view if there is no previous message."
(interactive "P")
(if (null notmuch-tree-outline-visibility)
(notmuch-tree-prev-matching-message pop-at-end)
(notmuch-tree-outline--next t nil pop-at-end)))
(defun notmuch-tree-outline-next-thread ()
"Next matching thread in forest, taking care of thread visibility."
(interactive)
(if (null notmuch-tree-outline-visibility)
(notmuch-tree-next-thread)
(notmuch-tree-outline--next nil t nil)))
(defun notmuch-tree-outline-previous-thread ()
"Previous matching thread in forest, taking care of thread visibility."
(interactive)
(if (null notmuch-tree-outline-visibility)
(notmuch-tree-prev-thread)
(notmuch-tree-outline--next t t nil)))
;;;; Mode definition
(defvar notmuch-tree-outline-mode-lighter nil
"The lighter mark for notmuch-tree-outline mode.
Usually empty since outline-minor-mode's lighter will be active.")
(define-minor-mode notmuch-tree-outline-mode
"Minor mode allowing message trees to be folded as outlines.
When this mode is set, each thread and subthread in the results
list is treated as a foldable section, with its first message as
its header.
The mode just makes available in the tree buffer all the
keybindings in `outline-minor-mode', and binds the following
additional keys:
\\{notmuch-tree-outline-mode-map}
The customizable variable `notmuch-tree-outline-visibility'
controls how navigation in the buffer is affected by this mode:
- If it is set to nil, `notmuch-tree-outline-previous',
`notmuch-tree-outline-next', and their thread counterparts
behave just as the corresponding notmuch-tree navigation keys
when this mode is not enabled.
- If, on the other hand, `notmuch-tree-outline-visibility' is
set to a non-nil value, these commands hiding the outlines of
the trees you are not reading as you move to new messages.
To enable notmuch-tree-outline-mode by default in all
notmuch-tree buffers, just set
`notmuch-tree-outline-mode-enabled' to t."
:lighter notmuch-tree-outline-mode-lighter
:keymap `((,(kbd "TAB") . outline-cycle)
(,(kbd "M-TAB") . outline-cycle-buffer)
("n" . notmuch-tree-outline-next)
("p" . notmuch-tree-outline-previous)
(,(kbd "M-n") . notmuch-tree-outline-next-thread)
(,(kbd "M-p") . notmuch-tree-outline-previous-thread))
(outline-minor-mode notmuch-tree-outline-mode)
(unless (derived-mode-p 'notmuch-tree-mode)
(user-error "notmuch-tree-outline-mode is only meaningful for notmuch trees!"))
(if notmuch-tree-outline-mode
(progn (setq-local outline-regexp "^[^\n]+")
(setq-local outline-level #'notmuch-tree-outline--level)
(notmuch-tree-outline--set-visibility))
(setq-local outline-regexp (default-value 'outline-regexp))
(setq-local outline-level (default-value 'outline-level))))
;;; _
(provide 'notmuch-tree)

View file

@ -841,6 +841,7 @@ non-authors is found, assume that all of the authors match."
overlay)
(insert invisible-string)
(setq overlay (make-overlay start (point)))
(overlay-put overlay 'evaporate t)
(overlay-put overlay 'invisible 'ellipsis)
(overlay-put overlay 'isearch-open-invisible #'delete-overlay)))
(insert padding))))

View file

@ -599,6 +599,8 @@ _notmuch_config_key_to_string (notmuch_config_key_t key)
return "database.autocommit";
case NOTMUCH_CONFIG_EXTRA_HEADERS:
return "show.extra_headers";
case NOTMUCH_CONFIG_INDEX_AS_TEXT:
return "index.as_text";
default:
return NULL;
}
@ -642,6 +644,7 @@ _notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
else
email = _get_email_from_passwd_file (notmuch);
return email;
case NOTMUCH_CONFIG_INDEX_AS_TEXT:
case NOTMUCH_CONFIG_NEW_IGNORE:
return "";
case NOTMUCH_CONFIG_AUTOCOMMIT:

View file

@ -291,6 +291,10 @@ struct _notmuch_database {
/* Track what parameters were specified when opening */
notmuch_open_param_t params;
/* list of regular expressions to check for text indexing */
regex_t *index_as_text;
size_t index_as_text_length;
};
/* Prior to database version 3, features were implied by the database

View file

@ -1454,9 +1454,11 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
&message);
if (status == NOTMUCH_STATUS_SUCCESS && message) {
status = _notmuch_message_remove_filename (message, filename);
if (notmuch_message_count_files (message) > 1) {
status = _notmuch_message_remove_filename (message, filename);
}
if (status == NOTMUCH_STATUS_SUCCESS)
_notmuch_message_delete (message);
status = _notmuch_message_delete (message);
else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
_notmuch_message_sync (message);
@ -1573,3 +1575,15 @@ notmuch_database_status_string (const notmuch_database_t *notmuch)
{
return notmuch->status_string;
}
bool
_notmuch_database_indexable_as_text (notmuch_database_t *notmuch, const char *mime_string)
{
for (size_t i = 0; i < notmuch->index_as_text_length; i++) {
if (regexec (&notmuch->index_as_text[i], mime_string, 0, NULL, 0) == 0) {
return true;
}
}
return false;
}

View file

@ -380,6 +380,23 @@ _index_pkcs7_part (notmuch_message_t *message,
GMimeObject *part,
_notmuch_message_crypto_t *msg_crypto);
static bool
_indexable_as_text (notmuch_message_t *message, GMimeObject *part)
{
GMimeContentType *content_type = g_mime_object_get_content_type (part);
notmuch_database_t *notmuch = notmuch_message_get_database (message);
if (content_type) {
char *mime_string = g_mime_content_type_get_mime_type (content_type);
if (mime_string) {
bool ret = _notmuch_database_indexable_as_text (notmuch, mime_string);
g_free (mime_string);
return ret;
}
}
return false;
}
/* Callback to generate terms for each mime part of a message. */
static void
_index_mime_part (notmuch_message_t *message,
@ -497,9 +514,11 @@ _index_mime_part (notmuch_message_t *message,
_notmuch_message_add_term (message, "tag", "attachment");
_notmuch_message_gen_terms (message, "attachment", filename);
/* XXX: Would be nice to call out to something here to parse
* the attachment into text and then index that. */
goto DONE;
if (! _indexable_as_text (message, part)) {
/* XXX: Would be nice to call out to something here to parse
* the attachment into text and then index that. */
goto DONE;
}
}
byte_array = g_byte_array_new ();

View file

@ -25,6 +25,20 @@
#include "database-private.h"
#include "message-private.h"
#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
static void
_log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error)
{
notmuch_database_t *notmuch = notmuch_message_get_database (message);
_notmuch_database_log (notmuch,
"A Xapian exception occurred at %s: %s\n",
where,
error.get_msg ().c_str ());
notmuch->exception_reported = true;
}
notmuch_status_t
notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value)
{
@ -83,10 +97,15 @@ _notmuch_message_modify_property (notmuch_message_t *message, const char *key, c
term = talloc_asprintf (message, "%s=%s", key, value);
if (delete_it)
private_status = _notmuch_message_remove_term (message, "property", term);
else
private_status = _notmuch_message_add_term (message, "property", term);
try {
if (delete_it)
private_status = _notmuch_message_remove_term (message, "property", term);
else
private_status = _notmuch_message_add_term (message, "property", term);
} catch (Xapian::Error &error) {
LOG_XAPIAN_EXCEPTION (message, error);
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
if (private_status)
return COERCE_STATUS (private_status,
@ -123,15 +142,22 @@ _notmuch_message_remove_all_properties (notmuch_message_t *message, const char *
if (status)
return status;
_notmuch_message_invalidate_metadata (message, "property");
if (key)
term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
prefix ? "" : "=");
else
term_prefix = _find_prefix ("property");
/* XXX better error reporting ? */
_notmuch_message_remove_terms (message, term_prefix);
try {
/* XXX better error reporting ? */
_notmuch_message_remove_terms (message, term_prefix);
} catch (Xapian::Error &error) {
LOG_XAPIAN_EXCEPTION (message, error);
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
if (! _notmuch_message_frozen (message))
_notmuch_message_sync (message);
return NOTMUCH_STATUS_SUCCESS;
}

View file

@ -719,6 +719,8 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
/* Ignore failure to remove non-existent term. */
}
}
_notmuch_message_invalidate_metadata (message, "property");
}
@ -941,6 +943,7 @@ _notmuch_message_add_filename (notmuch_message_t *message,
{
const char *relative, *directory;
notmuch_status_t status;
notmuch_private_status_t private_status;
void *local = talloc_new (message);
char *direntry;
@ -964,10 +967,17 @@ _notmuch_message_add_filename (notmuch_message_t *message,
/* New file-direntry allows navigating to this message with
* notmuch_directory_get_child_files() . */
status = COERCE_STATUS (_notmuch_message_add_term (message, "file-direntry", direntry),
"adding file-direntry term");
if (status)
return status;
private_status = _notmuch_message_add_term (message, "file-direntry", direntry);
switch (private_status) {
case NOTMUCH_PRIVATE_STATUS_SUCCESS:
break;
case NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG:
_notmuch_database_log (message->notmuch, "filename too long for file-direntry term: %s\n",
filename);
return NOTMUCH_STATUS_PATH_ERROR;
default:
return COERCE_STATUS (private_status, "adding file-direntry term");
}
status = _notmuch_message_add_folder_terms (message, directory);
if (status)
@ -1383,21 +1393,22 @@ _notmuch_message_delete (notmuch_message_t *message)
if (status)
return status;
message->notmuch->writable_xapian_db->delete_document (message->doc_id);
/* if this was a ghost to begin with, we are done */
private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
if (private_status)
return COERCE_STATUS (private_status,
"Error trying to determine whether message was a ghost");
if (is_ghost)
return NOTMUCH_STATUS_SUCCESS;
/* look for a non-ghost message in the same thread */
try {
Xapian::PostingIterator thread_doc, thread_doc_end;
Xapian::PostingIterator mail_doc, mail_doc_end;
/* look for a non-ghost message in the same thread */
/* if this was a ghost to begin with, we are done */
private_status = _notmuch_message_has_term (message, "type", "ghost", &is_ghost);
if (private_status)
return COERCE_STATUS (private_status,
"Error trying to determine whether message was a ghost");
message->notmuch->writable_xapian_db->delete_document (message->doc_id);
if (is_ghost)
return NOTMUCH_STATUS_SUCCESS;
_notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
&thread_doc_end);
_notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end);

View file

@ -259,6 +259,10 @@ _notmuch_database_filename_to_direntry (void *ctx,
notmuch_find_flags_t flags,
char **direntry);
bool
_notmuch_database_indexable_as_text (notmuch_database_t *notmuch,
const char *mime_string);
/* directory.cc */
notmuch_directory_t *

View file

@ -293,6 +293,8 @@ typedef struct _notmuch_indexopts notmuch_indexopts_t;
*
* NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
*
* NOTMUCH_STATUS_PATH_ERROR: filename is too long
*
* NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to create the
* database file (such as permission denied, or file not found,
* etc.), or the database already exists.
@ -720,7 +722,8 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch);
*
* The UUID is a NUL-terminated opaque string that uniquely identifies
* this database. Two revision numbers are only comparable if they
* have the same database UUID.
* have the same database UUID. The string 'uuid' is owned by notmuch
* and should not be freed or modified by the user.
*/
unsigned long
notmuch_database_get_revision (notmuch_database_t *notmuch,
@ -1170,7 +1173,10 @@ notmuch_query_search_threads_st (notmuch_query_t *query, notmuch_threads_t **out
*
* query = notmuch_query_create (database, query_string);
*
* for (messages = notmuch_query_search_messages (query);
* if (notmuch_query_search_messages (query, &messages) != NOTMUCH_STATUS_SUCCESS)
* return EXIT_FAILURE;
*
* for (;
* notmuch_messages_valid (messages);
* notmuch_messages_move_to_next (messages))
* {
@ -2558,6 +2564,7 @@ typedef enum {
NOTMUCH_CONFIG_USER_NAME,
NOTMUCH_CONFIG_AUTOCOMMIT,
NOTMUCH_CONFIG_EXTRA_HEADERS,
NOTMUCH_CONFIG_INDEX_AS_TEXT,
NOTMUCH_CONFIG_LAST
} notmuch_config_key_t;

View file

@ -320,6 +320,8 @@ _alloc_notmuch (const char *database_path, const char *config_path, const char *
notmuch->transaction_count = 0;
notmuch->transaction_threshold = 0;
notmuch->view = 1;
notmuch->index_as_text = NULL;
notmuch->index_as_text_length = 0;
notmuch->params = NOTMUCH_PARAM_NONE;
if (database_path)
@ -427,6 +429,53 @@ _load_database_state (notmuch_database_t *notmuch)
notmuch, notmuch->xapian_db->get_uuid ().c_str ());
}
/* XXX This should really be done lazily, but the error reporting path in the indexing code
* would need to be redone to report any errors.
*/
notmuch_status_t
_ensure_index_as_text (notmuch_database_t *notmuch, char **message)
{
int nregex = 0;
regex_t *regexv = NULL;
if (notmuch->index_as_text)
return NOTMUCH_STATUS_SUCCESS;
for (notmuch_config_values_t *list = notmuch_config_get_values (notmuch,
NOTMUCH_CONFIG_INDEX_AS_TEXT);
notmuch_config_values_valid (list);
notmuch_config_values_move_to_next (list)) {
regex_t *new_regex;
int rerr;
const char *str = notmuch_config_values_get (list);
size_t len = strlen (str);
/* str must be non-empty, because n_c_get_values skips empty
* strings */
assert (len > 0);
regexv = talloc_realloc (notmuch, regexv, regex_t, nregex + 1);
new_regex = &regexv[nregex];
rerr = regcomp (new_regex, str, REG_EXTENDED | REG_NOSUB);
if (rerr) {
size_t error_size = regerror (rerr, new_regex, NULL, 0);
char *error = (char *) talloc_size (str, error_size);
regerror (rerr, new_regex, error, error_size);
IGNORE_RESULT (asprintf (message, "Error in index.as_text: %s: %s\n", error, str));
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
nregex++;
}
notmuch->index_as_text = regexv;
notmuch->index_as_text_length = nregex;
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_finish_open (notmuch_database_t *notmuch,
const char *profile,
@ -531,6 +580,10 @@ _finish_open (notmuch_database_t *notmuch,
if (status)
goto DONE;
status = _ensure_index_as_text (notmuch, &message);
if (status)
goto DONE;
autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
if (unlikely (! autocommit_str)) {
INTERNAL_ERROR ("missing configuration for autocommit");

View file

@ -20,6 +20,7 @@
#include "notmuch-private.h"
#include "database-private.h"
#include "xapian-extra.h"
#include <glib.h> /* GHashTable, GPtrArray */
@ -186,7 +187,7 @@ _notmuch_query_string_to_xapian_query (notmuch_database_t *notmuch,
{
try {
if (query_string == "" || query_string == "*") {
output = Xapian::Query::MatchAll;
output = xapian_query_match_all ();
} else {
output =
notmuch->query_parser->

View file

@ -25,6 +25,7 @@
#include "regexp-fields.h"
#include "notmuch-private.h"
#include "database-private.h"
#include "xapian-extra.h"
notmuch_status_t
compile_regex (regex_t &regexp, const char *str, std::string &msg)
@ -200,7 +201,7 @@ RegexpFieldProcessor::operator() (const std::string & str)
if (str.empty ()) {
if (options & NOTMUCH_FIELD_PROBABILISTIC) {
return Xapian::Query (Xapian::Query::OP_AND_NOT,
Xapian::Query::MatchAll,
xapian_query_match_all (),
Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
} else {
return Xapian::Query (term_prefix);

View file

@ -77,6 +77,8 @@ typedef struct notmuch_show_params {
bool output_body;
int duplicate;
int part;
int offset;
int limit;
_notmuch_crypto_t crypto;
bool include_html;
GMimeStream *out_stream;

View file

@ -278,18 +278,24 @@ notmuch_conffile_open (notmuch_database_t *notmuch,
return NULL;
}
if (config->is_new)
g_key_file_set_comment (config->key_file, NULL, NULL,
toplevel_config_comment, NULL);
for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
const char *name = group_comment_table[i].group_name;
if (! g_key_file_has_group (config->key_file, name)) {
/* Force group to exist before adding comment */
g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
g_key_file_set_comment (config->key_file, name, NULL,
group_comment_table[i].comment, NULL);
if (config->is_new && (i == 0) ) {
const char *comment;
comment = talloc_asprintf (config, "%s\n%s",
toplevel_config_comment,
group_comment_table[i].comment);
g_key_file_set_comment (config->key_file, name, NULL, comment,
NULL);
} else {
g_key_file_set_comment (config->key_file, name, NULL,
group_comment_table[i].comment, NULL);
}
}
}
return config;

View file

@ -254,7 +254,7 @@ def count_messages(prefix=None):
def get_tags(prefix=None):
"Get a list of tags with a given prefix."
(status, stdout, stderr) = _spawn(
args=['notmuch', 'search', '--query=sexp', '--output=tags', _tag_query(prefix)],
args=['notmuch', 'search', '--exclude=false', '--query=sexp', '--output=tags', _tag_query(prefix)],
stdout=_subprocess.PIPE, wait=True)
return [tag for tag in stdout.splitlines()]
@ -719,7 +719,7 @@ class DatabaseCache:
self._known[id] = False
else:
(_, stdout, stderr) = _spawn(
args=['notmuch', 'search', '--output=files', 'id:{0}'.format(id)],
args=['notmuch', 'search', '--exclude=false', '--output=files', 'id:{0}'.format(id)],
stdout=_subprocess.PIPE,
wait=True)
self._known[id] = stdout != None

View file

@ -413,6 +413,10 @@ add_file (notmuch_database_t *notmuch, const char *filename,
case NOTMUCH_STATUS_FILE_NOT_EMAIL:
fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
break;
case NOTMUCH_STATUS_PATH_ERROR:
fprintf (stderr, "Note: Ignoring non-indexable path: %s\n", filename);
(void) print_status_database ("add_file", notmuch, status);
break;
case NOTMUCH_STATUS_FILE_ERROR:
/* Someone renamed/removed the file between scandir and now. */
state->vanished_files++;

View file

@ -1159,6 +1159,18 @@ do_show_threaded (void *ctx,
notmuch_thread_t *thread;
notmuch_messages_t *messages;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
int i;
if (params->offset < 0) {
unsigned count;
notmuch_status_t s = notmuch_query_count_threads (query, &count);
if (print_status_query ("notmuch show", query, s))
return 1;
params->offset += count;
if (params->offset < 0)
params->offset = 0;
}
status = notmuch_query_search_threads (query, &threads);
if (print_status_query ("notmuch show", query, status))
@ -1166,11 +1178,16 @@ do_show_threaded (void *ctx,
sp->begin_list (sp);
for (;
notmuch_threads_valid (threads);
notmuch_threads_move_to_next (threads)) {
for (i = 0;
notmuch_threads_valid (threads) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_threads_move_to_next (threads), i++) {
thread = notmuch_threads_get (threads);
if (i < params->offset) {
notmuch_thread_destroy (thread);
continue;
}
messages = notmuch_thread_get_toplevel_messages (thread);
if (messages == NULL)
@ -1201,6 +1218,18 @@ do_show_unthreaded (void *ctx,
notmuch_message_t *message;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
notmuch_bool_t excluded;
int i;
if (params->offset < 0) {
unsigned count;
notmuch_status_t s = notmuch_query_count_messages (query, &count);
if (print_status_query ("notmuch show", query, s))
return 1;
params->offset += count;
if (params->offset < 0)
params->offset = 0;
}
status = notmuch_query_search_messages (query, &messages);
if (print_status_query ("notmuch show", query, status))
@ -1208,9 +1237,13 @@ do_show_unthreaded (void *ctx,
sp->begin_list (sp);
for (;
notmuch_messages_valid (messages);
notmuch_messages_move_to_next (messages)) {
for (i = 0;
notmuch_messages_valid (messages) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_messages_move_to_next (messages), i++) {
if (i < params->offset) {
continue;
}
sp->begin_list (sp);
sp->begin_list (sp);
@ -1287,6 +1320,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
notmuch_show_params_t params = {
.part = -1,
.duplicate = 0,
.offset = 0,
.limit = -1, /* unlimited */
.omit_excluded = true,
.output_body = true,
.crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
@ -1328,6 +1363,8 @@ notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
{ .opt_bool = &params.output_body, .name = "body" },
{ .opt_bool = &params.include_html, .name = "include-html" },
{ .opt_int = &params.duplicate, .name = "duplicate" },
{ .opt_int = &params.limit, .name = "limit" },
{ .opt_int = &params.offset, .name = "offset" },
{ .opt_inherit = notmuch_shared_options },
{ }
};

View file

@ -24,11 +24,11 @@ Getting set up to run tests:
First, you need to get the corpus. If you don't already have the gpg
key for David Bremner, run
% gpg --search 'david@tethera.net'
% gpg --locate-external-key 'david@tethera.net'
This should get you a key with fingerprint
815B 6398 2A79 F8E7 C727 86C4 762B 57BB 7842 06AD
7A18 807F 100A 4570 C596 8420 7E4E 65C8 720B 706B
(the last 8 digits are printed as the "key id").

View file

@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEkiyHYXwaY0SiY6fqA0U5G1WqFSEFAmR119gACgkQA0U5G1Wq
FSHlSQ/+NSRj27PEZjaP2I+3j+rsMG3pnVckNcuOQyfgjJ+zEagMZyRu3vaIA/pX
xtBrNIX4l4CQIkqwyNjsuqJdzh6S3DeCWSEr1Q+GSBki+wQCBiRuDYY2HQoDezEK
4bMfniEWZpKJD8PfIabz0OOqMUsfXEYMd9kefew5/J4OGnDIv8E5pKfqvDNNO4rW
MhZ9w9uR9wkvmfmpO66kAgTfLllwiyNHWoWnzQfNmqM8eULFn7XxM1PEZShUEqXf
pTWCqqm5OyUcy8f+gy9Mb7DRRvnwLpHHRQlCzzH2c+ENQRpt1ErsgVKpHTVk4UsB
EML+zwyWEaQg7xVKWXRJDuGCF47S1GCQNUtvtx57HJl6Ds6N2mlr2KEGaI7qtiz5
5hdaTc0L/TVN0WS+uCdfdDDozFErf1kwhA6Jnpi0YNNdK+wpFzj7ISvA+DNHwJ75
TLBuJIU/h3QfX3NDC5xDbsWAgpv7a84e7ePO6+kAVkHsNYDbFjiunr5fRbqDsJcJ
B+aVGhKvFZbziC84Dn5Ae9Lpa40fQlxbdb+So2nDIiuR3P33vt7wr/e2ptVfrqkn
a1DM96n03VWexwEDMye3b3rOTXsN5Ul87zucg9xWm5JT75NGuqJ1WDJN/wwNPDro
ZXS1OHh7UKsU1tP2J9+gLiKYNBP4m4BQjEgYXpiYEoge9A1QplQ=
=5/Ep
-----END PGP SIGNATURE-----

View file

@ -1,3 +1,3 @@
# this should be both a valid Makefile fragment and valid POSIX(ish) shell.
PERFTEST_VERSION=0.4
PERFTEST_VERSION=0.5

View file

@ -137,6 +137,23 @@ detection of missing prerequisites. In the future we may treat tests
unable to run because of missing prerequisites, but not explicitly
skipped by the user, as failures.
Testing installed notmuch
-------------------------
Systems integrators (e.g. Linux distros) may wish to test an installed
version of notmuch. This can be done be running
$ NOTMUCH_TEST_INSTALLED=1 ./test/notmuch-test
In this scenario the test suite does not assume a built tree, and in
particular cannot rely on the output of 'configure'. You may want to
set certain feature environment variables ('NOTMUCH_HAVE_*') directly
if you know those apply to your installed notmuch). Consider also
setting TERM=dumb if the value of TERM cannot be used (e.g. in a
chroot with missing terminfo). Note that having a built tree may cause
surprising/broken results for NOTMUCH_TEST_INSTALLED, so consider
cleaning first.
Writing Tests
-------------
The test script is written as a shell script. It is to be named as

View file

@ -66,6 +66,7 @@ test_begin_subtest 'NOTMUCH_CONFIG is set and points to an existing file'
test_expect_success 'test -f "${NOTMUCH_CONFIG}"'
test_begin_subtest 'PATH is set to build directory'
test_subtest_broken_for_installed
test_expect_equal \
"$(dirname ${TEST_DIRECTORY})" \
"$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')"

View file

@ -12,13 +12,16 @@ test_expect_success 'notmuch help'
test_begin_subtest 'notmuch --version'
test_expect_success 'notmuch --version'
if [ $NOTMUCH_HAVE_MAN -eq 1 ]; then
if [ "${NOTMUCH_HAVE_MAN-0}" = "1" ]; then
test_begin_subtest 'notmuch --help tag'
test_expect_success 'notmuch --help tag'
test_begin_subtest 'notmuch help tag'
test_expect_success 'notmuch help tag'
else
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest 'notmuch --help tag (man pages not available)'
test_expect_success 'test_must_fail notmuch --help tag >/dev/null'

View file

@ -57,6 +57,7 @@ database.mail_root=MAIL_DIR
database.path=MAIL_DIR
foo.list=this;is another;list value;
foo.string=this is another string value
index.as_text=
maildir.synchronize_flags=true
new.ignore=
new.tags=unread;inbox

View file

@ -21,7 +21,8 @@ baz
EOF
expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output
test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config
sed '/^$/d' < new-notmuch-config > filtered-config
test_expect_equal_file ${expected_dir}/config-with-comments filtered-config
test_begin_subtest "setup consistent with config-set for single items"
# note this relies on the config state from the previous test.

View file

@ -383,7 +383,22 @@ No new mail. Removed 1 message.
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Long file names have reasonable diagnostics"
printf -v name 'f%.0s' {1..234}
generate_message "[filename]=$name"
notmuch new 2>&1 | notmuch_dir_sanitize >OUTPUT
rm ${MAIL_DIR}/${name}
cat <<EOF > EXPECTED
Note: Ignoring non-indexable path: MAIL_DIR/$name
add_file: Path supplied is illegal for this function
filename too long for file-direntry term: MAIL_DIR/$name
Processed 1 file in almost no time.
No new mail.
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Xapian exception: read only files"
test_subtest_broken_for_root
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@ -455,12 +470,4 @@ Date: Fri, 17 Jun 2016 22:14:41 -0400
EOF
test_expect_equal_file EXPECTED OUTPUT
add_email_corpus indexing
test_begin_subtest "index text/* attachments"
test_subtest_known_broken
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_done

View file

@ -299,6 +299,7 @@ database.backup_dir
database.hook_dir
database.mail_root=MAIL_DIR
database.path
index.as_text=
maildir.synchronize_flags=true
new.ignore=
new.tags=unread;inbox

View file

@ -157,7 +157,7 @@ print("4: {} messages".format(query.count_messages()))
EOF
test_expect_equal_file EXPECTED OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_begin_subtest "and of exact terms (query=sexp)"
output=$(notmuch count --query=sexp '(and "wonderful" "wizard")')

View file

@ -34,6 +34,12 @@ add_message '[subject]="search by id"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"
output=$(notmuch search id:${gen_msg_id} | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)"
test_begin_subtest "Message-Id with spaces"
test_subtest_known_broken
add_message '[id]="message id@example.com"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search --output=messages id:"message id@example.com")
test_expect_equal "$output" "messageid@example.com"
test_begin_subtest "Search by mid:"
add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
output=$(notmuch search mid:${gen_msg_id} | notmuch_search_sanitize)
@ -132,6 +138,7 @@ thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by to (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; subjectsearchtest (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; utf8-sübjéct (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; Message-Id with spaces (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by mid (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by tag (inbox searchbytag unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)

View file

@ -2,7 +2,7 @@
test_description='"notmuch search" in several variations'
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" != "1" ]; then
printf "Skipping due to missing sfsexp library\n"
test_done
fi

View file

@ -325,7 +325,7 @@ cat <<EOF >EXPECTED
EOF
test_expect_equal_file EXPECTED OUTPUT
if [[ NOTMUCH_HAVE_SFSEXP = 1 ]]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_begin_subtest "sexpr query: all messages"
notmuch address '*' > EXPECTED
notmuch address --query=sexp '()' > OUTPUT

81
test/T131-show-limiting.sh Executable file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env bash
test_description='"notmuch show" --offset and --limit parameters'
. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
show () {
local kind="$1"
shift
if [ "$kind" = messages ]; then
set -- --unthreaded "$@"
fi
notmuch show --body=false --format=text --entire-thread=false "$@" "*" |
sed -nre 's/^.message\{.*\<depth:0\>.*/&/p'
}
for outp in messages threads; do
test_begin_subtest "$outp: limit does the right thing"
show $outp | head -n 20 >expected
show $outp --limit=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: concatenation of limited shows"
show $outp | head -n 20 >expected
show $outp --limit=10 >output
show $outp --limit=10 --offset=10 >>output
test_expect_equal_file expected output
test_begin_subtest "$outp: limit larger than result set"
N=$(notmuch count --output=$outp "*")
show $outp >expected
show $outp --limit=$((1 + N)) >output
test_expect_equal_file expected output
test_begin_subtest "$outp: limit = 0"
test_expect_equal "$(show $outp --limit=0)" ""
test_begin_subtest "$outp: offset does the right thing"
# note: tail -n +N is 1-based
show $outp | tail -n +21 >expected
show $outp --offset=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: offset = 0"
show $outp >expected
show $outp --offset=0 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset"
show $outp | tail -n 20 >expected
show $outp --offset=-20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset"
show $outp | tail -n 1 >expected
show $outp --offset=-1 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with limit"
show $outp | tail -n 20 | head -n 10 >expected
show $outp --offset=-20 --limit=10 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with equal limit"
show $outp | tail -n 20 >expected
show $outp --offset=-20 --limit=20 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset combined with large limit"
show $outp | tail -n 20 >expected
show $outp --offset=-20 --limit=50 >output
test_expect_equal_file expected output
test_begin_subtest "$outp: negative offset larger than results"
N=$(notmuch count --output=$outp "*")
show $outp >expected
show $outp --offset=-$((1 + N)) >output
test_expect_equal_file expected output
done
test_done

View file

@ -320,6 +320,7 @@ test_begin_subtest "Tag name beginning with -"
test_expect_code 1 'notmuch tag +- One'
test_begin_subtest "Xapian exception: read only files"
test_subtest_broken_for_root
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@ -327,7 +328,7 @@ test_expect_equal "$output" "A Xapian exception occurred opening database"
add_email_corpus
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or wizard'

View file

@ -65,8 +65,9 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
\"tags\": [\"inbox\",
\"unread\"]}]"
if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_begin_subtest "Search message: json, 64-bit timestamp"
if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
if [ "${NOTMUCH_HAVE_64BIT_TIME_T-0}" != "1" ]; then
test_subtest_known_broken
fi
add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\""
@ -81,6 +82,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
\"query\": [\"id:$gen_msg_id\", null],
\"tags\": [\"inbox\",
\"unread\"]}]"
fi # NOTMUCH_TEST_INSTALLED undefined / empty
test_begin_subtest "Format version: too low"
test_expect_code 20 "notmuch search --format-version=0 \\*"

View file

@ -24,7 +24,7 @@ test_begin_subtest "Basic reply"
notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
test_expect_equal_file basic.expected OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_begin_subtest "Basic reply (query=sexp)"
notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT

View file

@ -118,7 +118,7 @@ notmuch dump -- from:cworth > dump-dash-cworth.actual
test_expect_equal_file dump-cworth.expected dump-dash-cworth.actual
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_begin_subtest "dump --query=sexp -- '(from cworth)'"
notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2
@ -139,6 +139,7 @@ notmuch dump --output=dump-outfile-dash-inbox.actual -- from:cworth
test_expect_equal_file dump-cworth.expected dump-outfile-dash-inbox.actual
test_begin_subtest "Check for a safe set of message-ids"
test_subtest_broken_for_installed
notmuch search --output=messages from:cworth | sed s/^id:// > EXPECTED
notmuch search --output=messages from:cworth | sed s/^id:// |\
$TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT
@ -246,6 +247,7 @@ notmuch dump --format=batch-tag > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest 'format=batch-tag, checking encoded output'
test_subtest_broken_for_installed
NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$test_count

View file

@ -258,6 +258,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)"
test_subtest_broken_for_installed
notmuch new > /dev/null
output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; Testing message sent via SMTP (inbox)"
@ -350,6 +351,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Reply within emacs"
test_subtest_broken_for_installed
test_emacs '(let ((message-hidden-headers ''()))
(notmuch-search "subject:\"testing message sent via SMTP\"")
(notmuch-test-wait)

View file

@ -30,6 +30,7 @@ msg_file=$(notmuch search --output=files subject:signed-message-sent-via-SMTP)
test_expect_equal_message_body sent_message "$msg_file"
test_begin_subtest "signed part content-type indexing"
test_subtest_broken_for_installed
notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT
cat <<EOF >EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)

View file

@ -183,8 +183,9 @@ test_begin_subtest "show PKCS#7 SignedData outputs valid JSON"
output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
test_valid_json "$output"
if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_begin_subtest "Verify signature on PKCS#7 SignedData message"
if [ $NOTMUCH_HAVE_64BIT_TIME_T -ne 1 ]; then
if [ "${NOTMUCH_HAVE_64BIT_TIME_T-0}" != "1" ]; then
test_subtest_known_broken
fi
output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
@ -194,6 +195,7 @@ test_json_nodes <<<"$output" \
'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"'
fi # NOTMUCH_TEST_INSTALLED undefined / empty
test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then

View file

@ -11,6 +11,10 @@ test_description='exception symbol hiding'
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest 'running test' run_test
mkdir -p ${PWD}/fakedb/.notmuch
$TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \

View file

@ -4,6 +4,10 @@ test_description="python bindings"
test_require_external_prereq ${NOTMUCH_PYTHON}
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
add_email_corpus
add_gnupg_home

View file

@ -2,10 +2,13 @@
test_description="python bindings (pytest)"
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
if [ "${NOTMUCH_HAVE_PYTHON3_CFFI-0}" = "0" -o "${NOTMUCH_HAVE_PYTHON3_PYTEST-0}" = "0" ]; then
test_done
fi
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage

View file

@ -2,7 +2,7 @@
test_description="python bindings (notmuch test suite)"
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; then
if [ "${NOTMUCH_HAVE_PYTHON3_CFFI-0}" = "0" -o "${NOTMUCH_HAVE_PYTHON3_PYTEST-0}" = "0" ]; then
test_done
fi

View file

@ -2,7 +2,7 @@
test_description="ruby bindings"
. $(dirname "$0")/test-lib.sh || exit 1
if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
if [ -z "${NOTMUCH_TEST_INSTALLED-}" -a "${NOTMUCH_HAVE_RUBY_DEV-0}" = "0" ]; then
test_subtest_missing_external_prereq_["ruby development files"]=t
fi
@ -12,10 +12,14 @@ test_ruby() {
(
cat <<-EOF
require 'notmuch'
db = Notmuch::Database.new('$MAIL_DIR')
db = Notmuch::Database.new()
EOF
cat
) | $NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"> OUTPUT
) | if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
ruby
else
$NOTMUCH_RUBY -I "$NOTMUCH_BUILDDIR/bindings/ruby"
fi> OUTPUT
test_expect_equal_file EXPECTED OUTPUT
}

View file

@ -2,6 +2,10 @@
test_description="argument parsing"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "sanity check"
$TEST_DIRECTORY/arg-test pos1 --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
cat <<EOF > EXPECTED

View file

@ -200,6 +200,30 @@ test_emacs '(test-log-error
(notmuch-tree "*")))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE"
# reinitialize database for outline tests
add_email_corpus
test_begin_subtest "start in outline mode"
test_emacs '(let ((notmuch-tree-outline-enabled t))
(notmuch-tree "tag:inbox")
(notmuch-test-wait)
(test-visible-output))'
# folding all messages by height or depth should look the same
test_expect_equal_file $EXPECTED/inbox-outline OUTPUT
test_begin_subtest "outline-cycle-buffer"
test_emacs '(let ((notmuch-tree-outline-enabled t))
(notmuch-tree "tag:inbox")
(notmuch-test-wait)
(outline-cycle-buffer)
(outline-cycle-buffer)
(notmuch-test-wait)
(test-visible-output))'
# folding all messages by height or depth should look the same
test_expect_equal_file $EXPECTED/notmuch-tree-tag-inbox OUTPUT
test_done
add_email_corpus duplicate
ID3=87r2ecrr6x.fsf@zephyr.silentflame.com

View file

@ -2,6 +2,10 @@
test_description="hex encoding and decoding"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "round trip"
find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT

View file

@ -2,6 +2,10 @@
test_description="date/time parser module"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
# Sanity/smoke tests for the date/time parser independent of notmuch
_date () {

View file

@ -35,7 +35,7 @@ notmuch show --entire-thread=true --sort=oldest-first $QUERY > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_query_syntax '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or wizard'

View file

@ -3,6 +3,10 @@ test_description="database version and feature compatibility"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "future database versions abort open"
${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/')

View file

@ -3,6 +3,10 @@ test_description="API tests for notmuch_message_*"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED}" ]; then
test_done
fi
add_email_corpus
test_begin_subtest "building database"
@ -516,4 +520,31 @@ cat <<EOF > EXPECTED
EOF
test_expect_equal_file EXPECTED OUTPUT
TERMLIST_PATH=(${MAIL_DIR}/.notmuch/xapian/termlist.*)
test_begin_subtest "remove message with corrupted db"
backup_database
cat c_head0 - c_tail <<'EOF' | test_private_C ${MAIL_DIR} ${TERMLIST_PATH}
{
notmuch_status_t status;
int fd = open(argv[2],O_WRONLY|O_TRUNC);
if (fd < 0) {
fprintf (stderr, "error opening %s\n", argv[1]);
exit (1);
}
stat = _notmuch_message_delete (message);
printf ("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
== stdout ==
1
== stderr ==
A Xapian exception occurred at message.cc:XXX: EOF reading block YYY
EOF
sed 's/EOF reading block [0-9]*/EOF reading block YYY/' < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED OUTPUT.clean
restore_database
test_done

View file

@ -95,7 +95,7 @@ subtotal=$(notmuch count lastmod:..$lastmod)
result=$(($subtotal == $total-1))
test_expect_equal 1 "$result"
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
test_begin_subtest 'exclude one message using negative lastmod (sexp)'
total=$(notmuch count '*')
notmuch tag +${RANDOM} id:4EFC743A.3060609@april.org

View file

@ -440,6 +440,7 @@ cat <<'EOF' >EXPECTED
10: 'USER_FULL_NAME'
11: '8000'
12: 'NULL'
13: ''
== stderr ==
EOF
unset MAILDIR
@ -725,6 +726,7 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "list by keys (ndlc)"
notmuch config set search.exclude_tags "foo;bar;fub"
notmuch config set new.ignore "sekrit_junk"
notmuch config set index.as_text "text/"
cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
{
notmuch_config_key_t key;
@ -751,6 +753,7 @@ cat <<'EOF' >EXPECTED
10: 'Notmuch Test Suite'
11: '8000'
12: 'NULL'
13: 'text/'
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
@ -785,6 +788,7 @@ cat <<'EOF' >EXPECTED
10: 'USER_FULL_NAME'
11: '8000'
12: 'NULL'
13: ''
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT.clean
@ -856,6 +860,7 @@ database.backup_dir MAIL_DIR/.notmuch/backups
database.hook_dir MAIL_DIR/.notmuch/hooks
database.mail_root MAIL_DIR
database.path MAIL_DIR
index.as_text text/
key with spaces value, with, spaces!
maildir.synchronize_flags true
new.ignore sekrit_junk

View file

@ -21,6 +21,10 @@ test_description='thread breakage during reindexing'
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
message_a () {
mkdir -p ${MAIL_DIR}/cur
cat > ${MAIL_DIR}/cur/a <<EOF

View file

@ -12,7 +12,7 @@ void print_properties (notmuch_message_t *message, const char *prefix, notmuch_b
notmuch_message_properties_t *list;
for (list = notmuch_message_get_properties (message, prefix, exact);
notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
printf("%s\n", notmuch_message_properties_value(list));
printf("%s = %s\n", notmuch_message_properties_key(list), notmuch_message_properties_value(list));
}
notmuch_message_properties_destroy (list);
}
@ -89,17 +89,6 @@ testkey2 = NULL
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch_message_remove_all_properties"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_message_remove_all_properties (message, NULL));
print_properties (message, "", FALSE);
EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "testing string map binary search (via message properties)"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
{
@ -157,7 +146,28 @@ print_properties (message, "testkey1", TRUE);
EOF
cat <<'EOF' >EXPECTED
== stdout ==
testvalue1
testkey1 = testvalue1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch_message_remove_all_properties"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_message_remove_all_properties (message, NULL));
EXPECT0(notmuch_database_destroy(db));
EXPECT0(notmuch_database_open_with_config (argv[1],
NOTMUCH_DATABASE_MODE_READ_WRITE,
"", NULL, &db, &msg));
if (msg) fputs (msg, stderr);
EXPECT0(notmuch_database_find_message(db, "4EFC743A.3060609@april.org", &message));
if (message == NULL) {
fprintf (stderr, "unable to find message");
exit (1);
}
print_properties (message, "", FALSE);
EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
@ -171,10 +181,9 @@ print_properties (message, "testkey1", TRUE);
EOF
cat <<'EOF' >EXPECTED
== stdout ==
alice
bob
testvalue1
testvalue2
testkey1 = alice
testkey1 = bob
testkey1 = testvalue2
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
@ -186,27 +195,14 @@ EXPECT0(notmuch_message_add_property (message, "testkey3", "testvalue3"));
EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
print_properties (message, "testkey", FALSE);
EOF
# expected: 4 values for testkey1, 3 values for testkey3
# they are not guaranteed to be sorted, so sort them, leaving the first
# line '== stdout ==' and the end ('== stderr ==' and whatever error
# may have been printed) alone
mv OUTPUT unsorted_OUTPUT
awk ' NR == 1 { print; next } \
NR < 6 { print | "sort"; next } \
NR == 6 { close("sort") } \
NR < 9 { print | "sort"; next } \
NR == 9 { close("sort") } \
{ print }' unsorted_OUTPUT > OUTPUT
rm unsorted_OUTPUT
cat <<'EOF' >EXPECTED
== stdout ==
alice
bob
testvalue1
testvalue2
alice3
bob3
testvalue3
testkey1 = alice
testkey1 = bob
testkey1 = testvalue2
testkey3 = alice3
testkey3 = bob3
testkey3 = testvalue3
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
@ -248,7 +244,7 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "dump message properties"
cat <<EOF > PROPERTIES
#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
EOF
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with ="));
@ -259,7 +255,7 @@ test_expect_equal_file PROPERTIES OUTPUT
test_begin_subtest "dump _only_ message properties"
cat <<EOF > EXPECTED
#notmuch-dump batch-tag:3 properties
#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue1 testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
#= 4EFC743A.3060609@april.org fancy%20key%20with%20%c3%a1cc%c3%a8nts=import%20value%20with%20= testkey1=alice testkey1=bob testkey1=testvalue2 testkey3=alice3 testkey3=bob3 testkey3=testvalue3
EOF
notmuch dump --include=properties > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
@ -328,7 +324,6 @@ EOF
cat <<'EOF' > EXPECTED
testkey1 = alice
testkey1 = bob
testkey1 = testvalue1
testkey1 = testvalue2
EOF
test_expect_equal_file EXPECTED OUTPUT
@ -344,7 +339,6 @@ EOF
cat <<'EOF' > EXPECTED
testkey1 = alice
testkey1 = bob
testkey1 = testvalue1
testkey1 = testvalue2
testkey3 = alice3
testkey3 = bob3
@ -362,4 +356,49 @@ for (key,val) in msg.get_properties("testkey",True):
EOF
test_expect_equal_file /dev/null OUTPUT
test_begin_subtest "notmuch_message_remove_all_properties_with_prefix"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_message_remove_all_properties_with_prefix (message, "testkey3"));
print_properties (message, "", FALSE);
EOF
cat <<'EOF' >EXPECTED
== stdout ==
fancy key with áccènts = import value with =
testkey1 = alice
testkey1 = bob
testkey1 = testvalue2
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "edit property on removed message without uncaught exception"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_database_remove_message (db, notmuch_message_get_filename (message)));
stat = notmuch_message_remove_property (message, "example", "example");
if (stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION)
fprintf (stderr, "unable to remove properties on message");
EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
unable to remove properties on message
EOF
test_expect_equal_file EXPECTED OUTPUT
add_email_corpus
test_begin_subtest "remove all properties on removed message without uncaught exception"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_database_remove_message (db, notmuch_message_get_filename (message)));
stat = notmuch_message_remove_all_properties_with_prefix (message, "");
if (stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION)
fprintf (stderr, "unable to remove properties on message");
EOF
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
unable to remove properties on message
EOF
test_expect_equal_file EXPECTED OUTPUT
test_done

View file

@ -5,7 +5,7 @@ test_description='reindexing messages'
add_email_corpus
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
count=$(notmuch count --lastmod '*' | cut -f 3)
for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \

View file

@ -3,6 +3,10 @@ test_description="message id parsing"
. $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "good message ids"
${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
<018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org>

77
test/T760-as-text.sh Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
test_description='index attachments as text'
. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus indexing
test_begin_subtest "empty as_text; skip text/x-diff"
messages=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain)
count=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz)
test_expect_equal "$messages,$count" "1,0"
notmuch config set index.as_text "^text/"
add_email_corpus indexing
test_begin_subtest "as_index is text/; find text/x-diff"
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "reindex with empty as_text, skips text/x-diff"
notmuch config set index.as_text
notmuch reindex '*'
messages=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain)
count=$(notmuch count id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz)
test_expect_equal "$messages,$count" "1,0"
test_begin_subtest "reindex with empty as_text; skips application/pdf"
notmuch config set index.as_text
notmuch reindex '*'
gmessages=$(notmuch count id:871qo9p4tf.fsf@tethera.net)
count=$(notmuch count id:871qo9p4tf.fsf@tethera.net and body:not-really-PDF)
test_expect_equal "$messages,$count" "1,0"
test_begin_subtest "reindex with as_text as text/; finds text/x-diff"
notmuch config set index.as_text "^text/"
notmuch reindex '*'
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "reindex with as_text as text/; skips application/pdf"
notmuch config set index.as_text "^text/"
notmuch config set index.as_text
notmuch reindex '*'
messages=$(notmuch count id:871qo9p4tf.fsf@tethera.net)
count=$(notmuch count id:871qo9p4tf.fsf@tethera.net and body:not-really-PDF)
test_expect_equal "$messages,$count" "1,0"
test_begin_subtest "as_text has multiple regexes"
notmuch config set index.as_text "blahblah;^text/"
notmuch reindex '*'
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "as_text is non-anchored regex"
notmuch config set index.as_text "e.t/"
notmuch reindex '*'
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain > EXPECTED
notmuch search id:20200930101213.2m2pt3jrspvcrxfx@localhost.localdomain and ersatz > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "as_text is 'application/pdf'"
notmuch config set index.as_text "^application/pdf$"
notmuch reindex '*'
notmuch search id:871qo9p4tf.fsf@tethera.net > EXPECTED
notmuch search id:871qo9p4tf.fsf@tethera.net and '"not really PDF"' > OUTPUT
test_expect_equal_file_nonempty EXPECTED OUTPUT
test_begin_subtest "as_text is bad regex"
notmuch config set index.as_text '['
notmuch reindex '*' >& OUTPUT
cat<<EOF > EXPECTED
Error in index.as_text: Invalid regular expression: [
EOF
test_expect_equal_file EXPECTED OUTPUT
test_done

View file

@ -2,14 +2,19 @@
test_description='run code with ASAN enabled against the library'
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_ASAN -ne 1 ]; then
if [ "${NOTMUCH_HAVE_ASAN-0}" != "1" ]; then
printf "Skipping due to missing ASAN support\n"
test_done
fi
if [ -n "${LD_PRELOAD-}" ]; then
printf "Skipping due to ASAN LD_PRELOAD restrictions\n"
test_done
fi
add_email_corpus
TEST_CFLAGS="-fsanitize=address"
TEST_CFLAGS="${TEST_CFLAGS:-} -fsanitize=address"
test_begin_subtest "open and destroy"
test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} <<EOF

92
test/T810-tsan.sh Executable file
View file

@ -0,0 +1,92 @@
#!/usr/bin/env bash
test_directory=$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)
test_description='run code with TSan enabled against the library'
# Note it is hard to ensure race conditions are deterministic so this
# only provides best effort detection.
. "$test_directory"/test-lib.sh || exit 1
if [ "${NOTMUCH_HAVE_TSAN-0}" != "1" ]; then
printf "Skipping due to missing TSan support\n"
test_done
fi
export TSAN_OPTIONS="suppressions=$test_directory/T810-tsan.suppressions"
TEST_CFLAGS="${TEST_CFLAGS:-} -fsanitize=thread"
cp -r ${MAIL_DIR} ${MAIL_DIR}-2
test_begin_subtest "create"
test_C ${MAIL_DIR} ${MAIL_DIR}-2 <<EOF
#include <notmuch-test.h>
#include <pthread.h>
void *thread (void *arg) {
char *mail_dir = arg;
/*
* Calls into notmuch_query_search_messages which was using the thread-unsafe
* Xapian::Query::MatchAll.
*/
EXPECT0(notmuch_database_create (mail_dir, NULL));
return NULL;
}
int main (int argc, char **argv) {
pthread_t t1, t2;
EXPECT0(pthread_create (&t1, NULL, thread, argv[1]));
EXPECT0(pthread_create (&t2, NULL, thread, argv[2]));
EXPECT0(pthread_join (t1, NULL));
EXPECT0(pthread_join (t2, NULL));
return 0;
}
EOF
cat <<EOF > EXPECTED
== stdout ==
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
add_email_corpus
rm -r ${MAIL_DIR}-2
cp -r ${MAIL_DIR} ${MAIL_DIR}-2
test_begin_subtest "query"
test_C ${MAIL_DIR} ${MAIL_DIR}-2 <<EOF
#include <notmuch-test.h>
#include <pthread.h>
void *thread (void *arg) {
char *mail_dir = arg;
notmuch_database_t *db;
/*
* 'from' is NOTMUCH_FIELD_PROBABILISTIC | NOTMUCH_FIELD_PROCESSOR and an
* empty string gets us to RegexpFieldProcessor::operator which was using
* the tread-unsafe Xapian::Query::MatchAll.
*/
EXPECT0(notmuch_database_open_with_config (mail_dir,
NOTMUCH_DATABASE_MODE_READ_ONLY,
NULL, NULL, &db, NULL));
notmuch_query_t *query = notmuch_query_create (db, "from:\"\"");
notmuch_messages_t *messages;
EXPECT0(notmuch_query_search_messages (query, &messages));
return NULL;
}
int main (int argc, char **argv) {
pthread_t t1, t2;
EXPECT0(pthread_create (&t1, NULL, thread, argv[1]));
EXPECT0(pthread_create (&t2, NULL, thread, argv[2]));
EXPECT0(pthread_join (t1, NULL));
EXPECT0(pthread_join (t2, NULL));
return 0;
}
EOF
cat <<EOF > EXPECTED
== stdout ==
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_done

View file

@ -0,0 +1,3 @@
# It's unclear how TSan-friendly GLib is:
# https://gitlab.gnome.org/GNOME/glib/-/issues/1672
called_from_lib:libglib*.so

View file

@ -2,7 +2,7 @@
test_description='"notmuch git" to save and restore tags'
. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_SFSEXP -ne 1 ]; then
if [ "${NOTMUCH_HAVE_SFSEXP-0}" != "1" ]; then
printf "Skipping due to missing sfsexp library\n"
test_done
fi
@ -233,6 +233,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "invoke as nmbug sets defaults"
test_subtest_broken_for_installed
"$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^\(prefix\|repository\)' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED
prefix = notmuch::
@ -241,6 +242,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_DIR works when invoked as nmbug"
test_subtest_broken_for_installed
NOTMUCH_GIT_DIR=`pwd`/foo "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED
repository = CWD/foo
@ -256,6 +258,7 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_DIR overrides config when invoked as 'nmbug'"
test_subtest_broken_for_installed
notmuch config set git.path `pwd`/bar
NOTMUCH_GIT_DIR=`pwd`/remote.git "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
notmuch config set git.path
@ -274,6 +277,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as 'nmbug'"
test_subtest_broken_for_installed
NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED
prefix = env::
@ -281,6 +285,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as nmbug"
test_subtest_broken_for_installed
NOTMUCH_GIT_PREFIX=foo:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED
prefix = foo::
@ -288,6 +293,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX overrides config when invoked as 'nmbug'"
test_subtest_broken_for_installed
notmuch config set git.tag_prefix config::
NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
notmuch config set git.path

View file

@ -0,0 +1,11 @@
From: David Bremner <david@tethera.net>
To: example@example.com
Subject: attachment content type
Date: Thu, 05 Jan 2023 08:02:36 -0400
Message-ID: <871qo9p4tf.fsf@tethera.net>
MIME-Version: 1.0
Content-Type: application/pdf
Content-Disposition: attachment; filename=fake.pdf
Content-Transfer-Encoding: base64
dGhpcyBpcyBub3QgcmVhbGx5IFBERgo=

View file

@ -0,0 +1,25 @@
2010-12-29 François Boulogne ─►[aur-general] Guidelines: cp, mkdir vs install (inbox unread)
2010-12-16 Olivier Berger ─►Essai accentué (inbox unread)
2009-11-18 Chris Wilson ─►[notmuch] [PATCH 1/2] Makefile: evaluate pkg-config once (inbox unread)
2009-11-18 Alex Botero-Lowry ┬►[notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
2009-11-17 Ingmar Vanhassel ┬►[notmuch] [PATCH] Typsos (inbox unread)
2009-11-17 Adrian Perez de Cast ┬►[notmuch] Introducing myself (inbox signed unread)
2009-11-17 Israel Herraiz ┬►[notmuch] New to the list (inbox unread)
2009-11-17 Jan Janak ┬►[notmuch] What a great idea! (inbox unread)
2009-11-17 Jan Janak ┬►[notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
2009-11-17 Aron Griffis ┬►[notmuch] archive (inbox unread)
2009-11-17 Keith Packard ┬►[notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
2009-11-17 Lars Kellogg-Stedman ┬►[notmuch] Working with Maildir storage? (inbox signed unread)
2009-11-17 Mikhail Gusarov ┬►[notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
2009-11-18 Keith Packard ┬►[notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (inbox unread)
2009-11-18 Alexander Botero-Low ─►[notmuch] request for pull (inbox unread)
2009-11-18 Jjgod Jiang ┬►[notmuch] Mac OS X/Darwin compatibility issues (inbox unread)
2009-11-18 Rolland Santimano ─►[notmuch] Link to mailing list archives ? (inbox unread)
2009-11-18 Jan Janak ─►[notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (inbox unread)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH] count_files: sort directory in inode order before statting (inbox unread)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH 2/2] Read mail directory in inode number order (inbox unread)
2009-11-18 Stewart Smith ─►[notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (inbox unread)
2009-11-18 Lars Kellogg-Stedman ┬►[notmuch] "notmuch help" outputs to stderr? (attachment inbox signed unread)
2009-11-17 Mikhail Gusarov ─►[notmuch] [PATCH] Handle rename of message file (inbox unread)
2009-11-17 Alex Botero-Lowry ┬►[notmuch] preliminary FreeBSD support (attachment inbox unread)
End of search results.

Some files were not shown because too many files have changed in this diff Show more