git-bisect.shon commit traverse_trees(): handle D/F conflict case sanely (1ee2657)
   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                case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
 292                '')     set git log ;;
 293                set*)   set gitk ;;
 294                esac
 295        else
 296                case "$1" in
 297                git*|tig) ;;
 298                -*)     set git log "$@" ;;
 299                *)      set git "$@" ;;
 300                esac
 301        fi
 302
 303        eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
 304}
 305
 306bisect_reset() {
 307        test -s "$GIT_DIR/BISECT_START" || {
 308                echo "We are not bisecting."
 309                return
 310        }
 311        case "$#" in
 312        0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
 313        1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null ||
 314               die "'$1' is not a valid commit"
 315           branch="$1" ;;
 316        *)
 317            usage ;;
 318        esac
 319        git checkout "$branch" -- && bisect_clean_state
 320}
 321
 322bisect_clean_state() {
 323        # There may be some refs packed during bisection.
 324        git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
 325        while read ref hash
 326        do
 327                git update-ref -d $ref $hash || exit
 328        done
 329        rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
 330        rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
 331        rm -f "$GIT_DIR/BISECT_LOG" &&
 332        rm -f "$GIT_DIR/BISECT_NAMES" &&
 333        rm -f "$GIT_DIR/BISECT_RUN" &&
 334        # Cleanup head-name if it got left by an old version of git-bisect
 335        rm -f "$GIT_DIR/head-name" &&
 336
 337        rm -f "$GIT_DIR/BISECT_START"
 338}
 339
 340bisect_replay () {
 341        test -r "$1" || die "cannot read $1 for replaying"
 342        bisect_reset
 343        while read git bisect command rev
 344        do
 345                test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
 346                if test "$git" = "git-bisect"; then
 347                        rev="$command"
 348                        command="$bisect"
 349                fi
 350                case "$command" in
 351                start)
 352                        cmd="bisect_start $rev"
 353                        eval "$cmd" ;;
 354                good|bad|skip)
 355                        bisect_write "$command" "$rev" ;;
 356                *)
 357                        die "?? what are you talking about?" ;;
 358                esac
 359        done <"$1"
 360        bisect_auto_next
 361}
 362
 363bisect_run () {
 364    bisect_next_check fail
 365
 366    while true
 367    do
 368      echo "running $@"
 369      "$@"
 370      res=$?
 371
 372      # Check for really bad run error.
 373      if [ $res -lt 0 -o $res -ge 128 ]; then
 374          echo >&2 "bisect run failed:"
 375          echo >&2 "exit code $res from '$@' is < 0 or >= 128"
 376          exit $res
 377      fi
 378
 379      # Find current state depending on run success or failure.
 380      # A special exit code of 125 means cannot test.
 381      if [ $res -eq 125 ]; then
 382          state='skip'
 383      elif [ $res -gt 0 ]; then
 384          state='bad'
 385      else
 386          state='good'
 387      fi
 388
 389      # We have to use a subshell because "bisect_state" can exit.
 390      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
 391      res=$?
 392
 393      cat "$GIT_DIR/BISECT_RUN"
 394
 395      if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
 396                > /dev/null; then
 397          echo >&2 "bisect run cannot continue any more"
 398          exit $res
 399      fi
 400
 401      if [ $res -ne 0 ]; then
 402          echo >&2 "bisect run failed:"
 403          echo >&2 "'bisect_state $state' exited with error code $res"
 404          exit $res
 405      fi
 406
 407      if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
 408          echo "bisect run success"
 409          exit 0;
 410      fi
 411
 412    done
 413}
 414
 415
 416case "$#" in
 4170)
 418    usage ;;
 419*)
 420    cmd="$1"
 421    shift
 422    case "$cmd" in
 423    help)
 424        git bisect -h ;;
 425    start)
 426        bisect_start "$@" ;;
 427    bad|good)
 428        bisect_state "$cmd" "$@" ;;
 429    skip)
 430        bisect_skip "$@" ;;
 431    next)
 432        # Not sure we want "next" at the UI level anymore.
 433        bisect_next "$@" ;;
 434    visualize|view)
 435        bisect_visualize "$@" ;;
 436    reset)
 437        bisect_reset "$@" ;;
 438    replay)
 439        bisect_replay "$@" ;;
 440    log)
 441        cat "$GIT_DIR/BISECT_LOG" ;;
 442    run)
 443        bisect_run "$@" ;;
 444    *)
 445        usage ;;
 446    esac
 447esac