diff options
Diffstat (limited to 'git-bisect.sh')
-rwxr-xr-x | git-bisect.sh | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/git-bisect.sh b/git-bisect.sh new file mode 100755 index 0000000000..388887a556 --- /dev/null +++ b/git-bisect.sh @@ -0,0 +1,382 @@ +#!/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" + echo "git-bisect start$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() { + missing_good= missing_bad= + git show-ref -q --verify refs/bisect/bad || missing_bad=t + test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t + + case "$missing_good,$missing_bad,$1" in + ,,*) + : have both good and bad - ok + ;; + *,) + # do not have both but not asked to fail - just report. + false + ;; + t,,good) + # have bad but not good. we could bisect although + # this is less optimum. + echo >&2 'Warning: bisecting only with a bad commit.' + if test -t 0 + then + printf >&2 'Are you sure [Y/n]? ' + case "$(read yesno)" in [Nn]*) exit 1 ;; esac + fi + : bisect without good... + ;; + *) + 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 ;; + esac +} + +bisect_auto_next() { + bisect_next_check && bisect_next || : +} + +bisect_next() { + case "$#" in 0) ;; *) usage ;; esac + bisect_autostart + bisect_next_check good + + bad=$(git rev-parse --verify refs/bisect/bad) && + good=$(git for-each-ref --format='^%(objectname)' \ + "refs/bisect/good-*" | tr '[\012]' ' ') && + eval="git rev-list --bisect-vars $good $bad --" && + eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && + eval=$(eval "$eval") && + eval "$eval" || exit + + if [ -z "$bisect_rev" ]; then + echo "$bad was both good and bad" + exit 1 + fi + if [ "$bisect_rev" = "$bad" ]; then + echo "$bisect_rev is first bad commit" + git diff-tree --pretty $bisect_rev + exit 0 + fi + + echo "Bisecting: $bisect_nr revisions left to test after this" + echo "$bisect_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 "$bisect_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 |