Skip to content

Instantly share code, notes, and snippets.

@mhagger
Created March 27, 2016 07:29
Show Gist options
  • Save mhagger/3a1af88cad66c0595330 to your computer and use it in GitHub Desktop.
Save mhagger/3a1af88cad66c0595330 to your computer and use it in GitHub Desktop.
#! /bin/sh
USAGE="git test-range [--test=NAME] OPTS RANGE [-- [COMMAND]]..."
LONG_USAGE="Run COMMAND for each commit in the specified RANGE in reverse order,
stopping if the command fails. The return code is that of the last
command executed (i.e., 0 only if the command succeeded for every
commit in the range).
Options:
--forget: forget any existing test results for the range.
--force | -f: forget any existing test results for the range and test it again.
--retest: if any commit in the range is marked as 'bad', try testing it again.
--keep-going | -k: if a commit fails the test, continue testing other commits
rather than aborting.
"
SUBDIRECTORY_OK=TRUE
notes_ref=
. "$(git --exec-path)/git-sh-setup"
read_status() {
if test -z "$notes_ref"
then
echo "unknown"
return
fi
local r="$1"
local rs="$2"
local status
if ! status="$(git notes --ref=$notes_ref show "$r^{tree}" 2>/dev/null)"
then
echo "unknown"
return
fi
case "$status" in
good|bad)
echo "$status"
;;
*)
echo 1>&2 "fatal: unrecognized status for tree $rs^{tree}!"
exit 3
;;
esac
}
write_note() {
if test -z "$notes_ref"
then
return
fi
r="$1"
value="$2"
if git notes --ref=$notes_ref add -f "$r^{tree}" -m "$value"
then
echo "Marked tree $r^{tree} to be $value"
else
echo 1>&2 "fatal: error adding note to $r^{tree}"
exit 3
fi
}
forget_note() {
if test -z "$notes_ref"
then
return
fi
r="$1"
if ! git notes --ref=$notes_ref remove --ignore-missing "$r^{tree}"
then
echo 1>&2 "fatal: error removing note from $r^{tree}"
exit 3
fi
}
test_revision() {
r="$1"
shift
git --no-pager log -1 --decorate $r &&
git co $r &&
if test -n "$command"
then
eval "$command"
else
"$@"
fi
}
test_and_record() {
local r="$1"
shift
test_revision $r "$@"
local retcode=$?
if test $retcode = 0
then
write_note $r "good"
else
echo
echo "*******************************************************************************"
echo "FAILED ON COMMIT $r"
echo
git --no-pager log -1 --decorate $r
echo "*******************************************************************************"
echo
echo "FAILURE!"
write_note $r "bad"
return $retcode
fi
}
setup_test() {
echo "setup_test $*"
name="$1"
notes_ref="tests/$name"
command="$(git config --get "test.$name.command")"
if test -z "$command"
then
echo 1>&2 "fatal: test $name is not defined!"
exit 2
fi
echo "Using test $name; command: $command"
}
require_clean_work_tree "test-range"
command=
force=false
forget=false
retest=false
keep_going=false
while test $# != 0
do
case "$1" in
--test)
if test $# -lt 2
then
usage
exit 2
fi
setup_test "$2"
shift 2
;;
--test=*)
setup_test "$(echo "$1" | sed -e 's/^--test=//')"
shift
;;
--force|-f)
force=true
shift
;;
--forget)
forget=true
shift
;;
--retest)
retest=true
shift
;;
--keep-going|-k)
keep_going=true
shift
;;
*)
break
;;
esac
done
if test $# -lt 1
then
usage
exit 2
fi
range=
while test $# != 0
do
case "$1" in
--)
shift
break
;;
*)
range="$range $1"
shift
;;
esac
done
if test $# != 0
then
# Use the rest of the arguments as the test command line.
if test -n "$command"
then
echo "error: both --test and command specified!"
exit 2
fi
else
if test -z "$name"
then
setup_test "default"
fi
fi
head=$(git symbolic-ref HEAD 2>/dev/null || git rev-parse HEAD)
if $force || $forget
then
for r in $(git rev-list --reverse $range)
do
forget_note $r
done
if $forget
then
exit 0
fi
fi
fail_count=0
for r in $(git rev-list --reverse $range)
do
rs="$(git rev-parse --short "$r")"
status="$(read_status $r $rs)"
echo "Old status: $status"
if test "$status" = "good"
then
echo "Tree $rs^{tree} is already known to be good."
continue
fi
if test "$status" = "bad"
then
if $retest
then
echo "Tree $rs^{tree} was previously tested to be bad; retesting..."
status="unknown"
# fall through
else
echo "Tree $rs^{tree} is already known to be bad!"
status="failed"
retcode=1
fi
fi
if test "$status" = "unknown"
then
test_and_record $r "$@"
retcode=$?
if test $retcode = 0
then
continue
fi
fi
# This commit has failed the test.
if $keep_going
then
fail_count=$((fail_count + 1))
continue
else
exit $retcode
fi
done
git checkout -f ${head#refs/heads/}
echo
case $fail_count in
0)
echo "ALL TESTS SUCCESSFUL"
exit 0
;;
1)
echo "!!! $fail_count TEST FAILED !!!"
exit 1
;;
*)
echo "!!! $fail_count TESTS FAILED !!!"
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment