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