git-bisect.shon commit Merge branch 'maint-1.5.4' into maint (464509f)
   1#!/bin/sh
   2
   3USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]'
   4LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
   5        reset bisect state and start bisection.
   6git bisect bad [<rev>]
   7        mark <rev> a known-bad revision.
   8git bisect good [<rev>...]
   9        mark <rev>... known-good revisions.
  10git bisect skip [<rev>...]
  11        mark <rev>... untestable revisions.
  12git bisect next
  13        find next bisection to test and check it out.
  14git bisect reset [<branch>]
  15        finish bisection search and go back to branch.
  16git bisect visualize
  17        show bisect status in gitk.
  18git bisect replay <logfile>
  19        replay bisection log.
  20git bisect log
  21        show bisect log.
  22git bisect run <cmd>...
  23        use <cmd>... to automatically bisect.'
  24
  25OPTIONS_SPEC=
  26. git-sh-setup
  27require_work_tree
  28
  29_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
  30_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
  31
  32sq() {
  33        @@PERL@@ -e '
  34                for (@ARGV) {
  35                        s/'\''/'\'\\\\\'\''/g;
  36                        print " '\''$_'\''";
  37                }
  38                print "\n";
  39        ' "$@"
  40}
  41
  42bisect_autostart() {
  43        test -f "$GIT_DIR/BISECT_NAMES" || {
  44                echo >&2 'You need to start by "git bisect start"'
  45                if test -t 0
  46                then
  47                        echo >&2 -n 'Do you want me to do it for you [Y/n]? '
  48                        read yesno
  49                        case "$yesno" in
  50                        [Nn]*)
  51                                exit ;;
  52                        esac
  53                        bisect_start
  54                else
  55                        exit 1
  56                fi
  57        }
  58}
  59
  60bisect_start() {
  61        #
  62        # Verify HEAD. If we were bisecting before this, reset to the
  63        # top-of-line master first!
  64        #
  65        head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
  66        head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
  67        die "Bad HEAD - I need a HEAD"
  68        start_head=''
  69        case "$head" in
  70        refs/heads/bisect)
  71                if [ -s "$GIT_DIR/BISECT_START" ]; then
  72                    branch=`cat "$GIT_DIR/BISECT_START"`
  73                else
  74                    branch=master
  75                fi
  76                git checkout $branch || exit
  77                ;;
  78        refs/heads/*|$_x40)
  79                # This error message should only be triggered by cogito usage,
  80                # and cogito users should understand it relates to cg-seek.
  81                [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
  82                start_head="${head#refs/heads/}"
  83                ;;
  84        *)
  85                die "Bad HEAD - strange symbolic ref"
  86                ;;
  87        esac
  88
  89        #
  90        # Get rid of any old bisect state
  91        #
  92        bisect_clean_state
  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=$(sq "$@")
 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 --verify "$arg^{commit}" 2>/dev/null) || {
 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        sq "$@" >"$GIT_DIR/BISECT_NAMES"
 128        test -n "$start_head" && echo "$start_head" >"$GIT_DIR/BISECT_START"
 129        eval "$eval"
 130        echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
 131        bisect_auto_next
 132}
 133
 134bisect_write() {
 135        state="$1"
 136        rev="$2"
 137        nolog="$3"
 138        case "$state" in
 139                bad)            tag="$state" ;;
 140                good|skip)      tag="$state"-"$rev" ;;
 141                *)              die "Bad bisect_write argument: $state" ;;
 142        esac
 143        git update-ref "refs/bisect/$tag" "$rev"
 144        echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
 145        test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 146}
 147
 148bisect_state() {
 149        bisect_autostart
 150        state=$1
 151        case "$#,$state" in
 152        0,*)
 153                die "Please call 'bisect_state' with at least one argument." ;;
 154        1,bad|1,good|1,skip)
 155                rev=$(git rev-parse --verify HEAD) ||
 156                        die "Bad rev input: HEAD"
 157                bisect_write "$state" "$rev" ;;
 158        2,bad|*,good|*,skip)
 159                shift
 160                eval=''
 161                for rev in "$@"
 162                do
 163                        sha=$(git rev-parse --verify "$rev^{commit}") ||
 164                                die "Bad rev input: $rev"
 165                        eval="$eval bisect_write '$state' '$sha'; "
 166                done
 167                eval "$eval" ;;
 168        *,bad)
 169                die "'git bisect bad' can take only one argument." ;;
 170        *)
 171                usage ;;
 172        esac
 173        bisect_auto_next
 174}
 175
 176bisect_next_check() {
 177        missing_good= missing_bad=
 178        git show-ref -q --verify refs/bisect/bad || missing_bad=t
 179        test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
 180
 181        case "$missing_good,$missing_bad,$1" in
 182        ,,*)
 183                : have both good and bad - ok
 184                ;;
 185        *,)
 186                # do not have both but not asked to fail - just report.
 187                false
 188                ;;
 189        t,,good)
 190                # have bad but not good.  we could bisect although
 191                # this is less optimum.
 192                echo >&2 'Warning: bisecting only with a bad commit.'
 193                if test -t 0
 194                then
 195                        printf >&2 'Are you sure [Y/n]? '
 196                        case "$(read yesno)" in [Nn]*) exit 1 ;; esac
 197                fi
 198                : bisect without good...
 199                ;;
 200        *)
 201                THEN=''
 202                test -f "$GIT_DIR/BISECT_NAMES" || {
 203                        echo >&2 'You need to start by "git bisect start".'
 204                        THEN='then '
 205                }
 206                echo >&2 'You '$THEN'need to give me at least one good' \
 207                        'and one bad revisions.'
 208                echo >&2 '(You can use "git bisect bad" and' \
 209                        '"git bisect good" for that.)'
 210                exit 1 ;;
 211        esac
 212}
 213
 214bisect_auto_next() {
 215        bisect_next_check && bisect_next || :
 216}
 217
 218filter_skipped() {
 219        _eval="$1"
 220        _skip="$2"
 221
 222        if [ -z "$_skip" ]; then
 223                eval $_eval
 224                return
 225        fi
 226
 227        # Let's parse the output of:
 228        # "git rev-list --bisect-vars --bisect-all ..."
 229        eval $_eval | while read hash line
 230        do
 231                case "$VARS,$FOUND,$TRIED,$hash" in
 232                        # We display some vars.
 233                        1,*,*,*) echo "$hash $line" ;;
 234
 235                        # Split line.
 236                        ,*,*,---*) ;;
 237
 238                        # We had nothing to search.
 239                        ,,,bisect_rev*)
 240                                echo "bisect_rev="
 241                                VARS=1
 242                                ;;
 243
 244                        # We did not find a good bisect rev.
 245                        # This should happen only if the "bad"
 246                        # commit is also a "skip" commit.
 247                        ,,*,bisect_rev*)
 248                                echo "bisect_rev=$TRIED"
 249                                VARS=1
 250                                ;;
 251
 252                        # We are searching.
 253                        ,,*,*)
 254                                TRIED="${TRIED:+$TRIED|}$hash"
 255                                case "$_skip" in
 256                                *$hash*) ;;
 257                                *)
 258                                        echo "bisect_rev=$hash"
 259                                        echo "bisect_tried=\"$TRIED\""
 260                                        FOUND=1
 261                                        ;;
 262                                esac
 263                                ;;
 264
 265                        # We have already found a rev to be tested.
 266                        ,1,*,bisect_rev*) VARS=1 ;;
 267                        ,1,*,*) ;;
 268
 269                        # ???
 270                        *) die "filter_skipped error " \
 271                            "VARS: '$VARS' " \
 272                            "FOUND: '$FOUND' " \
 273                            "TRIED: '$TRIED' " \
 274                            "hash: '$hash' " \
 275                            "line: '$line'"
 276                        ;;
 277                esac
 278        done
 279}
 280
 281exit_if_skipped_commits () {
 282        _tried=$1
 283        if expr "$_tried" : ".*[|].*" > /dev/null ; then
 284                echo "There are only 'skip'ped commit left to test."
 285                echo "The first bad commit could be any of:"
 286                echo "$_tried" | tr '[|]' '[\012]'
 287                echo "We cannot bisect more!"
 288                exit 2
 289        fi
 290}
 291
 292bisect_next() {
 293        case "$#" in 0) ;; *) usage ;; esac
 294        bisect_autostart
 295        bisect_next_check good
 296
 297        skip=$(git for-each-ref --format='%(objectname)' \
 298                "refs/bisect/skip-*" | tr '\012' ' ') || exit
 299
 300        BISECT_OPT=''
 301        test -n "$skip" && BISECT_OPT='--bisect-all'
 302
 303        bad=$(git rev-parse --verify refs/bisect/bad) &&
 304        good=$(git for-each-ref --format='^%(objectname)' \
 305                "refs/bisect/good-*" | tr '\012' ' ') &&
 306        eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
 307        eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
 308        eval=$(filter_skipped "$eval" "$skip") &&
 309        eval "$eval" || exit
 310
 311        if [ -z "$bisect_rev" ]; then
 312                echo "$bad was both good and bad"
 313                exit 1
 314        fi
 315        if [ "$bisect_rev" = "$bad" ]; then
 316                exit_if_skipped_commits "$bisect_tried"
 317                echo "$bisect_rev is first bad commit"
 318                git diff-tree --pretty $bisect_rev
 319                exit 0
 320        fi
 321
 322        # We should exit here only if the "bad"
 323        # commit is also a "skip" commit (see above).
 324        exit_if_skipped_commits "$bisect_rev"
 325
 326        echo "Bisecting: $bisect_nr revisions left to test after this"
 327        git branch -f new-bisect "$bisect_rev"
 328        git checkout -q new-bisect || exit
 329        git branch -M new-bisect bisect
 330        git show-branch "$bisect_rev"
 331}
 332
 333bisect_visualize() {
 334        bisect_next_check fail
 335
 336        if test $# = 0
 337        then
 338                case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
 339                '')     set git log ;;
 340                set*)   set gitk ;;
 341                esac
 342        else
 343                case "$1" in
 344                git*|tig) ;;
 345                -*)     set git log "$@" ;;
 346                *)      set git "$@" ;;
 347                esac
 348        fi
 349
 350        not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
 351        eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
 352}
 353
 354bisect_reset() {
 355        test -f "$GIT_DIR/BISECT_NAMES" || {
 356                echo "We are not bisecting."
 357                return
 358        }
 359        case "$#" in
 360        0) if [ -s "$GIT_DIR/BISECT_START" ]; then
 361               branch=`cat "$GIT_DIR/BISECT_START"`
 362           else
 363               branch=master
 364           fi ;;
 365        1) git show-ref --verify --quiet -- "refs/heads/$1" ||
 366               die "$1 does not seem to be a valid branch"
 367           branch="$1" ;;
 368        *)
 369            usage ;;
 370        esac
 371        if git checkout "$branch"; then
 372                # Cleanup head-name if it got left by an old version of git-bisect
 373                rm -f "$GIT_DIR/head-name"
 374                rm -f "$GIT_DIR/BISECT_START"
 375                bisect_clean_state
 376        fi
 377}
 378
 379bisect_clean_state() {
 380        # There may be some refs packed during bisection.
 381        git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
 382        while read ref hash
 383        do
 384                git update-ref -d $ref $hash
 385        done
 386        rm -f "$GIT_DIR/BISECT_LOG"
 387        rm -f "$GIT_DIR/BISECT_NAMES"
 388        rm -f "$GIT_DIR/BISECT_RUN"
 389}
 390
 391bisect_replay () {
 392        test -r "$1" || die "cannot read $1 for replaying"
 393        bisect_reset
 394        while read bisect command rev
 395        do
 396                test "$bisect" = "git-bisect" || continue
 397                case "$command" in
 398                start)
 399                        cmd="bisect_start $rev"
 400                        eval "$cmd" ;;
 401                good|bad|skip)
 402                        bisect_write "$command" "$rev" ;;
 403                *)
 404                        die "?? what are you talking about?" ;;
 405                esac
 406        done <"$1"
 407        bisect_auto_next
 408}
 409
 410bisect_run () {
 411    bisect_next_check fail
 412
 413    while true
 414    do
 415      echo "running $@"
 416      "$@"
 417      res=$?
 418
 419      # Check for really bad run error.
 420      if [ $res -lt 0 -o $res -ge 128 ]; then
 421          echo >&2 "bisect run failed:"
 422          echo >&2 "exit code $res from '$@' is < 0 or >= 128"
 423          exit $res
 424      fi
 425
 426      # Find current state depending on run success or failure.
 427      # A special exit code of 125 means cannot test.
 428      if [ $res -eq 125 ]; then
 429          state='skip'
 430      elif [ $res -gt 0 ]; then
 431          state='bad'
 432      else
 433          state='good'
 434      fi
 435
 436      # We have to use a subshell because "bisect_state" can exit.
 437      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
 438      res=$?
 439
 440      cat "$GIT_DIR/BISECT_RUN"
 441
 442      if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
 443                > /dev/null; then
 444          echo >&2 "bisect run cannot continue any more"
 445          exit $res
 446      fi
 447
 448      if [ $res -ne 0 ]; then
 449          echo >&2 "bisect run failed:"
 450          echo >&2 "'bisect_state $state' exited with error code $res"
 451          exit $res
 452      fi
 453
 454      if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
 455          echo "bisect run success"
 456          exit 0;
 457      fi
 458
 459    done
 460}
 461
 462
 463case "$#" in
 4640)
 465    usage ;;
 466*)
 467    cmd="$1"
 468    shift
 469    case "$cmd" in
 470    start)
 471        bisect_start "$@" ;;
 472    bad|good|skip)
 473        bisect_state "$cmd" "$@" ;;
 474    next)
 475        # Not sure we want "next" at the UI level anymore.
 476        bisect_next "$@" ;;
 477    visualize|view)
 478        bisect_visualize "$@" ;;
 479    reset)
 480        bisect_reset "$@" ;;
 481    replay)
 482        bisect_replay "$@" ;;
 483    log)
 484        cat "$GIT_DIR/BISECT_LOG" ;;
 485    run)
 486        bisect_run "$@" ;;
 487    *)
 488        usage ;;
 489    esac
 490esac