git-bisect.shon commit bisect: add 'git bisect terms' to view the current terms (21b55e3)
   1#!/bin/sh
   2
   3USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
   4LONG_USAGE='git bisect help
   5        print this long help message.
   6git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
   7        reset bisect state and start bisection.
   8git bisect (bad|new) [<rev>]
   9        mark <rev> a known-bad revision/
  10                a revision after change in a given property.
  11git bisect (good|old) [<rev>...]
  12        mark <rev>... known-good revisions/
  13                revisions before change in a given property.
  14git bisect terms [--term-good | --term-bad]
  15        show the terms used for old and new commits (default: bad, good)
  16git bisect skip [(<rev>|<range>)...]
  17        mark <rev>... untestable revisions.
  18git bisect next
  19        find next bisection to test and check it out.
  20git bisect reset [<commit>]
  21        finish bisection search and go back to commit.
  22git bisect visualize
  23        show bisect status in gitk.
  24git bisect replay <logfile>
  25        replay bisection log.
  26git bisect log
  27        show bisect log.
  28git bisect run <cmd>...
  29        use <cmd>... to automatically bisect.
  30
  31Please use "git help bisect" to get the full man page.'
  32
  33OPTIONS_SPEC=
  34. git-sh-setup
  35. git-sh-i18n
  36
  37_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
  38_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
  39TERM_BAD=bad
  40TERM_GOOD=good
  41
  42bisect_head()
  43{
  44        if test -f "$GIT_DIR/BISECT_HEAD"
  45        then
  46                echo BISECT_HEAD
  47        else
  48                echo HEAD
  49        fi
  50}
  51
  52bisect_autostart() {
  53        test -s "$GIT_DIR/BISECT_START" || {
  54                gettextln "You need to start by \"git bisect start\"" >&2
  55                if test -t 0
  56                then
  57                        # TRANSLATORS: Make sure to include [Y] and [n] in your
  58                        # translation. The program will only accept English input
  59                        # at this point.
  60                        gettext "Do you want me to do it for you [Y/n]? " >&2
  61                        read yesno
  62                        case "$yesno" in
  63                        [Nn]*)
  64                                exit ;;
  65                        esac
  66                        bisect_start
  67                else
  68                        exit 1
  69                fi
  70        }
  71}
  72
  73bisect_start() {
  74        #
  75        # Check for one bad and then some good revisions.
  76        #
  77        has_double_dash=0
  78        for arg; do
  79                case "$arg" in --) has_double_dash=1; break ;; esac
  80        done
  81        orig_args=$(git rev-parse --sq-quote "$@")
  82        bad_seen=0
  83        eval=''
  84        must_write_terms=0
  85        revs=''
  86        if test "z$(git rev-parse --is-bare-repository)" != zfalse
  87        then
  88                mode=--no-checkout
  89        else
  90                mode=''
  91        fi
  92        while [ $# -gt 0 ]; do
  93                arg="$1"
  94                case "$arg" in
  95                --)
  96                        shift
  97                        break
  98                ;;
  99                --no-checkout)
 100                        mode=--no-checkout
 101                        shift ;;
 102                --*)
 103                        die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
 104                *)
 105                        rev=$(git rev-parse -q --verify "$arg^{commit}") || {
 106                                test $has_double_dash -eq 1 &&
 107                                die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
 108                                break
 109                        }
 110                        revs="$revs $rev"
 111                        shift
 112                        ;;
 113                esac
 114        done
 115
 116        for rev in $revs
 117        do
 118                # The user ran "git bisect start <sha1>
 119                # <sha1>", hence did not explicitly specify
 120                # the terms, but we are already starting to
 121                # set references named with the default terms,
 122                # and won't be able to change afterwards.
 123                must_write_terms=1
 124
 125                case $bad_seen in
 126                0) state=$TERM_BAD ; bad_seen=1 ;;
 127                *) state=$TERM_GOOD ;;
 128                esac
 129                eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
 130        done
 131        #
 132        # Verify HEAD.
 133        #
 134        head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
 135        head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
 136        die "$(gettext "Bad HEAD - I need a HEAD")"
 137
 138        #
 139        # Check if we are bisecting.
 140        #
 141        start_head=''
 142        if test -s "$GIT_DIR/BISECT_START"
 143        then
 144                # Reset to the rev from where we started.
 145                start_head=$(cat "$GIT_DIR/BISECT_START")
 146                if test "z$mode" != "z--no-checkout"
 147                then
 148                        git checkout "$start_head" -- ||
 149                        die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")"
 150                fi
 151        else
 152                # Get rev from where we start.
 153                case "$head" in
 154                refs/heads/*|$_x40)
 155                        # This error message should only be triggered by
 156                        # cogito usage, and cogito users should understand
 157                        # it relates to cg-seek.
 158                        [ -s "$GIT_DIR/head-name" ] &&
 159                                die "$(gettext "won't bisect on cg-seek'ed tree")"
 160                        start_head="${head#refs/heads/}"
 161                        ;;
 162                *)
 163                        die "$(gettext "Bad HEAD - strange symbolic ref")"
 164                        ;;
 165                esac
 166        fi
 167
 168        #
 169        # Get rid of any old bisect state.
 170        #
 171        bisect_clean_state || exit
 172
 173        #
 174        # Change state.
 175        # In case of mistaken revs or checkout error, or signals received,
 176        # "bisect_auto_next" below may exit or misbehave.
 177        # We have to trap this to be able to clean up using
 178        # "bisect_clean_state".
 179        #
 180        trap 'bisect_clean_state' 0
 181        trap 'exit 255' 1 2 3 15
 182
 183        #
 184        # Write new start state.
 185        #
 186        echo "$start_head" >"$GIT_DIR/BISECT_START" && {
 187                test "z$mode" != "z--no-checkout" ||
 188                git update-ref --no-deref BISECT_HEAD "$start_head"
 189        } &&
 190        git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
 191        eval "$eval true" &&
 192        if test $must_write_terms -eq 1
 193        then
 194                write_terms "$TERM_BAD" "$TERM_GOOD"
 195        fi &&
 196        echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
 197        #
 198        # Check if we can proceed to the next bisect state.
 199        #
 200        bisect_auto_next
 201
 202        trap '-' 0
 203}
 204
 205bisect_write() {
 206        state="$1"
 207        rev="$2"
 208        nolog="$3"
 209        case "$state" in
 210                "$TERM_BAD")
 211                        tag="$state" ;;
 212                "$TERM_GOOD"|skip)
 213                        tag="$state"-"$rev" ;;
 214                *)
 215                        die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
 216        esac
 217        git update-ref "refs/bisect/$tag" "$rev" || exit
 218        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
 219        test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 220}
 221
 222is_expected_rev() {
 223        test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
 224        test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
 225}
 226
 227check_expected_revs() {
 228        for _rev in "$@"; do
 229                if ! is_expected_rev "$_rev"
 230                then
 231                        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
 232                        rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
 233                        return
 234                fi
 235        done
 236}
 237
 238bisect_skip() {
 239        all=''
 240        for arg in "$@"
 241        do
 242                case "$arg" in
 243                *..*)
 244                        revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
 245                *)
 246                        revs=$(git rev-parse --sq-quote "$arg") ;;
 247                esac
 248                all="$all $revs"
 249        done
 250        eval bisect_state 'skip' $all
 251}
 252
 253bisect_state() {
 254        bisect_autostart
 255        state=$1
 256        check_and_set_terms $state
 257        case "$#,$state" in
 258        0,*)
 259                die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
 260        1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
 261                rev=$(git rev-parse --verify $(bisect_head)) ||
 262                        die "$(gettext "Bad rev input: $(bisect_head)")"
 263                bisect_write "$state" "$rev"
 264                check_expected_revs "$rev" ;;
 265        2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
 266                shift
 267                hash_list=''
 268                for rev in "$@"
 269                do
 270                        sha=$(git rev-parse --verify "$rev^{commit}") ||
 271                                die "$(eval_gettext "Bad rev input: \$rev")"
 272                        hash_list="$hash_list $sha"
 273                done
 274                for rev in $hash_list
 275                do
 276                        bisect_write "$state" "$rev"
 277                done
 278                check_expected_revs $hash_list ;;
 279        *,"$TERM_BAD")
 280                die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;;
 281        *)
 282                usage ;;
 283        esac
 284        bisect_auto_next
 285}
 286
 287bisect_next_check() {
 288        missing_good= missing_bad=
 289        git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t
 290        test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t
 291
 292        case "$missing_good,$missing_bad,$1" in
 293        ,,*)
 294                : have both $TERM_GOOD and $TERM_BAD - ok
 295                ;;
 296        *,)
 297                # do not have both but not asked to fail - just report.
 298                false
 299                ;;
 300        t,,"$TERM_GOOD")
 301                # have bad (or new) but not good (or old).  we could bisect although
 302                # this is less optimum.
 303                eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
 304                if test -t 0
 305                then
 306                        # TRANSLATORS: Make sure to include [Y] and [n] in your
 307                        # translation. The program will only accept English input
 308                        # at this point.
 309                        gettext "Are you sure [Y/n]? " >&2
 310                        read yesno
 311                        case "$yesno" in [Nn]*) exit 1 ;; esac
 312                fi
 313                : bisect without $TERM_GOOD...
 314                ;;
 315        *)
 316                bad_syn=$(bisect_voc bad)
 317                good_syn=$(bisect_voc good)
 318                if test -s "$GIT_DIR/BISECT_START"
 319                then
 320
 321                        eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision.
 322(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
 323                else
 324                        eval_gettextln "You need to start by \"git bisect start\".
 325You then need to give me at least one \$good_syn and one \$bad_syn revision.
 326(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
 327                fi
 328                exit 1 ;;
 329        esac
 330}
 331
 332bisect_auto_next() {
 333        bisect_next_check && bisect_next || :
 334}
 335
 336bisect_next() {
 337        case "$#" in 0) ;; *) usage ;; esac
 338        bisect_autostart
 339        bisect_next_check $TERM_GOOD
 340
 341        # Perform all bisection computation, display and checkout
 342        git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
 343        res=$?
 344
 345        # Check if we should exit because bisection is finished
 346        if test $res -eq 10
 347        then
 348                bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
 349                bad_commit=$(git show-branch $bad_rev)
 350                echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
 351                exit 0
 352        elif test $res -eq 2
 353        then
 354                echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
 355                good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
 356                for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
 357                do
 358                        skipped_commit=$(git show-branch $skipped)
 359                        echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
 360                done
 361                exit $res
 362        fi
 363
 364        # Check for an error in the bisection process
 365        test $res -ne 0 && exit $res
 366
 367        return 0
 368}
 369
 370bisect_visualize() {
 371        bisect_next_check fail
 372
 373        if test $# = 0
 374        then
 375                if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
 376                        type gitk >/dev/null 2>&1
 377                then
 378                        set gitk
 379                else
 380                        set git log
 381                fi
 382        else
 383                case "$1" in
 384                git*|tig) ;;
 385                -*)     set git log "$@" ;;
 386                *)      set git "$@" ;;
 387                esac
 388        fi
 389
 390        eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
 391}
 392
 393bisect_reset() {
 394        test -s "$GIT_DIR/BISECT_START" || {
 395                gettextln "We are not bisecting."
 396                return
 397        }
 398        case "$#" in
 399        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
 400        1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
 401                        invalid="$1"
 402                        die "$(eval_gettext "'\$invalid' is not a valid commit")"
 403                }
 404                branch="$1" ;;
 405        *)
 406                usage ;;
 407        esac
 408
 409        if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
 410        then
 411                die "$(eval_gettext "Could not check out original HEAD '\$branch'.
 412Try 'git bisect reset <commit>'.")"
 413        fi
 414        bisect_clean_state
 415}
 416
 417bisect_clean_state() {
 418        # There may be some refs packed during bisection.
 419        git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
 420        while read ref hash
 421        do
 422                git update-ref -d $ref $hash || exit
 423        done
 424        rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
 425        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
 426        rm -f "$GIT_DIR/BISECT_LOG" &&
 427        rm -f "$GIT_DIR/BISECT_NAMES" &&
 428        rm -f "$GIT_DIR/BISECT_RUN" &&
 429        rm -f "$GIT_DIR/BISECT_TERMS" &&
 430        # Cleanup head-name if it got left by an old version of git-bisect
 431        rm -f "$GIT_DIR/head-name" &&
 432        git update-ref -d --no-deref BISECT_HEAD &&
 433        # clean up BISECT_START last
 434        rm -f "$GIT_DIR/BISECT_START"
 435}
 436
 437bisect_replay () {
 438        file="$1"
 439        test "$#" -eq 1 || die "$(gettext "No logfile given")"
 440        test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
 441        bisect_reset
 442        while read git bisect command rev
 443        do
 444                test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
 445                if test "$git" = "git-bisect"
 446                then
 447                        rev="$command"
 448                        command="$bisect"
 449                fi
 450                get_terms
 451                check_and_set_terms "$command"
 452                case "$command" in
 453                start)
 454                        cmd="bisect_start $rev"
 455                        eval "$cmd" ;;
 456                "$TERM_GOOD"|"$TERM_BAD"|skip)
 457                        bisect_write "$command" "$rev" ;;
 458                terms)
 459                        bisect_terms $rev ;;
 460                *)
 461                        die "$(gettext "?? what are you talking about?")" ;;
 462                esac
 463        done <"$file"
 464        bisect_auto_next
 465}
 466
 467bisect_run () {
 468        bisect_next_check fail
 469
 470        while true
 471        do
 472                command="$@"
 473                eval_gettextln "running \$command"
 474                "$@"
 475                res=$?
 476
 477                # Check for really bad run error.
 478                if [ $res -lt 0 -o $res -ge 128 ]
 479                then
 480                        eval_gettextln "bisect run failed:
 481exit code \$res from '\$command' is < 0 or >= 128" >&2
 482                        exit $res
 483                fi
 484
 485                # Find current state depending on run success or failure.
 486                # A special exit code of 125 means cannot test.
 487                if [ $res -eq 125 ]
 488                then
 489                        state='skip'
 490                elif [ $res -gt 0 ]
 491                then
 492                        state="$TERM_BAD"
 493                else
 494                        state="$TERM_GOOD"
 495                fi
 496
 497                # We have to use a subshell because "bisect_state" can exit.
 498                ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
 499                res=$?
 500
 501                cat "$GIT_DIR/BISECT_RUN"
 502
 503                if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \
 504                        >/dev/null
 505                then
 506                        gettextln "bisect run cannot continue any more" >&2
 507                        exit $res
 508                fi
 509
 510                if [ $res -ne 0 ]
 511                then
 512                        eval_gettextln "bisect run failed:
 513'bisect_state \$state' exited with error code \$res" >&2
 514                        exit $res
 515                fi
 516
 517                if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null
 518                then
 519                        gettextln "bisect run success"
 520                        exit 0;
 521                fi
 522
 523        done
 524}
 525
 526bisect_log () {
 527        test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
 528        cat "$GIT_DIR/BISECT_LOG"
 529}
 530
 531get_terms () {
 532        if test -s "$GIT_DIR/BISECT_TERMS"
 533        then
 534                {
 535                read TERM_BAD
 536                read TERM_GOOD
 537                } <"$GIT_DIR/BISECT_TERMS"
 538        fi
 539}
 540
 541write_terms () {
 542        TERM_BAD=$1
 543        TERM_GOOD=$2
 544        if test "$TERM_BAD" = "$TERM_GOOD"
 545        then
 546                die "$(gettext "please use two different terms")"
 547        fi
 548        check_term_format "$TERM_BAD" bad
 549        check_term_format "$TERM_GOOD" good
 550        printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
 551}
 552
 553check_term_format () {
 554        term=$1
 555        git check-ref-format refs/bisect/"$term" ||
 556        die "$(eval_gettext "'\$term' is not a valid term")"
 557        case "$term" in
 558        help|start|terms|skip|next|reset|visualize|replay|log|run)
 559                die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
 560                ;;
 561        bad|new)
 562                if test "$2" != bad
 563                then
 564                        # In theory, nothing prevents swapping
 565                        # completely good and bad, but this situation
 566                        # could be confusing and hasn't been tested
 567                        # enough. Forbid it for now.
 568                        die "$(eval_gettext "can't change the meaning of term '\$term'")"
 569                fi
 570                ;;
 571        good|old)
 572                if test "$2" != good
 573                then
 574                        die "$(eval_gettext "can't change the meaning of term '\$term'")"
 575                fi
 576                ;;
 577        esac
 578}
 579
 580check_and_set_terms () {
 581        cmd="$1"
 582        case "$cmd" in
 583        skip|start|terms) ;;
 584        *)
 585                if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD"
 586                then
 587                        die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")"
 588                fi
 589                case "$cmd" in
 590                bad|good)
 591                        if ! test -s "$GIT_DIR/BISECT_TERMS"
 592                        then
 593                                write_terms bad good
 594                        fi
 595                        ;;
 596                new|old)
 597                        if ! test -s "$GIT_DIR/BISECT_TERMS"
 598                        then
 599                                write_terms new old
 600                        fi
 601                        ;;
 602                esac ;;
 603        esac
 604}
 605
 606bisect_voc () {
 607        case "$1" in
 608        bad) echo "bad|new" ;;
 609        good) echo "good|old" ;;
 610        esac
 611}
 612
 613bisect_terms () {
 614        get_terms
 615        if ! test -s "$GIT_DIR/BISECT_TERMS"
 616        then
 617                die "$(gettext "no terms defined")"
 618        fi
 619        case "$#" in
 620        0)
 621                gettextln "Your current terms are $TERM_GOOD for the old state
 622and $TERM_BAD for the new state."
 623                ;;
 624        1)
 625                arg=$1
 626                case "$arg" in
 627                        --term-good|--term-old)
 628                                printf '%s\n' "$TERM_GOOD"
 629                                ;;
 630                        --term-bad|--term-new)
 631                                printf '%s\n' "$TERM_BAD"
 632                                ;;
 633                        *)
 634                                die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
 635Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
 636                                ;;
 637                esac
 638                ;;
 639        *)
 640                usage ;;
 641        esac
 642}
 643
 644case "$#" in
 6450)
 646        usage ;;
 647*)
 648        cmd="$1"
 649        get_terms
 650        shift
 651        case "$cmd" in
 652        help)
 653                git bisect -h ;;
 654        start)
 655                bisect_start "$@" ;;
 656        bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
 657                bisect_state "$cmd" "$@" ;;
 658        skip)
 659                bisect_skip "$@" ;;
 660        next)
 661                # Not sure we want "next" at the UI level anymore.
 662                bisect_next "$@" ;;
 663        visualize|view)
 664                bisect_visualize "$@" ;;
 665        reset)
 666                bisect_reset "$@" ;;
 667        replay)
 668                bisect_replay "$@" ;;
 669        log)
 670                bisect_log ;;
 671        run)
 672                bisect_run "$@" ;;
 673        terms)
 674                bisect_terms "$@" ;;
 675        *)
 676                usage ;;
 677        esac
 678esac