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