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