git-bisect.shon commit strbuf: add strbuf_reencode helper (d4241f5)
   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 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                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        if test $res -eq 10
 315        then
 316                bad_rev=$(git show-ref --hash --verify refs/bisect/bad)
 317                bad_commit=$(git show-branch $bad_rev)
 318                echo "# first bad commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
 319                exit 0
 320        elif test $res -eq 2
 321        then
 322                echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
 323                good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*")
 324                for skipped in $(git rev-list refs/bisect/bad --not $good_revs)
 325                do
 326                        skipped_commit=$(git show-branch $skipped)
 327                        echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
 328                done
 329                exit $res
 330        fi
 331
 332        # Check for an error in the bisection process
 333        test $res -ne 0 && exit $res
 334
 335        return 0
 336}
 337
 338bisect_visualize() {
 339        bisect_next_check fail
 340
 341        if test $# = 0
 342        then
 343                if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
 344                        type gitk >/dev/null 2>&1
 345                then
 346                        set gitk
 347                else
 348                        set git log
 349                fi
 350        else
 351                case "$1" in
 352                git*|tig) ;;
 353                -*)     set git log "$@" ;;
 354                *)      set git "$@" ;;
 355                esac
 356        fi
 357
 358        eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
 359}
 360
 361bisect_reset() {
 362        test -s "$GIT_DIR/BISECT_START" || {
 363                gettextln "We are not bisecting."
 364                return
 365        }
 366        case "$#" in
 367        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
 368        1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
 369                        invalid="$1"
 370                        die "$(eval_gettext "'\$invalid' is not a valid commit")"
 371                }
 372                branch="$1" ;;
 373        *)
 374                usage ;;
 375        esac
 376
 377        if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
 378        then
 379                die "$(eval_gettext "Could not check out original HEAD '\$branch'.
 380Try 'git bisect reset <commit>'.")"
 381        fi
 382        bisect_clean_state
 383}
 384
 385bisect_clean_state() {
 386        # There may be some refs packed during bisection.
 387        git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
 388        while read ref hash
 389        do
 390                git update-ref -d $ref $hash || exit
 391        done
 392        rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
 393        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
 394        rm -f "$GIT_DIR/BISECT_LOG" &&
 395        rm -f "$GIT_DIR/BISECT_NAMES" &&
 396        rm -f "$GIT_DIR/BISECT_RUN" &&
 397        # Cleanup head-name if it got left by an old version of git-bisect
 398        rm -f "$GIT_DIR/head-name" &&
 399        git update-ref -d --no-deref BISECT_HEAD &&
 400        # clean up BISECT_START last
 401        rm -f "$GIT_DIR/BISECT_START"
 402}
 403
 404bisect_replay () {
 405        file="$1"
 406        test "$#" -eq 1 || die "$(gettext "No logfile given")"
 407        test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
 408        bisect_reset
 409        while read git bisect command rev
 410        do
 411                test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
 412                if test "$git" = "git-bisect"
 413                then
 414                        rev="$command"
 415                        command="$bisect"
 416                fi
 417                case "$command" in
 418                start)
 419                        cmd="bisect_start $rev"
 420                        eval "$cmd" ;;
 421                good|bad|skip)
 422                        bisect_write "$command" "$rev" ;;
 423                *)
 424                        die "$(gettext "?? what are you talking about?")" ;;
 425                esac
 426        done <"$file"
 427        bisect_auto_next
 428}
 429
 430bisect_run () {
 431        bisect_next_check fail
 432
 433        while true
 434        do
 435                command="$@"
 436                eval_gettextln "running \$command"
 437                "$@"
 438                res=$?
 439
 440                # Check for really bad run error.
 441                if [ $res -lt 0 -o $res -ge 128 ]
 442                then
 443                        eval_gettextln "bisect run failed:
 444exit code \$res from '\$command' is < 0 or >= 128" >&2
 445                        exit $res
 446                fi
 447
 448                # Find current state depending on run success or failure.
 449                # A special exit code of 125 means cannot test.
 450                if [ $res -eq 125 ]
 451                then
 452                        state='skip'
 453                elif [ $res -gt 0 ]
 454                then
 455                        state='bad'
 456                else
 457                        state='good'
 458                fi
 459
 460                # We have to use a subshell because "bisect_state" can exit.
 461                ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
 462                res=$?
 463
 464                cat "$GIT_DIR/BISECT_RUN"
 465
 466                if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
 467                        >/dev/null
 468                then
 469                        gettextln "bisect run cannot continue any more" >&2
 470                        exit $res
 471                fi
 472
 473                if [ $res -ne 0 ]
 474                then
 475                        eval_gettextln "bisect run failed:
 476'bisect_state \$state' exited with error code \$res" >&2
 477                        exit $res
 478                fi
 479
 480                if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" >/dev/null
 481                then
 482                        gettextln "bisect run success"
 483                        exit 0;
 484                fi
 485
 486        done
 487}
 488
 489bisect_log () {
 490        test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
 491        cat "$GIT_DIR/BISECT_LOG"
 492}
 493
 494case "$#" in
 4950)
 496        usage ;;
 497*)
 498        cmd="$1"
 499        shift
 500        case "$cmd" in
 501        help)
 502                git bisect -h ;;
 503        start)
 504                bisect_start "$@" ;;
 505        bad|good)
 506                bisect_state "$cmd" "$@" ;;
 507        skip)
 508                bisect_skip "$@" ;;
 509        next)
 510                # Not sure we want "next" at the UI level anymore.
 511                bisect_next "$@" ;;
 512        visualize|view)
 513                bisect_visualize "$@" ;;
 514        reset)
 515                bisect_reset "$@" ;;
 516        replay)
 517                bisect_replay "$@" ;;
 518        log)
 519                bisect_log ;;
 520        run)
 521                bisect_run "$@" ;;
 522        *)
 523                usage ;;
 524        esac
 525esac