git-bisect.shon commit t6041: do not compress backup tar file (f5ee54a)
   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 <valid-branch>'.")"
 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 cg-seek'ed 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                hash_list=''
 241                for rev in "$@"
 242                do
 243                        sha=$(git rev-parse --verify "$rev^{commit}") ||
 244                                die "$(eval_gettext "Bad rev input: \$rev")"
 245                        hash_list="$hash_list $sha"
 246                done
 247                for rev in $hash_list
 248                do
 249                        bisect_write "$state" "$rev"
 250                done
 251                check_expected_revs $hash_list ;;
 252        *,bad)
 253                die "$(gettext "'git bisect bad' can take only one argument.")" ;;
 254        *)
 255                usage ;;
 256        esac
 257        bisect_auto_next
 258}
 259
 260bisect_next_check() {
 261        missing_good= missing_bad=
 262        git show-ref -q --verify refs/bisect/bad || missing_bad=t
 263        test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
 264
 265        case "$missing_good,$missing_bad,$1" in
 266        ,,*)
 267                : have both good and bad - ok
 268                ;;
 269        *,)
 270                # do not have both but not asked to fail - just report.
 271                false
 272                ;;
 273        t,,good)
 274                # have bad but not good.  we could bisect although
 275                # this is less optimum.
 276                gettextln "Warning: bisecting only with a bad commit." >&2
 277                if test -t 0
 278                then
 279                        # TRANSLATORS: Make sure to include [Y] and [n] in your
 280                        # translation. The program will only accept English input
 281                        # at this point.
 282                        gettext "Are you sure [Y/n]? " >&2
 283                        read yesno
 284                        case "$yesno" in [Nn]*) exit 1 ;; esac
 285                fi
 286                : bisect without good...
 287                ;;
 288        *)
 289
 290                if test -s "$GIT_DIR/BISECT_START"
 291                then
 292                        gettextln "You need to give me at least one good and one bad revision.
 293(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
 294                else
 295                        gettextln "You need to start by \"git bisect start\".
 296You then need to give me at least one good and one bad revision.
 297(You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
 298                fi
 299                exit 1 ;;
 300        esac
 301}
 302
 303bisect_auto_next() {
 304        bisect_next_check && bisect_next || :
 305}
 306
 307bisect_next() {
 308        case "$#" in 0) ;; *) usage ;; esac
 309        bisect_autostart
 310        bisect_next_check good
 311
 312        # Perform all bisection computation, display and checkout
 313        git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
 314        res=$?
 315
 316        # Check if we should exit because bisection is finished
 317        if test $res -eq 10
 318        then
 319                bad_rev=$(git show-ref --hash --verify refs/bisect/bad)
 320                bad_commit=$(git show-branch $bad_rev)
 321                echo "# first bad commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
 322                exit 0
 323        elif test $res -eq 2
 324        then
 325                echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
 326                good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*")
 327                for skipped in $(git rev-list refs/bisect/bad --not $good_revs)
 328                do
 329                        skipped_commit=$(git show-branch $skipped)
 330                        echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
 331                done
 332                exit $res
 333        fi
 334
 335        # Check for an error in the bisection process
 336        test $res -ne 0 && exit $res
 337
 338        return 0
 339}
 340
 341bisect_visualize() {
 342        bisect_next_check fail
 343
 344        if test $# = 0
 345        then
 346                if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
 347                        type gitk >/dev/null 2>&1
 348                then
 349                        set gitk
 350                else
 351                        set git log
 352                fi
 353        else
 354                case "$1" in
 355                git*|tig) ;;
 356                -*)     set git log "$@" ;;
 357                *)      set git "$@" ;;
 358                esac
 359        fi
 360
 361        eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
 362}
 363
 364bisect_reset() {
 365        test -s "$GIT_DIR/BISECT_START" || {
 366                gettextln "We are not bisecting."
 367                return
 368        }
 369        case "$#" in
 370        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
 371        1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
 372                        invalid="$1"
 373                        die "$(eval_gettext "'\$invalid' is not a valid commit")"
 374                }
 375                branch="$1" ;;
 376        *)
 377                usage ;;
 378        esac
 379
 380        if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
 381        then
 382                die "$(eval_gettext "Could not check out original HEAD '\$branch'.
 383Try 'git bisect reset <commit>'.")"
 384        fi
 385        bisect_clean_state
 386}
 387
 388bisect_clean_state() {
 389        # There may be some refs packed during bisection.
 390        git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
 391        while read ref hash
 392        do
 393                git update-ref -d $ref $hash || exit
 394        done
 395        rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
 396        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
 397        rm -f "$GIT_DIR/BISECT_LOG" &&
 398        rm -f "$GIT_DIR/BISECT_NAMES" &&
 399        rm -f "$GIT_DIR/BISECT_RUN" &&
 400        # Cleanup head-name if it got left by an old version of git-bisect
 401        rm -f "$GIT_DIR/head-name" &&
 402        git update-ref -d --no-deref BISECT_HEAD &&
 403        # clean up BISECT_START last
 404        rm -f "$GIT_DIR/BISECT_START"
 405}
 406
 407bisect_replay () {
 408        file="$1"
 409        test "$#" -eq 1 || die "$(gettext "No logfile given")"
 410        test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
 411        bisect_reset
 412        while read git bisect command rev
 413        do
 414                test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
 415                if test "$git" = "git-bisect"
 416                then
 417                        rev="$command"
 418                        command="$bisect"
 419                fi
 420                case "$command" in
 421                start)
 422                        cmd="bisect_start $rev"
 423                        eval "$cmd" ;;
 424                good|bad|skip)
 425                        bisect_write "$command" "$rev" ;;
 426                *)
 427                        die "$(gettext "?? what are you talking about?")" ;;
 428                esac
 429        done <"$file"
 430        bisect_auto_next
 431}
 432
 433bisect_run () {
 434        bisect_next_check fail
 435
 436        while true
 437        do
 438                command="$@"
 439                eval_gettextln "running \$command"
 440                "$@"
 441                res=$?
 442
 443                # Check for really bad run error.
 444                if [ $res -lt 0 -o $res -ge 128 ]
 445                then
 446                        eval_gettextln "bisect run failed:
 447exit code \$res from '\$command' is < 0 or >= 128" >&2
 448                        exit $res
 449                fi
 450
 451                # Find current state depending on run success or failure.
 452                # A special exit code of 125 means cannot test.
 453                if [ $res -eq 125 ]
 454                then
 455                        state='skip'
 456                elif [ $res -gt 0 ]
 457                then
 458                        state='bad'
 459                else
 460                        state='good'
 461                fi
 462
 463                # We have to use a subshell because "bisect_state" can exit.
 464                ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
 465                res=$?
 466
 467                cat "$GIT_DIR/BISECT_RUN"
 468
 469                if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
 470                        >/dev/null
 471                then
 472                        gettextln "bisect run cannot continue any more" >&2
 473                        exit $res
 474                fi
 475
 476                if [ $res -ne 0 ]
 477                then
 478                        eval_gettextln "bisect run failed:
 479'bisect_state \$state' exited with error code \$res" >&2
 480                        exit $res
 481                fi
 482
 483                if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" >/dev/null
 484                then
 485                        gettextln "bisect run success"
 486                        exit 0;
 487                fi
 488
 489        done
 490}
 491
 492bisect_log () {
 493        test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
 494        cat "$GIT_DIR/BISECT_LOG"
 495}
 496
 497case "$#" in
 4980)
 499        usage ;;
 500*)
 501        cmd="$1"
 502        shift
 503        case "$cmd" in
 504        help)
 505                git bisect -h ;;
 506        start)
 507                bisect_start "$@" ;;
 508        bad|good)
 509                bisect_state "$cmd" "$@" ;;
 510        skip)
 511                bisect_skip "$@" ;;
 512        next)
 513                # Not sure we want "next" at the UI level anymore.
 514                bisect_next "$@" ;;
 515        visualize|view)
 516                bisect_visualize "$@" ;;
 517        reset)
 518                bisect_reset "$@" ;;
 519        replay)
 520                bisect_replay "$@" ;;
 521        log)
 522                bisect_log ;;
 523        run)
 524                bisect_run "$@" ;;
 525        *)
 526                usage ;;
 527        esac
 528esac