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