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