From 0496e05eb4f85b9f80f25d59e440ff4b089c5731 Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Mon, 1 Jan 2018 22:16:48 -0800 Subject: [PATCH] tg: improve handling of configured core.hooksPath Since Git v2.9.0 there is support for a "core.hooksPath" config item, but the implementation semantics are an abomination. Improve the way TopGit deals with such nasties and add some tests specifically for the evil core.hooksPath setting. Signed-off-by: Kyle J. McKay --- t/t1062-hookspath.sh | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++ tg.sh | 63 +++++++++++++++++- 2 files changed, 245 insertions(+), 1 deletion(-) create mode 100755 t/t1062-hookspath.sh diff --git a/t/t1062-hookspath.sh b/t/t1062-hookspath.sh new file mode 100755 index 0000000..79dbe8b --- /dev/null +++ b/t/t1062-hookspath.sh @@ -0,0 +1,183 @@ +#!/bin/sh + +test_description='test core.hooksPath management' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +if vcmp "$git_version" '>=' "2.9"; then + test_set_prereq GIT_2_9 +fi + +test_plan 14 + +write_dummy_script() { + write_script "$@" <<\EOT +exit 0 +EOT +} + +write_dummy_file() { + echo "# dummy file $1" >"$1" +} + +matches() { + eval "case \"$1\" in $2) return 0; esac" + return 1 +} + +do_mergesetup() { + test_might_fail tg -C "$1" update no-such-branch-name 2>&1 +} + +test_expect_success 'setup' ' + mkdir _global _global/hooks insidious insidious/_global insidious/_global/hooks && + ln -s _global/hooks globhookdir && + ln -s . insidious/subdir && + git init -q --template "$PWD/_global/hooks" --bare insidious/_global && + write_dummy_script _global/hooks/pre-auto-gc && + write_dummy_script _global/hooks/pre-receive && + write_dummy_script _global/hooks/update && + write_dummy_script _global/hooks/post-commit && + write_dummy_script _global/hooks/post-receive && + write_dummy_file _global/hooks/prepare-commit-msg && + write_dummy_script _global/hooks/commit-msg.sh && + test_create_repo r1 && + test_create_repo r2 && + mkdir r2/.git/hooks && + test_create_repo r3 && + mkdir r3/.git/hooks && + (cd r3/.git/hooks && ln -s ../../../_global/hooks/* ./) && + rm r3/.git/hooks/prepare-commit-msg r3/.git/hooks/commit-msg.sh && + test_create_repo r4 && + mkdir r4/.git/hooks && + cp _global/hooks/* r4/.git/hooks/ && + rm r4/.git/hooks/prepare-commit-msg r4/.git/hooks/commit-msg.sh && + cp r4/.git/hooks/* insidious/_global/hooks/ && + test ! -d r5 && + cp -R r3 r5 && + test -d r5/.git/hooks && + rm r5/.git/hooks/update && + test ! -d r6 && + cp -R r4 r6 && + test -d r6/.git/hooks && + rm r6/.git/hooks/update && + test ! -d r7 && + cp -R r4 r7 && + test -d r7/.git/hooks && + echo different >> r7/.git/hooks/update && + test ! -d r8 && + cp -R r4 r8 && + test -d r8/.git/hooks && + echo notexec > r8/.git/hooks/update && + chmod a-x r8/.git/hooks/update +' + +test_expect_success GIT_2_9 'relative hookspath' ' + test_config -C r2 core.hooksPath "" && + bad= && + for rpath in hooks .git/hooks ../_global/hooks ../../_global/hooks; do + git -C r2 config core.hooksPath "$rpath" && + result="$(set +vx && do_mergesetup r2)" && + matches "$result" "*\"ignoring non-absolute core.hooks\"[Pp]\"ath\"*" || { + bad=1 && + break + } + done && + test -z "$bad" +' + +test_expect_success GIT_2_9 'no such hookspath' ' + test_config -C r2 core.hooksPath "" && + bad= && + for rpath in hooks .git/hooks ../_global/hooks ../../_global/hooks; do + git -C r2 config core.hooksPath "$PWD/ns1/ns2/ns3/ns4/$rpath" && + result="$(set +vx && do_mergesetup r2)" && + matches "$result" "*\"ignoring non-existent core.hooks\"[Pp]\"ath\"*" || { + bad=1 && + break + } + done && + test -z "$bad" +' + +test_expect_success GIT_2_9 'our absolute hooks' ' + test_config -C r2 core.hooksPath "" && + bad= && + for rpath in hooks hooks/../hooks ../.git/hooks ../../r2/.git/hooks; do + git -C r2 config core.hooksPath "$PWD/r2/.git/$rpath" && + result="$(set +vx && do_mergesetup r2)" && + newcp="$(git -C r2 config core.hooksPath)" && + test "$PWD/r2/.git/$rpath" = "$newcp" && + ! matches "$result" "*\" warning: \"*" || { + bad=1 && + break + } + done && + test -z "$bad" +' + +test_expect_success GIT_2_9 'no gratuitous hookspath' ' + bad= && + for rpath in r?; do + do_mergesetup $rpath >/dev/null && + test_must_fail git config -C $rpath --get core.hooksPath >/dev/null || { + bad=1 && + break + } + done && + test -z "$bad" +' + +friendly="r3 r4" + +for repo in r?; do +case " $friendly " in *" $repo "*);;*) continue; esac +test_expect_success GIT_2_9 '"friendly" hookspath '"$repo" ' + test_config -C $repo core.hooksPath "" && + bad= && + for gpath in _global/hooks globhookdir; do + rm -f "$gpath/pre-commit" && + git -C $repo config core.hooksPath "$PWD/$gpath" && + result="$(set +vx && do_mergesetup $repo)" && + newcp="$(git -C $repo config core.hooksPath)" && + test "$PWD/$gpath" != "$newcp" && + test "$(cd "$PWD/$repo/.git/hooks" && pwd -P)" = "$(cd "$newcp" && pwd -P)" && + ! matches "$result" "*\" warning: \"*" || { + bad=1 && + break + } + done && + test -z "$bad" +' +done + +for repo in r?; do +case " $friendly " in *" $repo "*) continue; esac +test_expect_success GIT_2_9 '"unfriendly" hookspath '"$repo" ' + test_config -C $repo core.hooksPath "" && + bad= && + for gpath in _global/hooks globhookdir; do + rm -f "$gpath/pre-commit" && + git -C $repo config core.hooksPath "$PWD/$gpath" && + result="$(set +vx && do_mergesetup $repo)" && + newcp="$(git -C $repo config core.hooksPath)" && + test "$PWD/$gpath" = "$newcp" && + ! matches "$result" "*\" warning: \"*" || { + bad=1 && + break + } + done && + test -z "$bad" +' +done + +test_expect_success GIT_2_9 'insidious hookspath' ' + git -C insidious/_global config core.hooksPath "$PWD/insidious/subdir/_global/hooks" && + do_mergesetup insidious/_global >/dev/null && + newcp="$(git -C insidious/_global config core.hooksPath)" && + test "$PWD/insidious/subdir/_global/hooks" = "$newcp" +' + +test_done diff --git a/tg.sh b/tg.sh index 458da1b..ae2de22 100644 --- a/tg.sh +++ b/tg.sh @@ -73,6 +73,7 @@ exec_lc_all_c() # any Git translations will still appear for Git commands awk() { exec_lc_all_c awk @AWK_PATH@ "$@"; } cat() { exec_lc_all_c cat cat "$@"; } +cmp() { exec_lc_all_c cmp cmp "$@"; } cut() { exec_lc_all_c cut cut "$@"; } find() { exec_lc_all_c find find "$@"; } grep() { exec_lc_all_c grep grep "$@"; } @@ -1920,6 +1921,53 @@ setup_git_dir_is_bare() fi } +git_hooks_pat_list="\ +[a]pplypatch-ms[g] [p]re-applypatc[h] [p]ost-applypatc[h] [p]re-commi[t] \ +[p]repare-commit-ms[g] [c]ommit-ms[g] [p]ost-commi[t] [p]re-rebas[e] \ +[p]ost-checkou[t] [p]ost-merg[e] [p]re-pus[h] [p]re-receiv[e] [u]pdat[e] \ +[p]ost-receiv[e] [p]ost-updat[e] [p]ush-to-checkou[t] [p]re-auto-g[c] \ +[p]ost-rewrit[e]" + +# git_hooks_dir must already be set to the value of core.hooksPath which +# exists and is an absolute path. The first and only argument is the +# "pwd -P" of the $git_hooks_dir directory. If the core.hooksPath setting +# appears to be "friendly" attempt to alter it to be an absolute path to +# "$git_common_dir/hooks" instead. A "friendly" core.hooksPath setting +# points to a directory for which "$git_common_dir/hooks" already has +# entries which are symbolic links to the same core.hooksPath items. +# There's no POSIX readlink utility, but there is a 'cmp -s' utility so we +# use that instead to check. Also the "friendly" core.hooksPath must be +# something that's recognizable as belonging to a "friendly". +maybe_adjust_friendly_hooks_path() +{ + case "$1" in */_global/hooks);;*) return 0; esac + [ -n "$1" ] && [ -d "$1" ] && [ -d "$git_common_dir/hooks" ] || return 0 + [ -w "$git_common_dir" ] || return 0 + ! [ -e "$git_common_dir/config" ] || { + [ -f "$git_common_dir/config" ] && [ -w "$git_common_dir/config" ] + } || return 0 + oktoswitch=1 + for ghook in $(cd "$1" && eval "echo $git_hooks_pat_list"); do + case "$ghook" in "["*) continue; esac + [ -x "$1/$ghook" ] && + [ -f "$1/$ghook" ] || continue + [ -x "$git_common_dir/hooks/$ghook" ] && + [ -f "$git_common_dir/hooks/$ghook" ] && + cmp -s "$1/$ghook" "$git_common_dir/hooks/$ghook" || { + oktoswitch= + break + } + done + if [ -n "$oktoswitch" ]; then + # a known "friendly" was detected and the hooks match; + # go ahead and silently switch the path + ! git config core.hooksPath "$git_common_dir/hooks" >/dev/null 2>&1 || + git_hooks_dir="$git_common_dir/hooks" + fi + unset_ oktoswitch + return 0 +} + setup_git_dirs() { [ -n "$git_dir" ] || git_dir="$(git rev-parse --git-dir)" @@ -1943,7 +1991,20 @@ setup_git_dirs() if vcmp "$git_version" '>=' "2.9" && gchp="$(git config --path --get core.hooksPath 2>/dev/null)" && [ -n "$gchp" ]; then case "$gchp" in /[!/]*) - git_hooks_dir="$gchp" + if [ -d "$gchp" ]; then + # if core.hooksPath is just another name for + # $git_common_dir/hooks, keep referring to it + # by $git_common_dir/hooks + abscdh="$(cd "$git_common_dir" && pwd -P)/hooks" + abshpd="$(cd "$gchp" && pwd -P)" + if [ "$abshpd" != "$abscdh" ]; then + git_hooks_dir="$gchp" + maybe_adjust_friendly_hooks_path "$abshpd" + fi + unset_ abscdh abshpd + else + [ -n "$1" ] || warn "ignoring non-existent core.hooksPath: $gchp" + fi ;; *) [ -n "$1" ] || warn "ignoring non-absolute core.hooksPath: $gchp" -- 2.11.4.GIT