git/git-bisect.sh
Christian Couder 38a47fd6e3 Bisect: teach "bisect start" to optionally use one bad and many good revs.
One bad commit is fundamentally needed for bisect to run,
and if we beforehand know more good commits, we can narrow
the bisect space down without doing the whole tree checkout
every time we give good commits.

This patch implements:

    git bisect start [<bad> [<good>...]] [--] [<pathspec>...]

as a short-hand for this command sequence:

    git bisect start
    git bisect bad $bad
    git bisect good $good1 $good2...

On the other hand, there may be some confusion between revs
(<bad> and <good>...) and <pathspec>... if -- is not used
and if an invalid rev or a pathspec that looks like a rev is
given.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-04-05 15:13:14 -07:00

368 lines
8 KiB
Bash
Executable file

#!/bin/sh
USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
reset bisect state and start bisection.
git bisect bad [<rev>]
mark <rev> a known-bad revision.
git bisect good [<rev>...]
mark <rev>... known-good revisions.
git bisect next
find next bisection to test and check it out.
git bisect reset [<branch>]
finish bisection search and go back to branch.
git bisect visualize
show bisect status in gitk.
git bisect replay <logfile>
replay bisection log.
git bisect log
show bisect log.
git bisect run <cmd>...
use <cmd>... to automatically bisect.'
. git-sh-setup
require_work_tree
sq() {
@@PERL@@ -e '
for (@ARGV) {
s/'\''/'\'\\\\\'\''/g;
print " '\''$_'\''";
}
print "\n";
' "$@"
}
bisect_autostart() {
test -d "$GIT_DIR/refs/bisect" || {
echo >&2 'You need to start by "git bisect start"'
if test -t 0
then
echo >&2 -n 'Do you want me to do it for you [Y/n]? '
read yesno
case "$yesno" in
[Nn]*)
exit ;;
esac
bisect_start
else
exit 1
fi
}
}
bisect_start() {
#
# Verify HEAD. If we were bisecting before this, reset to the
# top-of-line master first!
#
head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
die "Bad HEAD - I need a symbolic ref"
case "$head" in
refs/heads/bisect)
if [ -s "$GIT_DIR/head-name" ]; then
branch=`cat "$GIT_DIR/head-name"`
else
branch=master
fi
git checkout $branch || exit
;;
refs/heads/*)
[ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
;;
*)
die "Bad HEAD - strange symbolic ref"
;;
esac
#
# Get rid of any old bisect state
#
bisect_clean_state
mkdir "$GIT_DIR/refs/bisect"
#
# Check for one bad and then some good revisions.
#
has_double_dash=0
for arg; do
case "$arg" in --) has_double_dash=1; break ;; esac
done
orig_args=$(sq "$@")
bad_seen=0
while [ $# -gt 0 ]; do
arg="$1"
case "$arg" in
--)
shift
break
;;
*)
rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
test $has_double_dash -eq 1 &&
die "'$arg' does not appear to be a valid revision"
break
}
if [ $bad_seen -eq 0 ]; then
bad_seen=1
bisect_write_bad "$rev"
else
bisect_write_good "$rev"
fi
shift
;;
esac
done
sq "$@" >"$GIT_DIR/BISECT_NAMES"
{
printf "git-bisect start"
echo "$orig_args"
} >>"$GIT_DIR/BISECT_LOG"
bisect_auto_next
}
bisect_bad() {
bisect_autostart
case "$#" in
0)
rev=$(git-rev-parse --verify HEAD) ;;
1)
rev=$(git-rev-parse --verify "$1^{commit}") ;;
*)
usage ;;
esac || exit
bisect_write_bad "$rev"
echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
bisect_auto_next
}
bisect_write_bad() {
rev="$1"
echo "$rev" >"$GIT_DIR/refs/bisect/bad"
echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}
bisect_good() {
bisect_autostart
case "$#" in
0) revs=$(git-rev-parse --verify HEAD) || exit ;;
*) revs=$(git-rev-parse --revs-only --no-flags "$@") &&
test '' != "$revs" || die "Bad rev input: $@" ;;
esac
for rev in $revs
do
rev=$(git-rev-parse --verify "$rev^{commit}") || exit
bisect_write_good "$rev"
echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
done
bisect_auto_next
}
bisect_write_good() {
rev="$1"
echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}
bisect_next_check() {
next_ok=no
test -f "$GIT_DIR/refs/bisect/bad" &&
case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
refs/bisect/good-\*) ;;
*) next_ok=yes ;;
esac
case "$next_ok,$1" in
no,) false ;;
no,fail)
THEN=''
test -d "$GIT_DIR/refs/bisect" || {
echo >&2 'You need to start by "git bisect start".'
THEN='then '
}
echo >&2 'You '$THEN'need to give me at least one good' \
'and one bad revisions.'
echo >&2 '(You can use "git bisect bad" and' \
'"git bisect good" for that.)'
exit 1 ;;
*)
true ;;
esac
}
bisect_auto_next() {
bisect_next_check && bisect_next || :
}
bisect_next() {
case "$#" in 0) ;; *) usage ;; esac
bisect_autostart
bisect_next_check fail
bad=$(git-rev-parse --verify refs/bisect/bad) &&
good=$(git-rev-parse --sq --revs-only --not \
$(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit
if [ -z "$rev" ]; then
echo "$bad was both good and bad"
exit 1
fi
if [ "$rev" = "$bad" ]; then
echo "$rev is first bad commit"
git-diff-tree --pretty $rev
exit 0
fi
nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit
echo "Bisecting: $nr revisions left to test after this"
echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
git checkout -q new-bisect || exit
mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
git-show-branch "$rev"
}
bisect_visualize() {
bisect_next_check fail
not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
}
bisect_reset() {
case "$#" in
0) if [ -s "$GIT_DIR/head-name" ]; then
branch=`cat "$GIT_DIR/head-name"`
else
branch=master
fi ;;
1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
echo >&2 "$1 does not seem to be a valid branch"
exit 1
}
branch="$1" ;;
*)
usage ;;
esac
if git checkout "$branch"; then
rm -f "$GIT_DIR/head-name"
bisect_clean_state
fi
}
bisect_clean_state() {
rm -fr "$GIT_DIR/refs/bisect"
rm -f "$GIT_DIR/refs/heads/bisect"
rm -f "$GIT_DIR/BISECT_LOG"
rm -f "$GIT_DIR/BISECT_NAMES"
rm -f "$GIT_DIR/BISECT_RUN"
}
bisect_replay () {
test -r "$1" || {
echo >&2 "cannot read $1 for replaying"
exit 1
}
bisect_reset
while read bisect command rev
do
test "$bisect" = "git-bisect" || continue
case "$command" in
start)
cmd="bisect_start $rev"
eval "$cmd"
;;
good)
echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
;;
bad)
echo "$rev" >"$GIT_DIR/refs/bisect/bad"
echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
;;
*)
echo >&2 "?? what are you talking about?"
exit 1 ;;
esac
done <"$1"
bisect_auto_next
}
bisect_run () {
bisect_next_check fail
while true
do
echo "running $@"
"$@"
res=$?
# Check for really bad run error.
if [ $res -lt 0 -o $res -ge 128 ]; then
echo >&2 "bisect run failed:"
echo >&2 "exit code $res from '$@' is < 0 or >= 128"
exit $res
fi
# Use "bisect_good" or "bisect_bad"
# depending on run success or failure.
if [ $res -gt 0 ]; then
next_bisect='bisect_bad'
else
next_bisect='bisect_good'
fi
# We have to use a subshell because bisect_good or
# bisect_bad functions can exit.
( $next_bisect > "$GIT_DIR/BISECT_RUN" )
res=$?
cat "$GIT_DIR/BISECT_RUN"
if [ $res -ne 0 ]; then
echo >&2 "bisect run failed:"
echo >&2 "$next_bisect exited with error code $res"
exit $res
fi
if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
echo "bisect run success"
exit 0;
fi
done
}
case "$#" in
0)
usage ;;
*)
cmd="$1"
shift
case "$cmd" in
start)
bisect_start "$@" ;;
bad)
bisect_bad "$@" ;;
good)
bisect_good "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
bisect_next "$@" ;;
visualize)
bisect_visualize "$@" ;;
reset)
bisect_reset "$@" ;;
replay)
bisect_replay "$@" ;;
log)
cat "$GIT_DIR/BISECT_LOG" ;;
run)
bisect_run "$@" ;;
*)
usage ;;
esac
esac