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