git-bisect.sh 10.5 KB
Newer Older
1
#!/bin/sh
F
Fredrik Kuivinen 已提交
2

3
USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]'
4 5 6 7 8 9
LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
        reset bisect state and start bisection.
git bisect bad [<rev>]
        mark <rev> a known-bad revision.
git bisect good [<rev>...]
        mark <rev>... known-good revisions.
10 11
git bisect skip [<rev>...]
        mark <rev>... untestable revisions.
12 13 14 15 16 17 18 19 20 21 22 23
git bisect next
        find next bisection to test and check it out.
git bisect reset [<branch>]
        finish bisection search and go back to branch.
git bisect visualize
        show bisect status in gitk.
git bisect replay <logfile>
        replay bisection log.
git bisect log
        show bisect log.
git bisect run <cmd>...
        use <cmd>... to automatically bisect.'
F
Fredrik Kuivinen 已提交
24

25
OPTIONS_SPEC=
26
. git-sh-setup
27
require_work_tree
28

29 30 31
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"

32
sq() {
M
Michal Rokos 已提交
33
	@@PERL@@ -e '
34 35 36 37 38 39 40 41
		for (@ARGV) {
			s/'\''/'\'\\\\\'\''/g;
			print " '\''$_'\''";
		}
		print "\n";
	' "$@"
}

42
bisect_autostart() {
43
	test -f "$GIT_DIR/BISECT_NAMES" || {
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
		echo >&2 'You need to start by "git bisect start"'
		if test -t 0
		then
			echo >&2 -n 'Do you want me to do it for you [Y/n]? '
			read yesno
			case "$yesno" in
			[Nn]*)
				exit ;;
			esac
			bisect_start
		else
			exit 1
		fi
	}
}

bisect_start() {
	#
	# Verify HEAD. If we were bisecting before this, reset to the
	# top-of-line master first!
	#
65
	head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
66 67
	head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
	die "Bad HEAD - I need a HEAD"
68
	case "$head" in
J
Junio C Hamano 已提交
69
	refs/heads/bisect)
70 71
		if [ -s "$GIT_DIR/BISECT_START" ]; then
		    branch=`cat "$GIT_DIR/BISECT_START"`
72 73
		else
		    branch=master
74
		fi
75
		git checkout $branch || exit
76
		;;
77
	refs/heads/*|$_x40)
78 79
		# This error message should only be triggered by cogito usage,
		# and cogito users should understand it relates to cg-seek.
80
		[ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
81
		echo "${head#refs/heads/}" >"$GIT_DIR/BISECT_START"
82 83
		;;
	*)
J
Junio C Hamano 已提交
84
		die "Bad HEAD - strange symbolic ref"
85 86 87 88 89 90
		;;
	esac

	#
	# Get rid of any old bisect state
	#
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
	bisect_clean_state

	#
	# Check for one bad and then some good revisions.
	#
	has_double_dash=0
	for arg; do
	    case "$arg" in --) has_double_dash=1; break ;; esac
	done
	orig_args=$(sq "$@")
	bad_seen=0
	while [ $# -gt 0 ]; do
	    arg="$1"
	    case "$arg" in
	    --)
106
		shift
107 108 109
		break
		;;
	    *)
110
		rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
111 112 113 114
		    test $has_double_dash -eq 1 &&
		        die "'$arg' does not appear to be a valid revision"
		    break
		}
115 116 117 118 119
		case $bad_seen in
		0) state='bad' ; bad_seen=1 ;;
		*) state='good' ;;
		esac
		bisect_write "$state" "$rev" 'nolog'
120
		shift
121 122
		;;
	    esac
123
	done
124 125

	sq "$@" >"$GIT_DIR/BISECT_NAMES"
126
	echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
127
	bisect_auto_next
128 129
}

130 131 132
bisect_write() {
	state="$1"
	rev="$2"
133
	nolog="$3"
134 135 136 137 138
	case "$state" in
		bad)		tag="$state" ;;
		good|skip)	tag="$state"-"$rev" ;;
		*)		die "Bad bisect_write argument: $state" ;;
	esac
139
	git update-ref "refs/bisect/$tag" "$rev"
140
	echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
141
	test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
142 143
}

144
bisect_state() {
145
	bisect_autostart
146 147 148 149 150 151 152 153
	state=$1
	case "$#,$state" in
	0,*)
		die "Please call 'bisect_state' with at least one argument." ;;
	1,bad|1,good|1,skip)
		rev=$(git rev-parse --verify HEAD) ||
			die "Bad rev input: HEAD"
		bisect_write "$state" "$rev" ;;
154
	2,bad|*,good|*,skip)
155
		shift
156
		for rev in "$@"
157
		do
C
Christian Couder 已提交
158
			sha=$(git rev-parse --verify "$rev^{commit}") ||
159
				die "Bad rev input: $rev"
C
Christian Couder 已提交
160
			bisect_write "$state" "$sha"
161
		done ;;
162 163
	*,bad)
		die "'git bisect bad' can take only one argument." ;;
J
Junio C Hamano 已提交
164 165
	*)
		usage ;;
166
	esac
167 168 169
	bisect_auto_next
}

170
bisect_next_check() {
171 172 173
	missing_good= missing_bad=
	git show-ref -q --verify refs/bisect/bad || missing_bad=t
	test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
J
Junio C Hamano 已提交
174

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
	case "$missing_good,$missing_bad,$1" in
	,,*)
		: have both good and bad - ok
		;;
	*,)
		# do not have both but not asked to fail - just report.
		false
		;;
	t,,good)
		# have bad but not good.  we could bisect although
		# this is less optimum.
		echo >&2 'Warning: bisecting only with a bad commit.'
		if test -t 0
		then
			printf >&2 'Are you sure [Y/n]? '
			case "$(read yesno)" in [Nn]*) exit 1 ;; esac
		fi
		: bisect without good...
		;;
194
	*)
195
		THEN=''
196
		test -f "$GIT_DIR/BISECT_NAMES" || {
197 198 199 200 201 202 203 204
			echo >&2 'You need to start by "git bisect start".'
			THEN='then '
		}
		echo >&2 'You '$THEN'need to give me at least one good' \
			'and one bad revisions.'
		echo >&2 '(You can use "git bisect bad" and' \
			'"git bisect good" for that.)'
		exit 1 ;;
205 206 207 208
	esac
}

bisect_auto_next() {
209
	bisect_next_check && bisect_next || :
210 211
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
filter_skipped() {
	_eval="$1"
	_skip="$2"

	if [ -z "$_skip" ]; then
		eval $_eval
		return
	fi

	# Let's parse the output of:
	# "git rev-list --bisect-vars --bisect-all ..."
	eval $_eval | while read hash line
	do
		case "$VARS,$FOUND,$TRIED,$hash" in
			# We display some vars.
			1,*,*,*) echo "$hash $line" ;;

			# Split line.
			,*,*,---*) ;;

			# We had nothing to search.
			,,,bisect_rev*)
				echo "bisect_rev="
				VARS=1
				;;

			# We did not find a good bisect rev.
			# This should happen only if the "bad"
			# commit is also a "skip" commit.
			,,*,bisect_rev*)
				echo "bisect_rev=$TRIED"
				VARS=1
				;;

			# We are searching.
			,,*,*)
				TRIED="${TRIED:+$TRIED|}$hash"
				case "$_skip" in
				*$hash*) ;;
				*)
					echo "bisect_rev=$hash"
					echo "bisect_tried=\"$TRIED\""
					FOUND=1
					;;
				esac
				;;

			# We have already found a rev to be tested.
			,1,*,bisect_rev*) VARS=1 ;;
			,1,*,*) ;;

			# ???
			*) die "filter_skipped error " \
			    "VARS: '$VARS' " \
			    "FOUND: '$FOUND' " \
			    "TRIED: '$TRIED' " \
			    "hash: '$hash' " \
			    "line: '$line'"
			;;
		esac
	done
}

exit_if_skipped_commits () {
	_tried=$1
	if expr "$_tried" : ".*[|].*" > /dev/null ; then
		echo "There are only 'skip'ped commit left to test."
		echo "The first bad commit could be any of:"
J
Junio C Hamano 已提交
280
		echo "$_tried" | tr '[|]' '[\012]'
281 282 283 284 285
		echo "We cannot bisect more!"
		exit 2
	fi
}

286
bisect_next() {
287
	case "$#" in 0) ;; *) usage ;; esac
288
	bisect_autostart
289 290
	bisect_next_check good

291 292 293 294 295 296
	skip=$(git for-each-ref --format='%(objectname)' \
		"refs/bisect/skip-*" | tr '[\012]' ' ') || exit

	BISECT_OPT=''
	test -n "$skip" && BISECT_OPT='--bisect-all'

297
	bad=$(git rev-parse --verify refs/bisect/bad) &&
298 299
	good=$(git for-each-ref --format='^%(objectname)' \
		"refs/bisect/good-*" | tr '[\012]' ' ') &&
300
	eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
301
	eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
302
	eval=$(filter_skipped "$eval" "$skip") &&
303 304 305 306 307
	eval "$eval" || exit

	if [ -z "$bisect_rev" ]; then
		echo "$bad was both good and bad"
		exit 1
308
	fi
309
	if [ "$bisect_rev" = "$bad" ]; then
310
		exit_if_skipped_commits "$bisect_tried"
311
		echo "$bisect_rev is first bad commit"
312
		git diff-tree --pretty $bisect_rev
313
		exit 0
314
	fi
315

316 317 318 319
	# We should exit here only if the "bad"
	# commit is also a "skip" commit (see above).
	exit_if_skipped_commits "$bisect_rev"

320
	echo "Bisecting: $bisect_nr revisions left to test after this"
321
	git branch -f new-bisect "$bisect_rev"
322
	git checkout -q new-bisect || exit
323
	git branch -M new-bisect bisect
324
	git show-branch "$bisect_rev"
325 326
}

J
Junio C Hamano 已提交
327 328
bisect_visualize() {
	bisect_next_check fail
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

	if test $# = 0
	then
		case "${DISPLAY+set}" in
		'')	set git log ;;
		set)	set gitk ;;
		esac
	else
		case "$1" in
		git*|tig) ;;
		-*)	set git log "$@" ;;
		*)	set git "$@" ;;
		esac
	fi

344
	not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
345
	eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
J
Junio C Hamano 已提交
346 347
}

348
bisect_reset() {
349 350 351 352
	test -f "$GIT_DIR/BISECT_NAMES" || {
		echo "We are not bisecting."
		return
	}
353
	case "$#" in
354 355
	0) if [ -s "$GIT_DIR/BISECT_START" ]; then
	       branch=`cat "$GIT_DIR/BISECT_START"`
356 357 358
	   else
	       branch=master
	   fi ;;
359 360
	1) git show-ref --verify --quiet -- "refs/heads/$1" ||
	       die "$1 does not seem to be a valid branch"
361
	   branch="$1" ;;
362
	*)
363 364
	    usage ;;
	esac
365
	if git checkout "$branch"; then
366
		# Cleanup head-name if it got left by an old version of git-bisect
367
		rm -f "$GIT_DIR/head-name"
368
		rm -f "$GIT_DIR/BISECT_START"
369
		bisect_clean_state
370
	fi
371 372
}

373
bisect_clean_state() {
374 375 376 377 378 379
	# There may be some refs packed during bisection.
	git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
	while read ref hash
	do
		git update-ref -d $ref $hash
	done
380 381 382 383 384
	rm -f "$GIT_DIR/BISECT_LOG"
	rm -f "$GIT_DIR/BISECT_NAMES"
	rm -f "$GIT_DIR/BISECT_RUN"
}

385
bisect_replay () {
386
	test -r "$1" || die "cannot read $1 for replaying"
387 388 389 390 391 392
	bisect_reset
	while read bisect command rev
	do
		test "$bisect" = "git-bisect" || continue
		case "$command" in
		start)
393
			cmd="bisect_start $rev"
394 395 396
			eval "$cmd" ;;
		good|bad|skip)
			bisect_write "$command" "$rev" ;;
397
		*)
398
			die "?? what are you talking about?" ;;
399 400 401
		esac
	done <"$1"
	bisect_auto_next
402 403
}

404
bisect_run () {
405 406
    bisect_next_check fail

407 408 409 410 411 412 413 414 415 416 417 418 419
    while true
    do
      echo "running $@"
      "$@"
      res=$?

      # Check for really bad run error.
      if [ $res -lt 0 -o $res -ge 128 ]; then
	  echo >&2 "bisect run failed:"
	  echo >&2 "exit code $res from '$@' is < 0 or >= 128"
	  exit $res
      fi

420
      # Find current state depending on run success or failure.
421 422 423 424
      # A special exit code of 125 means cannot test.
      if [ $res -eq 125 ]; then
	  state='skip'
      elif [ $res -gt 0 ]; then
425
	  state='bad'
426
      else
427
	  state='good'
428 429
      fi

430 431
      # We have to use a subshell because "bisect_state" can exit.
      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
432 433 434 435
      res=$?

      cat "$GIT_DIR/BISECT_RUN"

436 437 438 439 440 441
      if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
		> /dev/null; then
	  echo >&2 "bisect run cannot continue any more"
	  exit $res
      fi

442 443
      if [ $res -ne 0 ]; then
	  echo >&2 "bisect run failed:"
444
	  echo >&2 "'bisect_state $state' exited with error code $res"
445 446 447 448 449 450 451 452 453 454 455 456
	  exit $res
      fi

      if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
	  echo "bisect run success"
	  exit 0;
      fi

    done
}


457 458 459 460 461 462 463 464 465
case "$#" in
0)
    usage ;;
*)
    cmd="$1"
    shift
    case "$cmd" in
    start)
        bisect_start "$@" ;;
466 467
    bad|good|skip)
        bisect_state "$cmd" "$@" ;;
468 469 470
    next)
        # Not sure we want "next" at the UI level anymore.
        bisect_next "$@" ;;
471
    visualize|view)
J
Junio C Hamano 已提交
472
	bisect_visualize "$@" ;;
473 474
    reset)
        bisect_reset "$@" ;;
475 476 477 478
    replay)
	bisect_replay "$@" ;;
    log)
	cat "$GIT_DIR/BISECT_LOG" ;;
479 480
    run)
        bisect_run "$@" ;;
481 482 483 484
    *)
        usage ;;
    esac
esac