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