#!/bin/sh setvar USAGE = ''[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'' setvar LONG_USAGE = ''git bisect help print this long help message. git bisect start [--term-{old,good}= --term-{new,bad}=] [--no-checkout] [ [...]] [--] [...] reset bisect state and start bisection. git bisect (bad|new) [] mark a known-bad revision/ a revision after change in a given property. git bisect (good|old) [...] mark ... known-good revisions/ revisions before change in a given property. git bisect terms [--term-good | --term-bad] show the terms used for old and new commits (default: bad, good) git bisect skip [(|)...] mark ... untestable revisions. git bisect next find next bisection to test and check it out. git bisect reset [] finish bisection search and go back to commit. git bisect visualize show bisect status in gitk. git bisect replay replay bisection log. git bisect log show bisect log. git bisect run ... use ... to automatically bisect. Please use "git help bisect" to get the full man page.'' setvar OPTIONS_SPEC = '' source git-sh-setup setvar _x40 = ''[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'' setvar _x40 = ""$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"" setvar TERM_BAD = 'bad' setvar TERM_GOOD = 'good' proc bisect_head { if test -f "$GIT_DIR/BISECT_HEAD" { echo BISECT_HEAD } else { echo HEAD } } proc bisect_autostart { test -s "$GIT_DIR/BISECT_START" || do { gettextln "You need to start by \"git bisect start\"" >&2 if test -t 0 { # TRANSLATORS: Make sure to include [Y] and [n] in your # translation. The program will only accept English input # at this point. gettext "Do you want me to do it for you [Y/n]? " >&2 read yesno match $yesno { with [Nn]* exit } bisect_start } else { exit 1 } } } proc bisect_start { # # Check for one bad and then some good revisions. # setvar has_double_dash = '0'for arg in @ARGV { match $arg { with -- setvar has_double_dash = '1'; break } } setvar orig_args = $(git rev-parse --sq-quote "$@") setvar bad_seen = '0' setvar eval = '''' setvar must_write_terms = '0' setvar revs = '''' if test "z$(git rev-parse --is-bare-repository)" != zfalse { setvar mode = '--no-checkout' } else { setvar mode = '''' } while test $Argc -gt 0 { setvar arg = "$1" match $arg { with -- shift break with --no-checkout setvar mode = '--no-checkout' shift with --term-good|--term-old shift setvar must_write_terms = '1' setvar TERM_GOOD = "$1" shift with --term-good=*|--term-old=* setvar must_write_terms = '1' setvar TERM_GOOD = ${1#*=} shift with --term-bad|--term-new shift setvar must_write_terms = '1' setvar TERM_BAD = "$1" shift with --term-bad=*|--term-new=* setvar must_write_terms = '1' setvar TERM_BAD = ${1#*=} shift with --* die $(eval_gettext "unrecognised option: '\$arg'") with * setvar rev = $(git rev-parse -q --verify "$arg^{commit}") || do { test $has_double_dash -eq 1 && die $(eval_gettext "'\$arg' does not appear to be a valid revision") break } setvar revs = ""$revs $rev"" shift } } for rev in [$revs] { # The user ran "git bisect start # ", hence did not explicitly specify # the terms, but we are already starting to # set references named with the default terms, # and won't be able to change afterwards. setvar must_write_terms = '1' match $bad_seen { with 0 setvar state = "$TERM_BAD" ; setvar bad_seen = '1' with * setvar state = "$TERM_GOOD" } setvar eval = ""$eval bisect_write '$state' '$rev' 'nolog' &&"" } # # Verify HEAD. # setvar head = $(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) || setvar head = $(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) || die $(gettext "Bad HEAD - I need a HEAD") # # Check if we are bisecting. # setvar start_head = '''' if test -s "$GIT_DIR/BISECT_START" { # Reset to the rev from where we started. setvar start_head = $(cat "$GIT_DIR/BISECT_START") if test "z$mode" != "z--no-checkout" { git checkout $start_head -- || die $(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset '.") } } else { # Get rev from where we start. match $head { with refs/heads/*|$_x40 # This error message should only be triggered by # cogito usage, and cogito users should understand # it relates to cg-seek. test -s "$GIT_DIR/head-name" && die $(gettext "won't bisect on cg-seek'ed tree") setvar start_head = "${head#refs/heads/}" with * die $(gettext "Bad HEAD - strange symbolic ref") } } # # Get rid of any old bisect state. # bisect_clean_state || exit # # Change state. # In case of mistaken revs or checkout error, or signals received, # "bisect_auto_next" below may exit or misbehave. # We have to trap this to be able to clean up using # "bisect_clean_state". # trap 'bisect_clean_state' 0 trap 'exit 255' 1 2 3 15 # # Write new start state. # echo $start_head >"$GIT_DIR/BISECT_START" && do { test "z$mode" != "z--no-checkout" || git update-ref --no-deref BISECT_HEAD $start_head } && git rev-parse --sq-quote @ARGV >"$GIT_DIR/BISECT_NAMES" && eval "$eval true" && if test $must_write_terms -eq 1 { write_terms $TERM_BAD $TERM_GOOD } && echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit # # Check if we can proceed to the next bisect state. # bisect_auto_next trap '-' 0 } proc bisect_write { setvar state = "$1" setvar rev = "$2" setvar nolog = "$3" match $state { with "$TERM_BAD" setvar tag = "$state" with "$TERM_GOOD"|skip setvar tag = ""$state"-"$rev"" with * die $(eval_gettext "Bad bisect_write argument: \$state") } git update-ref "refs/bisect/$tag" $rev || exit echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG" test -n $nolog || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } proc is_expected_rev { test -f "$GIT_DIR/BISECT_EXPECTED_REV" && test $1 = $(cat "$GIT_DIR/BISECT_EXPECTED_REV") } proc check_expected_revs { for _rev in [@ARGV] { if ! is_expected_rev $_rev { rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" rm -f "$GIT_DIR/BISECT_EXPECTED_REV" return } } } proc bisect_skip { setvar all = '''' for arg in [@ARGV] { match $arg { with *..* setvar revs = $(git rev-list "$arg") || die $(eval_gettext "Bad rev input: \$arg") with * setvar revs = $(git rev-parse --sq-quote "$arg") } setvar all = ""$all $revs"" } eval bisect_state 'skip' $all } proc bisect_state { bisect_autostart setvar state = "$1" check_and_set_terms $state match "$Argc,$state" { with 0,* die "Please call 'bisect_state' with at least one argument." with 1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip setvar bisected_head = $(bisect_head) setvar rev = $(git rev-parse --verify "$bisected_head") || die $(eval_gettext "Bad rev input: \$bisected_head") bisect_write $state $rev check_expected_revs $rev with 2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip shift setvar hash_list = '''' for rev in [@ARGV] { setvar sha = $(git rev-parse --verify "$rev^{commit}") || die $(eval_gettext "Bad rev input: \$rev") setvar hash_list = ""$hash_list $sha"" } for rev in [$hash_list] { bisect_write $state $rev } check_expected_revs $hash_list with *,"$TERM_BAD" die $(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.") with * usage } bisect_auto_next } proc bisect_next_check { setvar missing_good = '', missing_bad = '' git show-ref -q --verify refs/bisect/$TERM_BAD || setvar missing_bad = 't' test -n $(git for-each-ref "refs/bisect/$TERM_GOOD-*") || setvar missing_good = 't' match "$missing_good,$missing_bad,$1" { with ,,* : have both $TERM_GOOD and $TERM_BAD - ok with *, # do not have both but not asked to fail - just report. false with t,,"$TERM_GOOD" # have bad (or new) but not good (or old). we could bisect although # this is less optimum. eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2 if test -t 0 { # TRANSLATORS: Make sure to include [Y] and [n] in your # translation. The program will only accept English input # at this point. gettext "Are you sure [Y/n]? " >&2 read yesno match $yesno { with [Nn]* exit 1 } } : bisect without $TERM_GOOD... with * setvar bad_syn = $(bisect_voc bad) setvar good_syn = $(bisect_voc good) if test -s "$GIT_DIR/BISECT_START" { eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision. (You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 } else { eval_gettextln "You need to start by \"git bisect start\". You then need to give me at least one \$good_syn and one \$bad_syn revision. (You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2 } exit 1 } } proc bisect_auto_next { bisect_next_check && bisect_next || : } proc bisect_next { match "$Argc" { with 0 with * usage } bisect_autostart bisect_next_check $TERM_GOOD # Perform all bisection computation, display and checkout git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout) setvar res = "$Status" # Check if we should exit because bisection is finished if test $res -eq 10 { setvar bad_rev = $(git show-ref --hash --verify refs/bisect/$TERM_BAD) setvar bad_commit = $(git show-branch $bad_rev) echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG" exit 0 } elif test $res -eq 2 { echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG" setvar good_revs = $(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*") for skipped in [$(git rev-list refs/bisect/$TERM_BAD --not $good_revs)] { setvar skipped_commit = $(git show-branch $skipped) echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG" } exit $res } # Check for an error in the bisection process test $res -ne 0 && exit $res return 0 } proc bisect_visualize { bisect_next_check fail if test $Argc = 0 { if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" && type gitk >/dev/null 2>&1 { set gitk } else { set git log } } else { match $1 { with git*|tig with -* set git log @ARGV with * set git @ARGV } } eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES") } proc bisect_reset { test -s "$GIT_DIR/BISECT_START" || do { gettextln "We are not bisecting." return } match "$Argc" { with 0 setvar branch = $(cat "$GIT_DIR/BISECT_START") with 1 git rev-parse --quiet --verify "$1^{commit}" >/dev/null || do { setvar invalid = "$1" die $(eval_gettext "'\$invalid' is not a valid commit") } setvar branch = "$1" with * usage } if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout $branch -- { die $(eval_gettext "Could not check out original HEAD '\$branch'. Try 'git bisect reset '.") } bisect_clean_state } proc bisect_clean_state { # There may be some refs packed during bisection. git for-each-ref --format='%(refname) %(objectname)' refs/bisect/'*' | while read ref hash { git update-ref -d $ref $hash || exit } rm -f "$GIT_DIR/BISECT_EXPECTED_REV" && rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" && rm -f "$GIT_DIR/BISECT_LOG" && rm -f "$GIT_DIR/BISECT_NAMES" && rm -f "$GIT_DIR/BISECT_RUN" && rm -f "$GIT_DIR/BISECT_TERMS" && # Cleanup head-name if it got left by an old version of git-bisect rm -f "$GIT_DIR/head-name" && git update-ref -d --no-deref BISECT_HEAD && # clean up BISECT_START last rm -f "$GIT_DIR/BISECT_START" } proc bisect_replay { setvar file = "$1" test "$Argc" -eq 1 || die $(gettext "No logfile given") test -r $file || die $(eval_gettext "cannot read \$file for replaying") bisect_reset while read git bisect command rev { test "$git $bisect" = "git bisect" || test $git = "git-bisect" || continue if test $git = "git-bisect" { setvar rev = "$command" setvar command = "$bisect" } get_terms check_and_set_terms $command match $command { with start setvar cmd = ""bisect_start $rev"" eval $cmd with "$TERM_GOOD"|"$TERM_BAD"|skip bisect_write $command $rev with terms bisect_terms $rev with * die $(gettext "?? what are you talking about?") } } <"$file" bisect_auto_next } proc bisect_run { bisect_next_check fail while true { setvar command = "@ARGV" eval_gettextln "running \$command" @ARGV setvar res = "$Status" # Check for really bad run error. if test $res -lt 0 -o $res -ge 128 { eval_gettextln "bisect run failed: exit code \$res from '\$command' is < 0 or >= 128" >&2 exit $res } # Find current state depending on run success or failure. # A special exit code of 125 means cannot test. if test $res -eq 125 { setvar state = ''skip'' } elif test $res -gt 0 { setvar state = "$TERM_BAD" } else { setvar state = "$TERM_GOOD" } # We have to use a subshell because "bisect_state" can exit. shell { bisect_state $state >"$GIT_DIR/BISECT_RUN" } setvar res = "$Status" cat "$GIT_DIR/BISECT_RUN" if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \ >/dev/null { gettextln "bisect run cannot continue any more" >&2 exit $res } if test $res -ne 0 { eval_gettextln "bisect run failed: 'bisect_state \$state' exited with error code \$res" >&2 exit $res } if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null { gettextln "bisect run success" exit 0; } } } proc bisect_log { test -s "$GIT_DIR/BISECT_LOG" || die $(gettext "We are not bisecting.") cat "$GIT_DIR/BISECT_LOG" } proc get_terms { if test -s "$GIT_DIR/BISECT_TERMS" { do { read TERM_BAD read TERM_GOOD } <"$GIT_DIR/BISECT_TERMS" } } proc write_terms { setvar TERM_BAD = "$1" setvar TERM_GOOD = "$2" if test $TERM_BAD = $TERM_GOOD { die $(gettext "please use two different terms") } check_term_format $TERM_BAD bad check_term_format $TERM_GOOD good printf '%s\n%s\n' $TERM_BAD $TERM_GOOD >"$GIT_DIR/BISECT_TERMS" } proc check_term_format { setvar term = "$1" git check-ref-format refs/bisect/"$term" || die $(eval_gettext "'\$term' is not a valid term") match $term { with help|start|terms|skip|next|reset|visualize|replay|log|run die $(eval_gettext "can't use the builtin command '\$term' as a term") with bad|new if test $2 != bad { # In theory, nothing prevents swapping # completely good and bad, but this situation # could be confusing and hasn't been tested # enough. Forbid it for now. die $(eval_gettext "can't change the meaning of term '\$term'") } with good|old if test $2 != good { die $(eval_gettext "can't change the meaning of term '\$term'") } } } proc check_and_set_terms { setvar cmd = "$1" match $cmd { with skip|start|terms with * if test -s "$GIT_DIR/BISECT_TERMS" && test $cmd != $TERM_BAD && test $cmd != $TERM_GOOD { die $(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.") } match $cmd { with bad|good if ! test -s "$GIT_DIR/BISECT_TERMS" { write_terms bad good } with new|old if ! test -s "$GIT_DIR/BISECT_TERMS" { write_terms new old } } } } proc bisect_voc { match $1 { with bad echo "bad|new" with good echo "good|old" } } proc bisect_terms { get_terms if ! test -s "$GIT_DIR/BISECT_TERMS" { die $(gettext "no terms defined") } match "$Argc" { with 0 gettextln "Your current terms are $TERM_GOOD for the old state and $TERM_BAD for the new state." with 1 setvar arg = "$1" match $arg { with --term-good|--term-old printf '%s\n' $TERM_GOOD with --term-bad|--term-new printf '%s\n' $TERM_BAD with * die $(eval_gettext "invalid argument \$arg for 'git bisect terms'. Supported options are: --term-good|--term-old and --term-bad|--term-new.") } with * usage } } match "$Argc" { with 0 usage with * setvar cmd = "$1" get_terms shift match $cmd { with help git bisect -h with start bisect_start @ARGV with bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD" bisect_state $cmd @ARGV with skip bisect_skip @ARGV with next # Not sure we want "next" at the UI level anymore. bisect_next @ARGV with visualize|view bisect_visualize @ARGV with reset bisect_reset @ARGV with replay bisect_replay @ARGV with log bisect_log with run bisect_run @ARGV with terms bisect_terms @ARGV with * usage } }