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) 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/ruby/.vendorarchdir.time $(dir)/ruby.stamp
CLEAN += bindings/python-cffi/build $(dir)/python-cffi.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 return value
else: 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): class Python3StringMixIn(object):
def __str__(self): def __str__(self):

View file

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

View file

@ -58,40 +58,56 @@ notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE self)
notmuch_database_t *database; notmuch_database_t *database;
notmuch_status_t ret; notmuch_status_t ret;
/* Check arguments */ path = NULL;
rb_scan_args (argc, argv, "11", &pathv, &hashv); create = 0;
mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
SafeStringValue (pathv); /* Check arguments */
path = RSTRING_PTR (pathv); rb_scan_args (argc, argv, "02", &pathv, &hashv);
if (!NIL_P (pathv)) {
SafeStringValue (pathv);
path = RSTRING_PTR (pathv);
}
if (!NIL_P (hashv)) { if (!NIL_P (hashv)) {
Check_Type (hashv, T_HASH); VALUE rmode, rcreate;
create = RTEST (rb_hash_aref (hashv, ID2SYM (ID_db_create))); VALUE kwargs[2];
modev = rb_hash_aref (hashv, ID2SYM (ID_db_mode)); static ID keyword_ids[2];
if (NIL_P (modev))
mode = NOTMUCH_DATABASE_MODE_READ_ONLY; if (!keyword_ids[0]) {
else if (!FIXNUM_P (modev)) keyword_ids[0] = rb_intern_const ("mode");
rb_raise (rb_eTypeError, ":mode isn't a Fixnum"); keyword_ids[1] = rb_intern_const ("create");
else { }
mode = FIX2INT (modev);
switch (mode) { rb_get_kwargs (hashv, keyword_ids, 0, 2, kwargs);
case NOTMUCH_DATABASE_MODE_READ_ONLY:
case NOTMUCH_DATABASE_MODE_READ_WRITE: rmode = kwargs[0];
break; rcreate = kwargs[1];
default:
rb_raise ( rb_eTypeError, "Invalid mode"); 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 { if (rcreate != Qundef)
create = 0; create = RTEST (rcreate);
mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
} }
rb_check_typeddata (self, &notmuch_rb_database_type); rb_check_typeddata (self, &notmuch_rb_database_type);
if (create) if (create)
ret = notmuch_database_create (path, &database); ret = notmuch_database_create (path, &database);
else 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); notmuch_rb_status_raise (ret);
DATA_PTR (self) = notmuch_rb_object_create (database, "notmuch_rb_database"); 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); 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_cThread;
extern VALUE notmuch_rb_cMessages; extern VALUE notmuch_rb_cMessages;
extern VALUE notmuch_rb_cMessage; extern VALUE notmuch_rb_cMessage;
extern VALUE notmuch_rb_cTags;
extern VALUE notmuch_rb_eBaseError; extern VALUE notmuch_rb_eBaseError;
extern VALUE notmuch_rb_eDatabaseError; extern VALUE notmuch_rb_eDatabaseError;
@ -48,8 +47,6 @@ extern VALUE notmuch_rb_eUnbalancedFreezeThawError;
extern VALUE notmuch_rb_eUnbalancedAtomicError; extern VALUE notmuch_rb_eUnbalancedAtomicError;
extern ID ID_call; extern ID ID_call;
extern ID ID_db_create;
extern ID ID_db_mode;
/* RSTRING_PTR() is new in ruby-1.9 */ /* RSTRING_PTR() is new in ruby-1.9 */
#if !defined(RSTRING_PTR) #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_object_type;
extern const rb_data_type_t notmuch_rb_database_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_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_query_type;
extern const rb_data_type_t notmuch_rb_threads_type; extern const rb_data_type_t notmuch_rb_threads_type;
extern const rb_data_type_t notmuch_rb_thread_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) \ #define Data_Get_Notmuch_Directory(obj, ptr) \
Data_Get_Notmuch_Object ((obj), &notmuch_rb_directory_type, (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) \ #define Data_Get_Notmuch_Query(obj, ptr) \
Data_Get_Notmuch_Object ((obj), &notmuch_rb_query_type, (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 */ /* filenames.c */
VALUE VALUE
notmuch_rb_filenames_destroy (VALUE self); notmuch_rb_filenames_get (notmuch_filenames_t *fnames);
VALUE
notmuch_rb_filenames_each (VALUE self);
/* query.c */ /* query.c */
VALUE VALUE
@ -370,10 +360,7 @@ notmuch_rb_message_thaw (VALUE self);
/* tags.c */ /* tags.c */
VALUE VALUE
notmuch_rb_tags_destroy (VALUE self); notmuch_rb_tags_get (notmuch_tags_t *tags);
VALUE
notmuch_rb_tags_each (VALUE self);
/* init.c */ /* init.c */
void void

View file

@ -87,7 +87,7 @@ notmuch_rb_directory_get_child_files (VALUE self)
fnames = notmuch_directory_get_child_files (dir); 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); 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" #include "defs.h"
/*
* call-seq: FILENAMES.destroy! => nil
*
* Destroys the filenames, freeing all resources allocated for it.
*/
VALUE VALUE
notmuch_rb_filenames_destroy (VALUE self) notmuch_rb_filenames_get (notmuch_filenames_t *fnames)
{ {
notmuch_rb_object_destroy (self, &notmuch_rb_filenames_type); VALUE rb_array = rb_ary_new ();
return Qnil;
}
/*
* call-seq: FILENAMES.each {|item| block } => FILENAMES
*
* Calls +block+ once for each element in +self+, passing that element as a
* parameter.
*/
VALUE
notmuch_rb_filenames_each (VALUE self)
{
notmuch_filenames_t *fnames;
Data_Get_Notmuch_FileNames (self, fnames);
for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames)) for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames))
rb_yield (rb_str_new2 (notmuch_filenames_get (fnames))); rb_ary_push (rb_array, rb_str_new2 (notmuch_filenames_get (fnames)));
return rb_array;
return self;
} }

View file

@ -22,13 +22,11 @@
VALUE notmuch_rb_cDatabase; VALUE notmuch_rb_cDatabase;
VALUE notmuch_rb_cDirectory; VALUE notmuch_rb_cDirectory;
VALUE notmuch_rb_cFileNames;
VALUE notmuch_rb_cQuery; VALUE notmuch_rb_cQuery;
VALUE notmuch_rb_cThreads; VALUE notmuch_rb_cThreads;
VALUE notmuch_rb_cThread; VALUE notmuch_rb_cThread;
VALUE notmuch_rb_cMessages; VALUE notmuch_rb_cMessages;
VALUE notmuch_rb_cMessage; VALUE notmuch_rb_cMessage;
VALUE notmuch_rb_cTags;
VALUE notmuch_rb_eBaseError; VALUE notmuch_rb_eBaseError;
VALUE notmuch_rb_eDatabaseError; VALUE notmuch_rb_eDatabaseError;
@ -43,8 +41,6 @@ VALUE notmuch_rb_eUnbalancedFreezeThawError;
VALUE notmuch_rb_eUnbalancedAtomicError; VALUE notmuch_rb_eUnbalancedAtomicError;
ID ID_call; ID ID_call;
ID ID_db_create;
ID ID_db_mode;
const rb_data_type_t notmuch_rb_object_type = { const rb_data_type_t notmuch_rb_object_type = {
.wrap_struct_name = "notmuch_object", .wrap_struct_name = "notmuch_object",
@ -65,13 +61,11 @@ const rb_data_type_t notmuch_rb_object_type = {
define_type (database); define_type (database);
define_type (directory); define_type (directory);
define_type (filenames);
define_type (query); define_type (query);
define_type (threads); define_type (threads);
define_type (thread); define_type (thread);
define_type (messages); define_type (messages);
define_type (message); define_type (message);
define_type (tags);
/* /*
* Document-module: Notmuch * Document-module: Notmuch
@ -86,13 +80,11 @@ define_type (tags);
* the user: * the user:
* *
* - Notmuch::Database * - Notmuch::Database
* - Notmuch::FileNames
* - Notmuch::Query * - Notmuch::Query
* - Notmuch::Threads * - Notmuch::Threads
* - Notmuch::Messages * - Notmuch::Messages
* - Notmuch::Thread * - Notmuch::Thread
* - Notmuch::Message * - Notmuch::Message
* - Notmuch::Tags
*/ */
void void
@ -101,8 +93,6 @@ Init_notmuch (void)
VALUE mod; VALUE mod;
ID_call = rb_intern ("call"); ID_call = rb_intern ("call");
ID_db_create = rb_intern ("create");
ID_db_mode = rb_intern ("mode");
mod = rb_define_module ("Notmuch"); 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_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 */ 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 * 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, "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, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */
rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 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); 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) if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory"); 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) if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory"); 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); 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" #include "defs.h"
/*
* call-seq: TAGS.destroy! => nil
*
* Destroys the tags, freeing all resources allocated for it.
*/
VALUE VALUE
notmuch_rb_tags_destroy (VALUE self) notmuch_rb_tags_get (notmuch_tags_t *tags)
{ {
notmuch_rb_object_destroy (self, &notmuch_rb_tags_type); VALUE rb_array = rb_ary_new ();
return Qnil;
}
/*
* call-seq: TAGS.each {|item| block } => TAGS
*
* Calls +block+ once for each element in +self+, passing that element as a
* parameter.
*/
VALUE
notmuch_rb_tags_each (VALUE self)
{
const char *tag;
notmuch_tags_t *tags;
Data_Get_Notmuch_Tags (self, tags);
for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) { for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) {
tag = notmuch_tags_get (tags); const char *tag = notmuch_tags_get (tags);
rb_yield (rb_str_new2 (tag)); rb_ary_push (rb_array, rb_str_new2 (tag));
} }
return rb_array;
return self;
} }

View file

@ -204,5 +204,5 @@ notmuch_rb_thread_get_tags (VALUE self)
if (!tags) if (!tags)
rb_raise (notmuch_rb_eMemoryError, "Out of memory"); 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 #define _GNU_SOURCE
#include <strings.h> #include <strings.h> /* strcasecmp() in POSIX */
#include <string.h> /* strcasecmp() in *BSD */
int int
main () main ()

View file

@ -530,7 +530,7 @@ _notmuch_show()
! $split && ! $split &&
case "${cur}" in 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 compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
;; ;;

View file

@ -245,6 +245,8 @@ _notmuch_show() {
'--exclude=[respect excluded tags setting]:exclude tags:(true false)' \ '--exclude=[respect excluded tags setting]:exclude tags:(true false)' \
'--body=[output body]:output body content:(true false)' \ '--body=[output body]:output body content:(true false)' \
'--include-html[include text/html parts in the output]' \ '--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' '*::search term:_notmuch_search_term'
} }

19
configure vendored
View file

@ -422,6 +422,18 @@ else
fi fi
unset test_cmdline 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... " printf "Reading libnotmuch version from source... "
cat > _libversion.c <<EOF cat > _libversion.c <<EOF
#include <stdio.h> #include <stdio.h>
@ -541,8 +553,8 @@ version of GPGME.
Please try to rebuild your version of GMime against a more recent Please try to rebuild your version of GMime against a more recent
version of GPGME (at least GPGME 1.8.0). version of GPGME (at least GPGME 1.8.0).
EOF EOF
if command -v gpgme-config >/dev/null; then if GPGME_VERS="$(pkg-config --modversion gpgme || gpgme-config --version)"; then
printf 'Your current GPGME development version is: %s\n' "$(gpgme-config --version)" printf 'Your current GPGME development version is: %s\n' "$GPGME_VERS"
else else
printf 'You do not have the GPGME development libraries installed.\n' printf 'You do not have the GPGME development libraries installed.\n'
fi fi
@ -1590,8 +1602,9 @@ NOTMUCH_GMIME_VERIFY_WITH_SESSION_KEY=${gmime_verify_with_session_key}
NOTMUCH_ZLIB_CFLAGS="${zlib_cflags}" NOTMUCH_ZLIB_CFLAGS="${zlib_cflags}"
NOTMUCH_ZLIB_LDFLAGS="${zlib_ldflags}" 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_ASAN=${have_asan}
NOTMUCH_HAVE_TSAN=${have_tsan}
# do we have man pages? # do we have man pages?
NOTMUCH_HAVE_MAN=$((have_sphinx)) 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) (Debian package: libmail-box-perl)
- Mail::Header <https://metacpan.org/pod/Mail::Header> - Mail::Header <https://metacpan.org/pod/Mail::Header>
(Debian package: libmailtools-perl) (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> - Term::ReadLine::Gnu <https://metacpan.org/pod/Term::ReadLine::Gnu>
(Debian package: libterm-readline-gnu-perl) (Debian package: libterm-readline-gnu-perl)

View file

@ -2,7 +2,7 @@
# #
# notmuch-mutt - notmuch (of a) helper for Mutt # 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 # License: GNU General Public License (GPL), version 3 or above
# #
# See the bottom of this file for more documentation. # See the bottom of this file for more documentation.
@ -13,11 +13,11 @@ use warnings;
use File::Path; use File::Path;
use File::Basename; use File::Basename;
use File::Find;
use Getopt::Long qw(:config no_getopt_compat); use Getopt::Long qw(:config no_getopt_compat);
use Mail::Header; use Mail::Header;
use Mail::Box::Maildir; use Mail::Box::Maildir;
use Pod::Usage; use Pod::Usage;
use String::ShellQuote;
use Term::ReadLine; use Term::ReadLine;
use Digest::SHA; 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}; $xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME};
my $cache_dir = "$xdg_cache_dir/notmuch/mutt"; 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 die_subdir($$$) {
sub empty_maildir($) { 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) = (@_); my ($maildir) = (@_);
rmtree($maildir) if (-d $maildir); rmtree($maildir) if (-d $maildir);
my $folder = new Mail::Box::Maildir(folder => $maildir, my $folder = new Mail::Box::Maildir(folder => $maildir,
@ -46,7 +90,8 @@ sub search($$$) {
push @args, "--duplicate=1" if $remove_dups; push @args, "--duplicate=1" if $remove_dups;
push @args, $query; 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"; open my $pipe, '-|', @args or die "Running @args failed: $!\n";
while (<$pipe>) { while (<$pipe>) {
chomp; chomp;
@ -121,21 +166,23 @@ sub thread_action($$@) {
my $mid = get_message_id(); my $mid = get_message_id();
if (! defined $mid) { if (! defined $mid) {
empty_maildir($results_dir);
die "notmuch-mutt: cannot find Message-Id, abort.\n"; 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(@) { sub tag_action(@) {
my $mid = get_message_id(); my $mid = get_message_id();
defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n"; 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() { 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 notmuch (0.37-1) unstable; urgency=medium
* New upstream release. * New upstream release.

1
debian/control vendored
View file

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

8
debian/rules vendored
View file

@ -1,4 +1,9 @@
#!/usr/bin/make -f #!/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 export DEB_BUILD_MAINT_OPTIONS = hardening=+all
@ -7,7 +12,7 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
override_dh_auto_configure: override_dh_auto_configure:
BASHCMD=/bin/bash ./configure --prefix=/usr \ BASHCMD=/bin/bash ./configure --prefix=/usr \
--libdir=/usr/lib/$$(dpkg-architecture -q DEB_TARGET_MULTIARCH) \ --libdir=/usr/lib/${DEB_TARGET_MULTIARCH} \
--includedir=/usr/include \ --includedir=/usr/include \
--mandir=/usr/share/man \ --mandir=/usr/share/man \
--infodir=/usr/share/info \ --infodir=/usr/share/info \
@ -24,7 +29,6 @@ override_dh_auto_build:
override_dh_auto_clean: override_dh_auto_clean:
dh_auto_clean dh_auto_clean
PYBUILD_NAME=notmuch dh_auto_clean --buildsystem=pybuild --sourcedirectory bindings/python 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 dh_auto_clean --sourcedirectory bindings/ruby
$(MAKE) -C contrib/notmuch-mutt clean $(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) # (format_message_sprinter)
id: messageid, id: messageid,
match: bool, match: bool,
excluded: bool,
filename: [string*], filename: [string*],
timestamp: unix_time, # date header as unix time timestamp: unix_time, # date header as unix time
date_relative: string, # user-friendly timestamp 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) $(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.texi.stamp
CLEAN := $(CLEAN) $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.info.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) $(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. # General information about the project.
project = u'notmuch' 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__) 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`. 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 .. nmconfig:: index.decrypt
Policy for decrypting encrypted messages during indexing. Must be 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 .. 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 .. envvar:: NOTMUCH_GIT_PREFIX
Default tag prefix (filter). Overriden by :option:`--tag-prefix`. Default tag prefix (filter). Overridden by :option:`--tag-prefix`.
SEE ALSO SEE ALSO
======== ========

View file

@ -43,7 +43,7 @@ Supported options for **search** include
.. option:: --output=(summary|threads|messages|files|tags) .. option:: --output=(summary|threads|messages|files|tags)
summary summary (default)
Output a summary of each thread with any message matching the Output a summary of each thread with any message matching the
search terms. The summary includes the thread ID, date, the search terms. The summary includes the thread ID, date, the
number of messages in the thread (both the number matched and 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 By default, results will be displayed in reverse chronological
order, (that is, the newest results will be displayed first). 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 .. option:: --verify
Compute and report the validity of any MIME cryptographic 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. 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 Match messages whose subject contains "quick brown fox", but also
"brown fox quicksand". "brown fox quicksand".
@ -336,7 +336,7 @@ user defined fields is permitted within a macro.
NOTES 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 lexical scope. There is one top level scope
containing all macro definitions, but all containing all macro definitions, but all
parameter definitions are local to a given macro. parameter definitions are local to a given macro.
@ -347,10 +347,10 @@ NOTES
.. [#aka-bool] a.k.a. boolean prefixes .. [#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. 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. this modifier is not supported in the ``body`` field.
.. [#not-path] Due to the way recursive ``path`` queries are implemented .. [#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 is important, well refer to the Emacs interface as
*notmuch-emacs*. *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 framework (or just by setting the appropriate variables). We try to
point out relevant variables in this manual, but in order to avoid point out relevant variables in this manual, but in order to avoid
duplication of information, you can usually find the most detailed duplication of information, you can usually find the most detailed
@ -493,7 +493,7 @@ in :ref:`notmuch-search`.
Dealing with duplicates 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 (see :any:`duplicate-files`), then :any:`notmuch-show` displays the
number of duplicates and identifies the current duplicate. In the number of duplicates and identifies the current duplicate. In the
following example duplicate 3 of 5 is displayed. 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 See also :el:defcustom:`notmuch-search-result-format` and
:el:defcustom:`notmuch-unthreaded-result-format`. :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: .. _notmuch-unthreaded:
@ -678,7 +717,7 @@ operations specified in ``notmuch-tagging-keys``; i.e. each
notmuch-tag-undo 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-show`, :any:`notmuch-search`, :any:`notmuch-tree`, and
:any:`notmuch-unthreaded` mode) keeps a local stack of tagging :any:`notmuch-unthreaded` mode) keeps a local stack of tagging
operations. These can be undone via :any:`notmuch-tag-undo`. By default 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))) (let ((user-agent (funcall notmuch-mua-user-agent-function)))
(unless (string-empty-p user-agent) (unless (string-empty-p user-agent)
(push (cons 'User-Agent user-agent) other-headers)))) (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) (notmuch-mua-pop-to-buffer (message-buffer-name "mail" to)
(or switch-function (or switch-function
(notmuch-mua-get-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, ;; Cause `message-setup-1' to do things relevant for mail,
;; such as observe `message-default-mail-headers'. ;; such as observe `message-default-mail-headers'.
(message-this-is-mail t)) (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)) (message-setup-1 headers yank-action send-actions return-action))
(notmuch-fcc-header-setup) (notmuch-fcc-header-setup)
(notmuch-mua--remove-dont-reply-to-names) (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) (defun notmuch-show-update-tags (tags)
"Update the displayed tags of the current message." "Update the displayed tags of the current message."
(save-excursion (save-excursion
(goto-char (notmuch-show-message-top)) (let ((inhibit-read-only t)
(when (re-search-forward "(\\([^()]*\\))$" (line-end-position) t) (start (notmuch-show-message-top))
(let ((inhibit-read-only t)) (depth (notmuch-show-get-prop :depth))
(replace-match (concat "(" (orig-tags (notmuch-show-get-prop :orig-tags))
(notmuch-tag-format-tags (props (notmuch-show-get-message-properties))
tags (extent (notmuch-show-message-extent)))
(notmuch-show-get-prop :orig-tags)) (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) (defun notmuch-clean-address (address)
"Try to clean a single email ADDRESS for display. Return a cons "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) (plist-put msg :height 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 "Insert a notmuch style headerline based on HEADERS for a
message at DEPTH in the current thread." message at DEPTH in the current thread."
(let ((start (point)) (let* ((start (point))
(from (notmuch-sanitize (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))))) (notmuch-show-clean-address (plist-get headers :From)))))
(when (string-match "\\cR" from) (when (string-match "\\cR" from)
;; If the From header has a right-to-left character add ;; If the From header has a right-to-left character add
@ -549,7 +560,7 @@ message at DEPTH in the current thread."
" (" " ("
date date
") (" ") ("
(notmuch-tag-format-tags tags tags) (notmuch-tag-format-tags tags (or orig-tags tags))
")") ")")
(insert (insert
(if (> file-count 1) (if (> file-count 1)
@ -1171,8 +1182,6 @@ is out of range."
(defun notmuch-show-insert-msg (msg depth) (defun notmuch-show-insert-msg (msg depth)
"Insert the message MSG at depth DEPTH in the current thread." "Insert the message MSG at depth DEPTH in the current thread."
(let* ((headers (plist-get msg :headers)) (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 ;; Indentation causes the buffer offset of the start/end
;; points to move, so we must use markers. ;; points to move, so we must use markers.
message-start message-end message-start message-end
@ -1180,11 +1189,7 @@ is out of range."
headers-start headers-end headers-start headers-end
(bare-subject (notmuch-show-strip-re (plist-get headers :Subject)))) (bare-subject (notmuch-show-strip-re (plist-get headers :Subject))))
(setq message-start (point-marker)) (setq message-start (point-marker))
(notmuch-show-insert-headerline headers (notmuch-show-insert-headerline msg depth (plist-get msg :tags))
(or (and notmuch-show-relative-dates
(plist-get msg :date_relative))
(plist-get headers :Date))
(plist-get msg :tags) depth duplicate files)
(setq content-start (point-marker)) (setq content-start (point-marker))
;; Set `headers-start' to point after the 'Subject:' header to be ;; Set `headers-start' to point after the 'Subject:' header to be
;; compatible with the existing implementation. This just sets it ;; 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 A message tree is another name for a single sub-thread: i.e., a
message together with all its descendents." message together with all its descendents."
(let ((msg (car tree)) (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 (cond
((and (< 0 depth) (not last)) ((and (< 0 depth) (not last))
(push (alist-get 'vertical-tee notmuch-tree-thread-symbols) tree-status)) (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 :first (and first (eq 0 depth))))
(setq msg (plist-put msg :tree-status tree-status)) (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 :orig-tags (plist-get msg :tags)))
(setq msg (plist-put msg :level level))
(notmuch-tree-goto-and-insert-msg msg) (notmuch-tree-goto-and-insert-msg msg)
(pop tree-status) (pop tree-status)
(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) (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
(hl-line-mode 1) (hl-line-mode 1)
(setq buffer-read-only t) (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 (defvar notmuch-tree-process-exit-functions nil
"Functions called when the process inserting a tree of results finishes. "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 nil
notmuch-search-oldest-first))) 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) (provide 'notmuch-tree)

View file

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

View file

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

View file

@ -291,6 +291,10 @@ struct _notmuch_database {
/* Track what parameters were specified when opening */ /* Track what parameters were specified when opening */
notmuch_open_param_t params; 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 /* 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); &message);
if (status == NOTMUCH_STATUS_SUCCESS && 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) if (status == NOTMUCH_STATUS_SUCCESS)
_notmuch_message_delete (message); status = _notmuch_message_delete (message);
else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
_notmuch_message_sync (message); _notmuch_message_sync (message);
@ -1573,3 +1575,15 @@ notmuch_database_status_string (const notmuch_database_t *notmuch)
{ {
return notmuch->status_string; 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, GMimeObject *part,
_notmuch_message_crypto_t *msg_crypto); _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. */ /* Callback to generate terms for each mime part of a message. */
static void static void
_index_mime_part (notmuch_message_t *message, _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_add_term (message, "tag", "attachment");
_notmuch_message_gen_terms (message, "attachment", filename); _notmuch_message_gen_terms (message, "attachment", filename);
/* XXX: Would be nice to call out to something here to parse if (! _indexable_as_text (message, part)) {
* the attachment into text and then index that. */ /* XXX: Would be nice to call out to something here to parse
goto DONE; * the attachment into text and then index that. */
goto DONE;
}
} }
byte_array = g_byte_array_new (); byte_array = g_byte_array_new ();

View file

@ -25,6 +25,20 @@
#include "database-private.h" #include "database-private.h"
#include "message-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_status_t
notmuch_message_get_property (notmuch_message_t *message, const char *key, const char **value) 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); term = talloc_asprintf (message, "%s=%s", key, value);
if (delete_it) try {
private_status = _notmuch_message_remove_term (message, "property", term); if (delete_it)
else private_status = _notmuch_message_remove_term (message, "property", term);
private_status = _notmuch_message_add_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) if (private_status)
return COERCE_STATUS (private_status, return COERCE_STATUS (private_status,
@ -123,15 +142,22 @@ _notmuch_message_remove_all_properties (notmuch_message_t *message, const char *
if (status) if (status)
return status; return status;
_notmuch_message_invalidate_metadata (message, "property");
if (key) if (key)
term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key, term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
prefix ? "" : "="); prefix ? "" : "=");
else else
term_prefix = _find_prefix ("property"); term_prefix = _find_prefix ("property");
/* XXX better error reporting ? */ try {
_notmuch_message_remove_terms (message, term_prefix); /* 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; 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. */ /* 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; const char *relative, *directory;
notmuch_status_t status; notmuch_status_t status;
notmuch_private_status_t private_status;
void *local = talloc_new (message); void *local = talloc_new (message);
char *direntry; char *direntry;
@ -964,10 +967,17 @@ _notmuch_message_add_filename (notmuch_message_t *message,
/* New file-direntry allows navigating to this message with /* New file-direntry allows navigating to this message with
* notmuch_directory_get_child_files() . */ * notmuch_directory_get_child_files() . */
status = COERCE_STATUS (_notmuch_message_add_term (message, "file-direntry", direntry), private_status = _notmuch_message_add_term (message, "file-direntry", direntry);
"adding file-direntry term"); switch (private_status) {
if (status) case NOTMUCH_PRIVATE_STATUS_SUCCESS:
return status; 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); status = _notmuch_message_add_folder_terms (message, directory);
if (status) if (status)
@ -1383,21 +1393,22 @@ _notmuch_message_delete (notmuch_message_t *message)
if (status) if (status)
return 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 { try {
Xapian::PostingIterator thread_doc, thread_doc_end; Xapian::PostingIterator thread_doc, thread_doc_end;
Xapian::PostingIterator mail_doc, mail_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, _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
&thread_doc_end); &thread_doc_end);
_notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_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, notmuch_find_flags_t flags,
char **direntry); char **direntry);
bool
_notmuch_database_indexable_as_text (notmuch_database_t *notmuch,
const char *mime_string);
/* directory.cc */ /* directory.cc */
notmuch_directory_t * 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_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 * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to create the
* database file (such as permission denied, or file not found, * database file (such as permission denied, or file not found,
* etc.), or the database already exists. * 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 * The UUID is a NUL-terminated opaque string that uniquely identifies
* this database. Two revision numbers are only comparable if they * 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 unsigned long
notmuch_database_get_revision (notmuch_database_t *notmuch, 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); * 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_valid (messages);
* notmuch_messages_move_to_next (messages)) * notmuch_messages_move_to_next (messages))
* { * {
@ -2558,6 +2564,7 @@ typedef enum {
NOTMUCH_CONFIG_USER_NAME, NOTMUCH_CONFIG_USER_NAME,
NOTMUCH_CONFIG_AUTOCOMMIT, NOTMUCH_CONFIG_AUTOCOMMIT,
NOTMUCH_CONFIG_EXTRA_HEADERS, NOTMUCH_CONFIG_EXTRA_HEADERS,
NOTMUCH_CONFIG_INDEX_AS_TEXT,
NOTMUCH_CONFIG_LAST NOTMUCH_CONFIG_LAST
} notmuch_config_key_t; } 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_count = 0;
notmuch->transaction_threshold = 0; notmuch->transaction_threshold = 0;
notmuch->view = 1; notmuch->view = 1;
notmuch->index_as_text = NULL;
notmuch->index_as_text_length = 0;
notmuch->params = NOTMUCH_PARAM_NONE; notmuch->params = NOTMUCH_PARAM_NONE;
if (database_path) if (database_path)
@ -427,6 +429,53 @@ _load_database_state (notmuch_database_t *notmuch)
notmuch, notmuch->xapian_db->get_uuid ().c_str ()); 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 static notmuch_status_t
_finish_open (notmuch_database_t *notmuch, _finish_open (notmuch_database_t *notmuch,
const char *profile, const char *profile,
@ -531,6 +580,10 @@ _finish_open (notmuch_database_t *notmuch,
if (status) if (status)
goto DONE; goto DONE;
status = _ensure_index_as_text (notmuch, &message);
if (status)
goto DONE;
autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT); autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
if (unlikely (! autocommit_str)) { if (unlikely (! autocommit_str)) {
INTERNAL_ERROR ("missing configuration for autocommit"); INTERNAL_ERROR ("missing configuration for autocommit");

View file

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

View file

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

View file

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

View file

@ -278,18 +278,24 @@ notmuch_conffile_open (notmuch_database_t *notmuch,
return NULL; 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++) { for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
const char *name = group_comment_table[i].group_name; const char *name = group_comment_table[i].group_name;
if (! g_key_file_has_group (config->key_file, name)) { if (! g_key_file_has_group (config->key_file, name)) {
/* Force group to exist before adding comment */ /* Force group to exist before adding comment */
g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val"); 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_remove_key (config->key_file, name, "dummy_key", NULL);
g_key_file_set_comment (config->key_file, name, NULL, if (config->is_new && (i == 0) ) {
group_comment_table[i].comment, NULL); 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; return config;

View file

@ -254,7 +254,7 @@ def count_messages(prefix=None):
def get_tags(prefix=None): def get_tags(prefix=None):
"Get a list of tags with a given prefix." "Get a list of tags with a given prefix."
(status, stdout, stderr) = _spawn( (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) stdout=_subprocess.PIPE, wait=True)
return [tag for tag in stdout.splitlines()] return [tag for tag in stdout.splitlines()]
@ -719,7 +719,7 @@ class DatabaseCache:
self._known[id] = False self._known[id] = False
else: else:
(_, stdout, stderr) = _spawn( (_, 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, stdout=_subprocess.PIPE,
wait=True) wait=True)
self._known[id] = stdout != None 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: case NOTMUCH_STATUS_FILE_NOT_EMAIL:
fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename); fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
break; 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: case NOTMUCH_STATUS_FILE_ERROR:
/* Someone renamed/removed the file between scandir and now. */ /* Someone renamed/removed the file between scandir and now. */
state->vanished_files++; state->vanished_files++;

View file

@ -1159,6 +1159,18 @@ do_show_threaded (void *ctx,
notmuch_thread_t *thread; notmuch_thread_t *thread;
notmuch_messages_t *messages; notmuch_messages_t *messages;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; 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); status = notmuch_query_search_threads (query, &threads);
if (print_status_query ("notmuch show", query, status)) if (print_status_query ("notmuch show", query, status))
@ -1166,11 +1178,16 @@ do_show_threaded (void *ctx,
sp->begin_list (sp); sp->begin_list (sp);
for (; for (i = 0;
notmuch_threads_valid (threads); notmuch_threads_valid (threads) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_threads_move_to_next (threads)) { notmuch_threads_move_to_next (threads), i++) {
thread = notmuch_threads_get (threads); thread = notmuch_threads_get (threads);
if (i < params->offset) {
notmuch_thread_destroy (thread);
continue;
}
messages = notmuch_thread_get_toplevel_messages (thread); messages = notmuch_thread_get_toplevel_messages (thread);
if (messages == NULL) if (messages == NULL)
@ -1201,6 +1218,18 @@ do_show_unthreaded (void *ctx,
notmuch_message_t *message; notmuch_message_t *message;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS; notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
notmuch_bool_t excluded; 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); status = notmuch_query_search_messages (query, &messages);
if (print_status_query ("notmuch show", query, status)) if (print_status_query ("notmuch show", query, status))
@ -1208,9 +1237,13 @@ do_show_unthreaded (void *ctx,
sp->begin_list (sp); sp->begin_list (sp);
for (; for (i = 0;
notmuch_messages_valid (messages); notmuch_messages_valid (messages) && (params->limit < 0 || i < params->offset + params->limit);
notmuch_messages_move_to_next (messages)) { notmuch_messages_move_to_next (messages), i++) {
if (i < params->offset) {
continue;
}
sp->begin_list (sp); sp->begin_list (sp);
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 = { notmuch_show_params_t params = {
.part = -1, .part = -1,
.duplicate = 0, .duplicate = 0,
.offset = 0,
.limit = -1, /* unlimited */
.omit_excluded = true, .omit_excluded = true,
.output_body = true, .output_body = true,
.crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO }, .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.output_body, .name = "body" },
{ .opt_bool = &params.include_html, .name = "include-html" }, { .opt_bool = &params.include_html, .name = "include-html" },
{ .opt_int = &params.duplicate, .name = "duplicate" }, { .opt_int = &params.duplicate, .name = "duplicate" },
{ .opt_int = &params.limit, .name = "limit" },
{ .opt_int = &params.offset, .name = "offset" },
{ .opt_inherit = notmuch_shared_options }, { .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 First, you need to get the corpus. If you don't already have the gpg
key for David Bremner, run 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 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"). (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. # 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 unable to run because of missing prerequisites, but not explicitly
skipped by the user, as failures. 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 Writing Tests
------------- -------------
The test script is written as a shell script. It is to be named as 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_expect_success 'test -f "${NOTMUCH_CONFIG}"'
test_begin_subtest 'PATH is set to build directory' test_begin_subtest 'PATH is set to build directory'
test_subtest_broken_for_installed
test_expect_equal \ test_expect_equal \
"$(dirname ${TEST_DIRECTORY})" \ "$(dirname ${TEST_DIRECTORY})" \
"$(echo $PATH|cut -f1 -d: | sed -e 's,/test/valgrind/bin$,,')" "$(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_begin_subtest 'notmuch --version'
test_expect_success '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_begin_subtest 'notmuch --help tag'
test_expect_success 'notmuch --help tag' test_expect_success 'notmuch --help tag'
test_begin_subtest 'notmuch help tag' test_begin_subtest 'notmuch help tag'
test_expect_success 'notmuch help tag' test_expect_success 'notmuch help tag'
else else
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest 'notmuch --help tag (man pages not available)' test_begin_subtest 'notmuch --help tag (man pages not available)'
test_expect_success 'test_must_fail notmuch --help tag >/dev/null' 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 database.path=MAIL_DIR
foo.list=this;is another;list value; foo.list=this;is another;list value;
foo.string=this is another string value foo.string=this is another string value
index.as_text=
maildir.synchronize_flags=true maildir.synchronize_flags=true
new.ignore= new.ignore=
new.tags=unread;inbox new.tags=unread;inbox

View file

@ -21,7 +21,8 @@ baz
EOF EOF
expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output 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" test_begin_subtest "setup consistent with config-set for single items"
# note this relies on the config state from the previous test. # note this relies on the config state from the previous test.

View file

@ -383,7 +383,22 @@ No new mail. Removed 1 message.
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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_begin_subtest "Xapian exception: read only files"
test_subtest_broken_for_root
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.* chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' ) output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.* chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@ -455,12 +470,4 @@ Date: Fri, 17 Jun 2016 22:14:41 -0400
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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 test_done

View file

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

View file

@ -157,7 +157,7 @@ print("4: {} messages".format(query.count_messages()))
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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)" test_begin_subtest "and of exact terms (query=sexp)"
output=$(notmuch count --query=sexp '(and "wonderful" "wizard")') 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) 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_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:" test_begin_subtest "Search by mid:"
add_message '[subject]="search by mid"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' 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) 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; 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; utf8-sübjéct (inbox unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by id (inbox unread) thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by 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 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 tag (inbox searchbytag unread)
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread) thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; search by thread (inbox unread)

View file

@ -2,7 +2,7 @@
test_description='"notmuch search" in several variations' test_description='"notmuch search" in several variations'
. $(dirname "$0")/test-lib.sh || exit 1 . $(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" printf "Skipping due to missing sfsexp library\n"
test_done test_done
fi fi

View file

@ -325,7 +325,7 @@ cat <<EOF >EXPECTED
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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" test_begin_subtest "sexpr query: all messages"
notmuch address '*' > EXPECTED notmuch address '*' > EXPECTED
notmuch address --query=sexp '()' > OUTPUT 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_expect_code 1 'notmuch tag +- One'
test_begin_subtest "Xapian exception: read only files" test_begin_subtest "Xapian exception: read only files"
test_subtest_broken_for_root
chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.* chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' ) output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.* chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
@ -327,7 +328,7 @@ test_expect_equal "$output" "A Xapian exception occurred opening database"
add_email_corpus 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 '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or 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\", \"tags\": [\"inbox\",
\"unread\"]}]" \"unread\"]}]"
if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_begin_subtest "Search message: json, 64-bit timestamp" 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 test_subtest_known_broken
fi fi
add_message "[subject]=\"json-search-64bit-timestamp-subject\"" "[date]=\"Tue, 01 Jan 2999 12:00:00 -0000\"" "[body]=\"json-search-64bit-timestamp-message\"" 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], \"query\": [\"id:$gen_msg_id\", null],
\"tags\": [\"inbox\", \"tags\": [\"inbox\",
\"unread\"]}]" \"unread\"]}]"
fi # NOTMUCH_TEST_INSTALLED undefined / empty
test_begin_subtest "Format version: too low" test_begin_subtest "Format version: too low"
test_expect_code 20 "notmuch search --format-version=0 \\*" 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 notmuch reply id:${gen_msg_id} >OUTPUT 2>&1 && echo OK >> OUTPUT
test_expect_equal_file basic.expected 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)" test_begin_subtest "Basic reply (query=sexp)"
notmuch reply --query=sexp "(id ${gen_msg_id})" >OUTPUT 2>&1 && echo OK >> OUTPUT 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 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)'" test_begin_subtest "dump --query=sexp -- '(from cworth)'"
notmuch dump --query=sexp -- '(from cworth)' > dump-dash-cworth.actual2 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_expect_equal_file dump-cworth.expected dump-outfile-dash-inbox.actual
test_begin_subtest "Check for a safe set of message-ids" 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:// > EXPECTED
notmuch search --output=messages from:cworth | sed s/^id:// |\ notmuch search --output=messages from:cworth | sed s/^id:// |\
$TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT $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_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest 'format=batch-tag, checking encoded output' test_begin_subtest 'format=batch-tag, checking encoded output'
test_subtest_broken_for_installed
NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\ NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$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_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)" test_begin_subtest "Verify that sent messages are saved/searchable (via FCC)"
test_subtest_broken_for_installed
notmuch new > /dev/null notmuch new > /dev/null
output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize) output=$(notmuch search 'subject:"testing message sent via SMTP"' | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; Testing message sent via SMTP (inbox)" test_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_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Reply within emacs" test_begin_subtest "Reply within emacs"
test_subtest_broken_for_installed
test_emacs '(let ((message-hidden-headers ''())) test_emacs '(let ((message-hidden-headers ''()))
(notmuch-search "subject:\"testing message sent via SMTP\"") (notmuch-search "subject:\"testing message sent via SMTP\"")
(notmuch-test-wait) (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_expect_equal_message_body sent_message "$msg_file"
test_begin_subtest "signed part content-type indexing" 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 notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize > OUTPUT
cat <<EOF >EXPECTED cat <<EOF >EXPECTED
thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed) 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) output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example)
test_valid_json "$output" test_valid_json "$output"
if [ -z "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_begin_subtest "Verify signature on PKCS#7 SignedData message" 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 test_subtest_known_broken
fi fi
output=$(notmuch show --format=json id:smime-onepart-signed@protected-headers.example) 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' \ 'expires:[0][0][0]["crypto"]["signed"]["status"][0]["expires"]=2611032858' \
'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \ 'fingerprint:[0][0][0]["crypto"]["signed"]["status"][0]["fingerprint"]="702BA4B157F1E2B7D16B0C6A5FFC8A7DE2057DEB"' \
'status:[0][0][0]["crypto"]["signed"]["status"][0]["status"]="good"' '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" test_begin_subtest "Verify signature on PKCS#7 SignedData message signer User ID"
if [ $NOTMUCH_GMIME_X509_CERT_VALIDITY -ne 1 ]; then 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 . $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest 'running test' run_test test_begin_subtest 'running test' run_test
mkdir -p ${PWD}/fakedb/.notmuch mkdir -p ${PWD}/fakedb/.notmuch
$TEST_DIRECTORY/symbol-test ${PWD}/fakedb ${PWD}/nonexistent 2>&1 \ $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} test_require_external_prereq ${NOTMUCH_PYTHON}
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
add_email_corpus add_email_corpus
add_gnupg_home add_gnupg_home

View file

@ -2,10 +2,13 @@
test_description="python bindings (pytest)" test_description="python bindings (pytest)"
. $(dirname "$0")/test-lib.sh || exit 1 . $(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 test_done
fi 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)" test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage

View file

@ -2,7 +2,7 @@
test_description="python bindings (notmuch test suite)" test_description="python bindings (notmuch test suite)"
. $(dirname "$0")/test-lib.sh || exit 1 . $(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 test_done
fi fi

View file

@ -2,7 +2,7 @@
test_description="ruby bindings" test_description="ruby bindings"
. $(dirname "$0")/test-lib.sh || exit 1 . $(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 test_subtest_missing_external_prereq_["ruby development files"]=t
fi fi
@ -12,10 +12,14 @@ test_ruby() {
( (
cat <<-EOF cat <<-EOF
require 'notmuch' require 'notmuch'
db = Notmuch::Database.new('$MAIL_DIR') db = Notmuch::Database.new()
EOF EOF
cat 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 test_expect_equal_file EXPECTED OUTPUT
} }

View file

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

View file

@ -200,6 +200,30 @@ test_emacs '(test-log-error
(notmuch-tree "*")))' (notmuch-tree "*")))'
test_expect_equal "$(cat MESSAGES)" "COMPLETE" 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 add_email_corpus duplicate
ID3=87r2ecrr6x.fsf@zephyr.silentflame.com ID3=87r2ecrr6x.fsf@zephyr.silentflame.com

View file

@ -2,6 +2,10 @@
test_description="hex encoding and decoding" test_description="hex encoding and decoding"
. $(dirname "$0")/test-lib.sh || exit 1 . $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "round trip" test_begin_subtest "round trip"
find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT $TEST_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" test_description="date/time parser module"
. $(dirname "$0")/test-lib.sh || exit 1 . $(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 # Sanity/smoke tests for the date/time parser independent of notmuch
_date () { _date () {

View file

@ -35,7 +35,7 @@ notmuch show --entire-thread=true --sort=oldest-first $QUERY > OUTPUT
test_expect_equal_file EXPECTED 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 '(and "wonderful" "wizard")' 'wonderful and wizard'
test_query_syntax '(or "php" "wizard")' 'php or 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 . $(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_begin_subtest "future database versions abort open"
${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 "" ${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') 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 . $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED}" ]; then
test_done
fi
add_email_corpus add_email_corpus
test_begin_subtest "building database" test_begin_subtest "building database"
@ -516,4 +520,31 @@ cat <<EOF > EXPECTED
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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 test_done

View file

@ -95,7 +95,7 @@ subtotal=$(notmuch count lastmod:..$lastmod)
result=$(($subtotal == $total-1)) result=$(($subtotal == $total-1))
test_expect_equal 1 "$result" 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)' test_begin_subtest 'exclude one message using negative lastmod (sexp)'
total=$(notmuch count '*') total=$(notmuch count '*')
notmuch tag +${RANDOM} id:4EFC743A.3060609@april.org notmuch tag +${RANDOM} id:4EFC743A.3060609@april.org

View file

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

View file

@ -21,6 +21,10 @@ test_description='thread breakage during reindexing'
. $(dirname "$0")/test-lib.sh || exit 1 . $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
message_a () { message_a () {
mkdir -p ${MAIL_DIR}/cur mkdir -p ${MAIL_DIR}/cur
cat > ${MAIL_DIR}/cur/a <<EOF 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; notmuch_message_properties_t *list;
for (list = notmuch_message_get_properties (message, prefix, exact); for (list = notmuch_message_get_properties (message, prefix, exact);
notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) { 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); notmuch_message_properties_destroy (list);
} }
@ -89,17 +89,6 @@ testkey2 = NULL
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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)" test_begin_subtest "testing string map binary search (via message properties)"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
{ {
@ -157,7 +146,28 @@ print_properties (message, "testkey1", TRUE);
EOF EOF
cat <<'EOF' >EXPECTED cat <<'EOF' >EXPECTED
== stdout == == 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 == == stderr ==
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -171,10 +181,9 @@ print_properties (message, "testkey1", TRUE);
EOF EOF
cat <<'EOF' >EXPECTED cat <<'EOF' >EXPECTED
== stdout == == stdout ==
alice testkey1 = alice
bob testkey1 = bob
testvalue1 testkey1 = testvalue2
testvalue2
== stderr == == stderr ==
EOF EOF
test_expect_equal_file EXPECTED OUTPUT 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")); EXPECT0(notmuch_message_add_property (message, "testkey3", "alice3"));
print_properties (message, "testkey", FALSE); print_properties (message, "testkey", FALSE);
EOF 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 cat <<'EOF' >EXPECTED
== stdout == == stdout ==
alice testkey1 = alice
bob testkey1 = bob
testvalue1 testkey1 = testvalue2
testvalue2 testkey3 = alice3
alice3 testkey3 = bob3
bob3 testkey3 = testvalue3
testvalue3
== stderr == == stderr ==
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -248,7 +244,7 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "dump message properties" test_begin_subtest "dump message properties"
cat <<EOF > 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 EOF
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
EXPECT0(notmuch_message_add_property (message, "fancy key with áccènts", "import value with =")); 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" test_begin_subtest "dump _only_ message properties"
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
#notmuch-dump batch-tag:3 properties #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 EOF
notmuch dump --include=properties > OUTPUT notmuch dump --include=properties > OUTPUT
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -328,7 +324,6 @@ EOF
cat <<'EOF' > EXPECTED cat <<'EOF' > EXPECTED
testkey1 = alice testkey1 = alice
testkey1 = bob testkey1 = bob
testkey1 = testvalue1
testkey1 = testvalue2 testkey1 = testvalue2
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
@ -344,7 +339,6 @@ EOF
cat <<'EOF' > EXPECTED cat <<'EOF' > EXPECTED
testkey1 = alice testkey1 = alice
testkey1 = bob testkey1 = bob
testkey1 = testvalue1
testkey1 = testvalue2 testkey1 = testvalue2
testkey3 = alice3 testkey3 = alice3
testkey3 = bob3 testkey3 = bob3
@ -362,4 +356,49 @@ for (key,val) in msg.get_properties("testkey",True):
EOF EOF
test_expect_equal_file /dev/null OUTPUT 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 test_done

View file

@ -5,7 +5,7 @@ test_description='reindexing messages'
add_email_corpus add_email_corpus
if [ $NOTMUCH_HAVE_SFSEXP -eq 1 ]; then if [ "${NOTMUCH_HAVE_SFSEXP-0}" = "1" ]; then
count=$(notmuch count --lastmod '*' | cut -f 3) count=$(notmuch count --lastmod '*' | cut -f 3)
for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \ 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 . $(dirname "$0")/test-lib.sh || exit 1
if [ -n "${NOTMUCH_TEST_INSTALLED-}" ]; then
test_done
fi
test_begin_subtest "good message ids" test_begin_subtest "good message ids"
${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT ${TEST_DIRECTORY}/message-id-parse <<EOF >OUTPUT
<018b1a8f2d1df62e804ce88b65401304832dfbbf.1346614915.git.jani@nikula.org> <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' test_description='run code with ASAN enabled against the library'
. $(dirname "$0")/test-lib.sh || exit 1 . $(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" printf "Skipping due to missing ASAN support\n"
test_done test_done
fi fi
if [ -n "${LD_PRELOAD-}" ]; then
printf "Skipping due to ASAN LD_PRELOAD restrictions\n"
test_done
fi
add_email_corpus add_email_corpus
TEST_CFLAGS="-fsanitize=address" TEST_CFLAGS="${TEST_CFLAGS:-} -fsanitize=address"
test_begin_subtest "open and destroy" test_begin_subtest "open and destroy"
test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} <<EOF 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' test_description='"notmuch git" to save and restore tags'
. $(dirname "$0")/test-lib.sh || exit 1 . $(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" printf "Skipping due to missing sfsexp library\n"
test_done test_done
fi fi
@ -233,6 +233,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "invoke as nmbug sets defaults" 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 "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^\(prefix\|repository\)' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
prefix = notmuch:: prefix = notmuch::
@ -241,6 +242,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_DIR works when invoked as nmbug" 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 NOTMUCH_GIT_DIR=`pwd`/foo "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
repository = CWD/foo 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_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 config set git.path `pwd`/bar
NOTMUCH_GIT_DIR=`pwd`/remote.git "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT NOTMUCH_GIT_DIR=`pwd`/remote.git "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^repository' | notmuch_dir_sanitize > OUTPUT
notmuch config set git.path notmuch config set git.path
@ -274,6 +277,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as 'nmbug'" 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 NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
prefix = env:: prefix = env::
@ -281,6 +285,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX works when invoked as nmbug" 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 NOTMUCH_GIT_PREFIX=foo:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
cat <<EOF > EXPECTED cat <<EOF > EXPECTED
prefix = foo:: prefix = foo::
@ -288,6 +293,7 @@ EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "env variable NOTMUCH_GIT_PREFIX overrides config when invoked as 'nmbug'" 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 config set git.tag_prefix config::
NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT NOTMUCH_GIT_PREFIX=env:: "$NOTMUCH_BUILDDIR"/nmbug -ldebug status |& grep '^prefix' | notmuch_dir_sanitize > OUTPUT
notmuch config set git.path 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