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