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