This obsoletes [1]. Interdiff is below. The main changes are dealing with hooks in new locations, and some documentation. For a summary, of changes, see the new documentation, in patch 38/38.
[1] id:20210103233547.122707-1-da...@tethera.net doc/man1/notmuch-config.rst | 43 +++++++-- doc/man5/notmuch-hooks.rst | 6 - hooks.c | 7 - lib/config.cc | 5 + lib/notmuch.h | 3 lib/open.cc | 42 +++++++++ notmuch-client.h | 2 notmuch-count.c | 2 notmuch-insert.c | 7 - notmuch-new.c | 4 test/T140-excludes.sh | 15 +++ test/T400-hooks.sh | 197 ++++++++++++++++++++++++-------------------- test/T590-libconfig.sh | 32 +++++++ test/T750-gzip.sh | 2 test/test-lib.el | 4 15 files changed, 258 insertions(+), 113 deletions(-) diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 769f336a..bc597957 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -17,10 +17,6 @@ DESCRIPTION The **config** command can be used to get or set settings in the notmuch configuration file and corresponding database. -Items marked **[STORED IN DATABASE]** are only in the database. They -should not be placed in the configuration file, and should be accessed -programmatically as described in the SYNOPSIS above. - **get** The value of the specified configuration item is printed to stdout. If the item has multiple values (it is a list), each value @@ -54,6 +50,11 @@ The available configuration items are described below. Default: ``$MAILDIR`` variable if set, otherwise ``$HOME/mail``. +**database.hook_dir** + + Directory containing hooks run by notmuch commands. See + **notmuch-hooks(5)**. + **user.name** Your full name. @@ -134,7 +135,7 @@ The available configuration items are described below. Default: ``true``. -**index.decrypt** **[STORED IN DATABASE]** +**index.decrypt** Policy for decrypting encrypted messages during indexing. Must be one of: ``false``, ``auto``, ``nostash``, or ``true``. @@ -187,7 +188,7 @@ The available configuration items are described below. Default: ``auto``. -**index.header.<prefix>** **[STORED IN DATABASE]** +**index.header.<prefix>** Define the query prefix <prefix>, based on a mail header. For example ``index.header.List=List-Id`` will add a probabilistic prefix ``List:`` that searches the ``List-Id`` field. User @@ -202,7 +203,7 @@ The available configuration items are described below. (since notmuch 0.30, "compact" and "field_processor" are always included.) -**query.<name>** **[STORED IN DATABASE]** +**query.<name>** Expansion for named query called <name>. See **notmuch-search-terms(7)** for more information about named queries. @@ -214,8 +215,32 @@ The following environment variables can be used to control the behavior of notmuch. **NOTMUCH\_CONFIG** - Specifies the location of the notmuch configuration file. Notmuch - will use ${HOME}/.notmuch-config if this variable is not set. + Specifies the location of the notmuch configuration file. + +**NOTMUCH_PROFILE** + Selects among notmuch configurations. + +FILES +===== + +CONFIGURATION +------------- + +If ``NOTMUCH_CONFIG`` is unset, notmuch tries (in order) + +- ``$XDG_CONFIG_HOME/notmuch/<profile>/config`` where ``<profile>`` is + defined by ``$NOTMUCH_PROFILE`` or "default" +- ``${HOME}/.notmuch-config<profile>`` where ``<profile>`` is + ``.$NOTMUCH_PROFILE`` or "" + +Hooks +----- + +If ``database.hook_dir`` is unset, notmuch tries (in order) + +- ``$XDG_CONFIG_HOME/notmuch/<profile>/hooks`` where ``<profile>`` is + defined by ``$NOTMUCH_PROFILE`` or "default" +- ``<database.path>/.notmuch/hooks`` SEE ALSO ======== diff --git a/doc/man5/notmuch-hooks.rst b/doc/man5/notmuch-hooks.rst index de2ed0c2..c509afb3 100644 --- a/doc/man5/notmuch-hooks.rst +++ b/doc/man5/notmuch-hooks.rst @@ -5,15 +5,15 @@ notmuch-hooks SYNOPSIS ======== -$DATABASEDIR/.notmuch/hooks/* +<hook_dir>/{pre-new, post-new, post-insert} DESCRIPTION =========== Hooks are scripts (or arbitrary executables or symlinks to such) that notmuch invokes before and after certain actions. These scripts reside -in the .notmuch/hooks directory within the database directory and must -have executable permissions. +in a directory defined as described in **notmuch-config(1)**. They +must have executable permissions. The currently available hooks are described below. diff --git a/hooks.c b/hooks.c index 59c58070..ec89b22e 100644 --- a/hooks.c +++ b/hooks.c @@ -24,14 +24,15 @@ #include <sys/wait.h> int -notmuch_run_hook (const char *db_path, const char *hook) +notmuch_run_hook (notmuch_database_t *notmuch, const char *hook) { char *hook_path; int status = 0; pid_t pid; - hook_path = talloc_asprintf (NULL, "%s/%s/%s/%s", db_path, ".notmuch", - "hooks", hook); + hook_path = talloc_asprintf (notmuch, "%s/%s", + notmuch_config_get (notmuch, NOTMUCH_CONFIG_HOOK_DIR), + hook); if (hook_path == NULL) { fprintf (stderr, "Out of memory\n"); return 1; diff --git a/lib/config.cc b/lib/config.cc index 7a1494be..4b115a07 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -303,6 +303,8 @@ notmuch_config_values_get (notmuch_config_values_t *values) { void notmuch_config_values_start (notmuch_config_values_t *values) { + if (values == NULL) + return; if (values->children) { talloc_free (values->children); } @@ -388,6 +390,8 @@ _notmuch_config_key_to_string (notmuch_config_key_t key) { switch (key) { case NOTMUCH_CONFIG_DATABASE_PATH: return "database.path"; + case NOTMUCH_CONFIG_HOOK_DIR: + return "database.hook_dir"; case NOTMUCH_CONFIG_EXCLUDE_TAGS: return "search.exclude_tags"; case NOTMUCH_CONFIG_NEW_TAGS: @@ -426,6 +430,7 @@ _notmuch_config_default (void *ctx, notmuch_config_key_t key) { return "inbox;unread"; case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS: return "true"; + case NOTMUCH_CONFIG_HOOK_DIR: case NOTMUCH_CONFIG_NEW_IGNORE: case NOTMUCH_CONFIG_USER_NAME: case NOTMUCH_CONFIG_PRIMARY_EMAIL: diff --git a/lib/notmuch.h b/lib/notmuch.h index e51b738d..3c3fd487 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -2461,6 +2461,7 @@ notmuch_config_list_destroy (notmuch_config_list_t *config_list); typedef enum _notmuch_config_key { NOTMUCH_CONFIG_FIRST, NOTMUCH_CONFIG_DATABASE_PATH = NOTMUCH_CONFIG_FIRST, + NOTMUCH_CONFIG_HOOK_DIR, NOTMUCH_CONFIG_EXCLUDE_TAGS, NOTMUCH_CONFIG_NEW_TAGS, NOTMUCH_CONFIG_NEW_IGNORE, @@ -2562,7 +2563,7 @@ notmuch_config_values_move_to_next (notmuch_config_values_t *values); /** * reset the 'values' iterator to the first element * - * @param[in,out] values iterator + * @param[in,out] values iterator. A NULL value is ignored. * * @since libnotmuch 5.4 (notmuch 0.32) * diff --git a/lib/open.cc b/lib/open.cc index 92661271..eb7c8a01 100644 --- a/lib/open.cc +++ b/lib/open.cc @@ -67,6 +67,44 @@ _xdg_dir (void *ctx, profile_name); } +static notmuch_status_t +_choose_hook_dir (notmuch_database_t *notmuch, + const char *profile, + char **message) +{ + const char *config; + const char *hook_dir; + struct stat st; + int err; + + hook_dir = notmuch_config_get (notmuch, NOTMUCH_CONFIG_HOOK_DIR); + + if (hook_dir) + return NOTMUCH_STATUS_SUCCESS; + + config = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile); + if (! config) + return NOTMUCH_STATUS_PATH_ERROR; + + hook_dir = talloc_asprintf (notmuch, "%s/hooks", config); + + err = stat (hook_dir, &st); + if (err) { + if (errno == ENOENT) { + const char *database_path = notmuch_database_get_path (notmuch); + hook_dir = talloc_asprintf (notmuch, "%s/.notmuch/hooks", database_path); + } else { + IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n", + hook_dir, strerror (errno))); + return NOTMUCH_STATUS_FILE_ERROR; + } + } + + _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_HOOK_DIR, hook_dir); + + return NOTMUCH_STATUS_SUCCESS; +} + static notmuch_status_t _load_key_file (const char *path, const char *profile, @@ -300,6 +338,10 @@ notmuch_database_open_with_config (const char *database_path, if (status) goto DONE; + status = _choose_hook_dir (notmuch, profile, &message); + if (status) + goto DONE; + status = _notmuch_config_load_defaults (notmuch); if (status) goto DONE; diff --git a/notmuch-client.h b/notmuch-client.h index 9e09c36a..f60f5406 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -339,7 +339,7 @@ const char * _notmuch_config_get_path (notmuch_config_t *config); int -notmuch_run_hook (const char *db_path, const char *hook); +notmuch_run_hook (notmuch_database_t *notmuch, const char *hook); bool debugger_is_active (void); diff --git a/notmuch-count.c b/notmuch-count.c index 321c9207..048b1f44 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -80,7 +80,7 @@ print_count (notmuch_database_t *notmuch, const char *query_str, return -1; } - for (; + for (notmuch_config_values_start (exclude_tags); notmuch_config_values_valid (exclude_tags); notmuch_config_values_move_to_next (exclude_tags)) { diff --git a/notmuch-insert.c b/notmuch-insert.c index e483b949..0f272e2e 100644 --- a/notmuch-insert.c +++ b/notmuch-insert.c @@ -481,7 +481,6 @@ notmuch_insert_command (unused(notmuch_config_t *config),notmuch_database_t *not notmuch_process_shared_options (argv[0]); - /* XXX TODO replace this use of DATABASE_PATH with something specific to hooks */ db_path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH); if (! db_path) @@ -570,7 +569,7 @@ notmuch_insert_command (unused(notmuch_config_t *config),notmuch_database_t *not status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts); /* Commit changes. */ - close_status = notmuch_database_destroy (notmuch); + close_status = notmuch_database_close (notmuch); if (close_status) { /* Hold on to the first error, if any. */ if (! status) @@ -595,9 +594,11 @@ notmuch_insert_command (unused(notmuch_config_t *config),notmuch_database_t *not if (hooks && status == NOTMUCH_STATUS_SUCCESS) { /* Ignore hook failures. */ - notmuch_run_hook (db_path, "post-insert"); + notmuch_run_hook (notmuch, "post-insert"); } + notmuch_database_destroy (notmuch); + talloc_free (local); return status_to_exit (status); diff --git a/notmuch-new.c b/notmuch-new.c index 0f416939..2fc34e2c 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -1167,7 +1167,7 @@ notmuch_new_command (unused(notmuch_config_t *config), notmuch_database_t *notmu } if (hooks) { - ret = notmuch_run_hook (db_path, "pre-new"); + ret = notmuch_run_hook (notmuch, "pre-new"); if (ret) return EXIT_FAILURE; } @@ -1284,7 +1284,7 @@ notmuch_new_command (unused(notmuch_config_t *config), notmuch_database_t *notmu notmuch_database_close (notmuch); if (hooks && ! ret && ! interrupted) - ret = notmuch_run_hook (db_path, "post-new"); + ret = notmuch_run_hook (notmuch, "post-new"); notmuch_database_destroy (notmuch); diff --git a/test/T140-excludes.sh b/test/T140-excludes.sh index cef07095..0cacc41d 100755 --- a/test/T140-excludes.sh +++ b/test/T140-excludes.sh @@ -286,6 +286,21 @@ test_begin_subtest "Count, default exclusion: tag in query (threads)" output=$(notmuch count --output=threads tag:test and tag:deleted) test_expect_equal "$output" "3" +test_begin_subtest "Count, default exclusion, batch" +notmuch count --batch --output=messages<<EOF > OUTPUT +tag:test +tag:test and tag:deleted +tag:test +tag:test and tag:deleted +EOF +cat <<EOF >EXPECTED +2 +4 +2 +4 +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Count, exclude=true: tag in query (messages)" output=$(notmuch count --exclude=true tag:test and tag:deleted) test_expect_equal "$output" "4" diff --git a/test/T400-hooks.sh b/test/T400-hooks.sh index 49c690eb..b9894853 100755 --- a/test/T400-hooks.sh +++ b/test/T400-hooks.sh @@ -2,8 +2,6 @@ test_description='hooks' . $(dirname "$0")/test-lib.sh || exit 1 -HOOK_DIR=${MAIL_DIR}/.notmuch/hooks - create_echo_hook () { local TOKEN="${RANDOM}" mkdir -p ${HOOK_DIR} @@ -16,6 +14,7 @@ EOF } create_failing_hook () { + local HOOK_DIR=${2} mkdir -p ${HOOK_DIR} cat <<EOF >"${HOOK_DIR}/${1}" #!/bin/sh @@ -24,98 +23,120 @@ EOF chmod +x "${HOOK_DIR}/${1}" } -rm_hooks () { - rm -rf ${HOOK_DIR} -} - # add a message to generate mail dir and database add_message # create maildir structure for notmuch-insert mkdir -p "$MAIL_DIR"/{cur,new,tmp} -test_begin_subtest "pre-new is run" -rm_hooks -generate_message -create_echo_hook "pre-new" expected output -notmuch new > /dev/null -test_expect_equal_file expected output - -test_begin_subtest "post-new is run" -rm_hooks -generate_message -create_echo_hook "post-new" expected output -notmuch new > /dev/null -test_expect_equal_file expected output - -test_begin_subtest "post-insert hook is run" -rm_hooks -generate_message -create_echo_hook "post-insert" expected output -notmuch insert < "$gen_msg_filename" -test_expect_equal_file expected output - -test_begin_subtest "pre-new is run before post-new" -rm_hooks -generate_message -create_echo_hook "pre-new" pre-new.expected pre-new.output -create_echo_hook "post-new" post-new.expected post-new.output -notmuch new > /dev/null -test_expect_equal_file post-new.expected post-new.output - -test_begin_subtest "pre-new non-zero exit status (hook status)" -rm_hooks -generate_message -create_failing_hook "pre-new" -output=`notmuch new 2>&1` -test_expect_equal "$output" "Error: pre-new hook failed with status 13" - -# depends on the previous subtest leaving broken hook behind -test_begin_subtest "pre-new non-zero exit status (notmuch status)" -test_expect_code 1 "notmuch new" - -# depends on the previous subtests leaving 1 new message behind -test_begin_subtest "pre-new non-zero exit status aborts new" -rm_hooks -output=$(NOTMUCH_NEW) -test_expect_equal "$output" "Added 1 new message to the database." - -test_begin_subtest "post-new non-zero exit status (hook status)" -rm_hooks -generate_message -create_failing_hook "post-new" -NOTMUCH_NEW 2>output.stderr >output -cat output.stderr >> output -echo "Added 1 new message to the database." > expected -echo "Error: post-new hook failed with status 13" >> expected -test_expect_equal_file expected output - -# depends on the previous subtest leaving broken hook behind -test_begin_subtest "post-new non-zero exit status (notmuch status)" -test_expect_code 1 "notmuch new" - -test_begin_subtest "post-insert hook does not affect insert status" -rm_hooks -generate_message -create_failing_hook "post-insert" -test_expect_success "notmuch insert < \"$gen_msg_filename\" > /dev/null" - -test_begin_subtest "hook without executable permissions" -rm_hooks -mkdir -p ${HOOK_DIR} -cat <<EOF >"${HOOK_DIR}/pre-new" -#!/bin/sh -echo foo +for config in traditional profile explicit XDG; do + unset NOTMUCH_PROFILE + notmuch config set database.hook_dir + case $config in + traditional) + HOOK_DIR=${MAIL_DIR}/.notmuch/hooks + ;; + profile) + dir=${HOME}/.config/notmuch/other + mkdir -p ${dir} + HOOK_DIR=${dir}/hooks + cp ${NOTMUCH_CONFIG} ${dir}/config + export NOTMUCH_PROFILE=other + ;; + explicit) + HOOK_DIR=${HOME}/.notmuch-hooks + mkdir -p $HOOK_DIR + notmuch config set database.hook_dir $HOOK_DIR + ;; + *) + HOOK_DIR=${HOME}/.config/notmuch/default/hooks + ;; + esac + + test_begin_subtest "pre-new is run [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_echo_hook "pre-new" expected output $HOOK_DIR + notmuch new > /dev/null + test_expect_equal_file expected output + + test_begin_subtest "post-new is run [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_echo_hook "post-new" expected output $HOOK_DIR + notmuch new > /dev/null + test_expect_equal_file expected output + + test_begin_subtest "post-insert hook is run [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_echo_hook "post-insert" expected output $HOOK_DIR + notmuch insert < "$gen_msg_filename" + test_expect_equal_file expected output + + test_begin_subtest "pre-new is run before post-new [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_echo_hook "pre-new" pre-new.expected pre-new.output $HOOK_DIR + create_echo_hook "post-new" post-new.expected post-new.output $HOOK_DIR + notmuch new > /dev/null + test_expect_equal_file post-new.expected post-new.output + + test_begin_subtest "pre-new non-zero exit status (hook status) [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_failing_hook "pre-new" $HOOK_DIR + output=`notmuch new 2>&1` + test_expect_equal "$output" "Error: pre-new hook failed with status 13" + + # depends on the previous subtest leaving broken hook behind + test_begin_subtest "pre-new non-zero exit status (notmuch status) [${config}]" + test_expect_code 1 "notmuch new" + + # depends on the previous subtests leaving 1 new message behind + test_begin_subtest "pre-new non-zero exit status aborts new [${config}]" + rm -rf ${HOOK_DIR} + output=$(NOTMUCH_NEW) + test_expect_equal "$output" "Added 1 new message to the database." + + test_begin_subtest "post-new non-zero exit status (hook status) [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_failing_hook "post-new" $HOOK_DIR + NOTMUCH_NEW 2>output.stderr >output + cat output.stderr >> output + echo "Added 1 new message to the database." > expected + echo "Error: post-new hook failed with status 13" >> expected + test_expect_equal_file expected output + + # depends on the previous subtest leaving broken hook behind + test_begin_subtest "post-new non-zero exit status (notmuch status) [${config}]" + test_expect_code 1 "notmuch new" + + test_begin_subtest "post-insert hook does not affect insert status [${config}]" + rm -rf ${HOOK_DIR} + generate_message + create_failing_hook "post-insert" $HOOK_DIR + test_expect_success "notmuch insert < \"$gen_msg_filename\" > /dev/null" + + test_begin_subtest "hook without executable permissions [${config}]" + rm -rf ${HOOK_DIR} + mkdir -p ${HOOK_DIR} + cat <<EOF >"${HOOK_DIR}/pre-new" + #!/bin/sh + echo foo EOF -output=`notmuch new 2>&1` -test_expect_code 1 "notmuch new" - -test_begin_subtest "hook execution failure" -rm_hooks -mkdir -p ${HOOK_DIR} -cat <<EOF >"${HOOK_DIR}/pre-new" -no hashbang, execl fails + output=`notmuch new 2>&1` + test_expect_code 1 "notmuch new" + + test_begin_subtest "hook execution failure [${config}]" + rm -rf ${HOOK_DIR} + mkdir -p ${HOOK_DIR} + cat <<EOF >"${HOOK_DIR}/pre-new" + no hashbang, execl fails EOF -chmod +x "${HOOK_DIR}/pre-new" -test_expect_code 1 "notmuch new" + chmod +x "${HOOK_DIR}/pre-new" + test_expect_code 1 "notmuch new" + rm -rf ${HOOK_DIR} +done test_done diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh index 97f8fdc7..c78ed204 100755 --- a/test/T590-libconfig.sh +++ b/test/T590-libconfig.sh @@ -201,6 +201,37 @@ EOF test_expect_equal_file EXPECTED OUTPUT restore_database +test_begin_subtest "NOTMUCH_CONFIG_HOOK_DIR: traditional" +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% +{ + const char *val = notmuch_config_get (db, NOTMUCH_CONFIG_HOOK_DIR); + printf("database.hook_dir = %s\n", val); +} +EOF +cat <<'EOF' >EXPECTED +== stdout == +database.hook_dir = MAIL_DIR/.notmuch/hooks +== stderr == +EOF +test_expect_equal_file EXPECTED OUTPUT + +test_begin_subtest "NOTMUCH_CONFIG_HOOK_DIR: xdg" +dir="${HOME}/.config/notmuch/default/hooks" +mkdir -p $dir +cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% +{ + const char *val = notmuch_config_get (db, NOTMUCH_CONFIG_HOOK_DIR); + printf("database.hook_dir = %s\n", val); +} +EOF +cat <<'EOF' >EXPECTED +== stdout == +database.hook_dir = CWD/home/.config/notmuch/default/hooks +== stderr == +EOF +rmdir $dir +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "notmuch_config_get_values" cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL% { @@ -333,6 +364,7 @@ EOF cat <<'EOF' >EXPECTED == stdout == MAIL_DIR +MAIL_DIR/.notmuch/hooks inbox;unread NULL diff --git a/test/T750-gzip.sh b/test/T750-gzip.sh index 807086fd..7d550e66 100755 --- a/test/T750-gzip.sh +++ b/test/T750-gzip.sh @@ -171,7 +171,7 @@ test_expect_equal_file EXPECTED OUTPUT add_email_corpus lkml test_begin_subtest "new doesn't run out of file descriptors with many gzipped files" ulimit -n 200 -find ${MAIL_DIR} -name .notmuch -prune -false -o -type f -exec gzip --recursive {} \; +find ${MAIL_DIR} -name .notmuch -prune -o -type f -print0 | xargs -0 gzip -- test_expect_success "notmuch new" test_done diff --git a/test/test-lib.el b/test/test-lib.el index ec16c59c..4de5b292 100644 --- a/test/test-lib.el +++ b/test/test-lib.el @@ -1,4 +1,4 @@ -;; test-lib.el --- auxiliary stuff for Notmuch Emacs tests. +;;; test-lib.el --- auxiliary stuff for Notmuch Emacs tests ;; ;; Copyright © Carl Worth ;; Copyright © David Edmondson @@ -20,6 +20,8 @@ ;; ;; Authors: Dmitry Kurochkin <dmitry.kuroch...@gmail.com> +;;; Code: + (require 'cl-lib) ;; Ensure that the dynamic variables that are defined by this library _______________________________________________ notmuch mailing list -- notmuch@notmuchmail.org To unsubscribe send an email to notmuch-le...@notmuchmail.org