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