bisect: only check merge bases when needed
[git/mjg.git] / git-bisect.sh
blob69a9a565e0fe655cf09fad8ac693d896ef5d288a
1 #!/bin/sh
3 USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
4 LONG_USAGE='git bisect help
5 print this long help message.
6 git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
7 reset bisect state and start bisection.
8 git bisect bad [<rev>]
9 mark <rev> a known-bad revision.
10 git bisect good [<rev>...]
11 mark <rev>... known-good revisions.
12 git bisect skip [<rev>...]
13 mark <rev>... untestable revisions.
14 git bisect next
15 find next bisection to test and check it out.
16 git bisect reset [<branch>]
17 finish bisection search and go back to branch.
18 git bisect visualize
19 show bisect status in gitk.
20 git bisect replay <logfile>
21 replay bisection log.
22 git bisect log
23 show bisect log.
24 git bisect run <cmd>...
25 use <cmd>... to automatically bisect.
27 Please use "git help bisect" to get the full man page.'
29 OPTIONS_SPEC=
30 . git-sh-setup
31 require_work_tree
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"
36 sq() {
37 @@PERL@@ -e '
38 for (@ARGV) {
39 s/'\''/'\'\\\\\'\''/g;
40 print " '\''$_'\''";
42 print "\n";
43 ' "$@"
46 bisect_autostart() {
47 test -s "$GIT_DIR/BISECT_START" || {
48 echo >&2 'You need to start by "git bisect start"'
49 if test -t 0
50 then
51 echo >&2 -n 'Do you want me to do it for you [Y/n]? '
52 read yesno
53 case "$yesno" in
54 [Nn]*)
55 exit ;;
56 esac
57 bisect_start
58 else
59 exit 1
64 bisect_start() {
66 # Verify HEAD.
68 head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
69 head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
70 die "Bad HEAD - I need a HEAD"
73 # Check if we are bisecting.
75 start_head=''
76 if test -s "$GIT_DIR/BISECT_START"
77 then
78 # Reset to the rev from where we started.
79 start_head=$(cat "$GIT_DIR/BISECT_START")
80 git checkout "$start_head" || exit
81 else
82 # Get rev from where we start.
83 case "$head" in
84 refs/heads/*|$_x40)
85 # This error message should only be triggered by
86 # cogito usage, and cogito users should understand
87 # it relates to cg-seek.
88 [ -s "$GIT_DIR/head-name" ] &&
89 die "won't bisect on seeked tree"
90 start_head="${head#refs/heads/}"
93 die "Bad HEAD - strange symbolic ref"
95 esac
99 # Get rid of any old bisect state.
101 bisect_clean_state || exit
104 # Check for one bad and then some good revisions.
106 has_double_dash=0
107 for arg; do
108 case "$arg" in --) has_double_dash=1; break ;; esac
109 done
110 orig_args=$(sq "$@")
111 bad_seen=0
112 eval=''
113 while [ $# -gt 0 ]; do
114 arg="$1"
115 case "$arg" in
117 shift
118 break
121 rev=$(git rev-parse -q --verify "$arg^{commit}") || {
122 test $has_double_dash -eq 1 &&
123 die "'$arg' does not appear to be a valid revision"
124 break
126 case $bad_seen in
127 0) state='bad' ; bad_seen=1 ;;
128 *) state='good' ;;
129 esac
130 eval="$eval bisect_write '$state' '$rev' 'nolog'; "
131 shift
133 esac
134 done
137 # Change state.
138 # In case of mistaken revs or checkout error, or signals received,
139 # "bisect_auto_next" below may exit or misbehave.
140 # We have to trap this to be able to clean up using
141 # "bisect_clean_state".
143 trap 'bisect_clean_state' 0
144 trap 'exit 255' 1 2 3 15
147 # Write new start state.
149 echo "$start_head" >"$GIT_DIR/BISECT_START" &&
150 sq "$@" >"$GIT_DIR/BISECT_NAMES" &&
151 eval "$eval" &&
152 echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
154 # Check if we can proceed to the next bisect state.
156 bisect_auto_next
158 trap '-' 0
161 bisect_write() {
162 state="$1"
163 rev="$2"
164 nolog="$3"
165 case "$state" in
166 bad) tag="$state" ;;
167 good|skip) tag="$state"-"$rev" ;;
168 *) die "Bad bisect_write argument: $state" ;;
169 esac
170 git update-ref "refs/bisect/$tag" "$rev" || exit
171 echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
172 test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
175 is_expected_rev() {
176 test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
177 test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
180 mark_expected_rev() {
181 echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
184 check_expected_revs() {
185 for _rev in "$@"; do
186 if ! is_expected_rev "$_rev"; then
187 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
188 rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
189 return
191 done
194 bisect_state() {
195 bisect_autostart
196 state=$1
197 case "$#,$state" in
198 0,*)
199 die "Please call 'bisect_state' with at least one argument." ;;
200 1,bad|1,good|1,skip)
201 rev=$(git rev-parse --verify HEAD) ||
202 die "Bad rev input: HEAD"
203 bisect_write "$state" "$rev"
204 check_expected_revs "$rev" ;;
205 2,bad|*,good|*,skip)
206 shift
207 eval=''
208 for rev in "$@"
210 sha=$(git rev-parse --verify "$rev^{commit}") ||
211 die "Bad rev input: $rev"
212 eval="$eval bisect_write '$state' '$sha'; "
213 done
214 eval "$eval"
215 check_expected_revs "$@" ;;
216 *,bad)
217 die "'git bisect bad' can take only one argument." ;;
219 usage ;;
220 esac
221 bisect_auto_next
224 bisect_next_check() {
225 missing_good= missing_bad=
226 git show-ref -q --verify refs/bisect/bad || missing_bad=t
227 test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
229 case "$missing_good,$missing_bad,$1" in
230 ,,*)
231 : have both good and bad - ok
234 # do not have both but not asked to fail - just report.
235 false
237 t,,good)
238 # have bad but not good. we could bisect although
239 # this is less optimum.
240 echo >&2 'Warning: bisecting only with a bad commit.'
241 if test -t 0
242 then
243 printf >&2 'Are you sure [Y/n]? '
244 read yesno
245 case "$yesno" in [Nn]*) exit 1 ;; esac
247 : bisect without good...
250 THEN=''
251 test -s "$GIT_DIR/BISECT_START" || {
252 echo >&2 'You need to start by "git bisect start".'
253 THEN='then '
255 echo >&2 'You '$THEN'need to give me at least one good' \
256 'and one bad revisions.'
257 echo >&2 '(You can use "git bisect bad" and' \
258 '"git bisect good" for that.)'
259 exit 1 ;;
260 esac
263 bisect_auto_next() {
264 bisect_next_check && bisect_next || :
267 filter_skipped() {
268 _eval="$1"
269 _skip="$2"
271 if [ -z "$_skip" ]; then
272 eval "$_eval"
273 return
276 # Let's parse the output of:
277 # "git rev-list --bisect-vars --bisect-all ..."
278 eval "$_eval" | while read hash line
280 case "$VARS,$FOUND,$TRIED,$hash" in
281 # We display some vars.
282 1,*,*,*) echo "$hash $line" ;;
284 # Split line.
285 ,*,*,---*) ;;
287 # We had nothing to search.
288 ,,,bisect_rev*)
289 echo "bisect_rev="
290 VARS=1
293 # We did not find a good bisect rev.
294 # This should happen only if the "bad"
295 # commit is also a "skip" commit.
296 ,,*,bisect_rev*)
297 echo "bisect_rev=$TRIED"
298 VARS=1
301 # We are searching.
302 ,,*,*)
303 TRIED="${TRIED:+$TRIED|}$hash"
304 case "$_skip" in
305 *$hash*) ;;
307 echo "bisect_rev=$hash"
308 echo "bisect_tried=\"$TRIED\""
309 FOUND=1
311 esac
314 # We have already found a rev to be tested.
315 ,1,*,bisect_rev*) VARS=1 ;;
316 ,1,*,*) ;;
318 # ???
319 *) die "filter_skipped error " \
320 "VARS: '$VARS' " \
321 "FOUND: '$FOUND' " \
322 "TRIED: '$TRIED' " \
323 "hash: '$hash' " \
324 "line: '$line'"
326 esac
327 done
330 exit_if_skipped_commits () {
331 _tried=$1
332 if expr "$_tried" : ".*[|].*" > /dev/null ; then
333 echo "There are only 'skip'ped commit left to test."
334 echo "The first bad commit could be any of:"
335 echo "$_tried" | tr '[|]' '[\012]'
336 echo "We cannot bisect more!"
337 exit 2
341 bisect_checkout() {
342 _rev="$1"
343 _msg="$2"
344 echo "Bisecting: $_msg"
345 mark_expected_rev "$_rev"
346 git checkout -q "$_rev" || exit
347 git show-branch "$_rev"
350 is_among() {
351 _rev="$1"
352 _list="$2"
353 case "$_list" in *$_rev*) return 0 ;; esac
354 return 1
357 handle_bad_merge_base() {
358 _badmb="$1"
359 _good="$2"
360 if is_expected_rev "$_badmb"; then
361 cat >&2 <<EOF
362 The merge base $_badmb is bad.
363 This means the bug has been fixed between $_badmb and [$_good].
365 exit 3
366 else
367 cat >&2 <<EOF
368 Some good revs are not ancestor of the bad rev.
369 git bisect cannot work properly in this case.
370 Maybe you mistake good and bad revs?
372 exit 1
376 handle_skipped_merge_base() {
377 _mb="$1"
378 _bad="$2"
379 _good="$3"
380 cat >&2 <<EOF
381 Warning: the merge base between $_bad and [$_good] must be skipped.
382 So we cannot be sure the first bad commit is between $_mb and $_bad.
383 We continue anyway.
387 check_merge_bases() {
388 _bad="$1"
389 _good="$2"
390 _skip="$3"
391 for _mb in $(git merge-base --all $_bad $_good)
393 if is_among "$_mb" "$_good"; then
394 continue
395 elif test "$_mb" = "$_bad"; then
396 handle_bad_merge_base "$_bad" "$_good"
397 elif is_among "$_mb" "$_skip"; then
398 handle_skipped_merge_base "$_mb" "$_bad" "$_good"
399 else
400 bisect_checkout "$_mb" "a merge base must be tested"
401 checkout_done=1
402 return
404 done
407 check_good_are_ancestors_of_bad() {
408 test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
409 return
411 _bad="$1"
412 _good=$(echo $2 | sed -e 's/\^//g')
413 _skip="$3"
415 # Bisecting with no good rev is ok
416 test -z "$_good" && return
418 _side=$(git rev-list $_good ^$_bad)
419 if test -n "$_side"; then
420 check_merge_bases "$_bad" "$_good" "$_skip" || return
421 test "$checkout_done" -eq "1" && return
424 : > "$GIT_DIR/BISECT_ANCESTORS_OK"
427 bisect_next() {
428 case "$#" in 0) ;; *) usage ;; esac
429 bisect_autostart
430 bisect_next_check good
432 # Get bad, good and skipped revs
433 bad=$(git rev-parse --verify refs/bisect/bad) &&
434 good=$(git for-each-ref --format='^%(objectname)' \
435 "refs/bisect/good-*" | tr '\012' ' ') &&
436 skip=$(git for-each-ref --format='%(objectname)' \
437 "refs/bisect/skip-*" | tr '\012' ' ') &&
439 # Maybe some merge bases must be tested first
440 check_good_are_ancestors_of_bad "$bad" "$good" "$skip" || exit
441 test "$checkout_done" -eq "1" && checkout_done='' && return
443 # Get bisection information
444 BISECT_OPT=''
445 test -n "$skip" && BISECT_OPT='--bisect-all'
446 eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
447 eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
448 eval=$(filter_skipped "$eval" "$skip") &&
449 eval "$eval" || exit
451 if [ -z "$bisect_rev" ]; then
452 echo "$bad was both good and bad"
453 exit 1
455 if [ "$bisect_rev" = "$bad" ]; then
456 exit_if_skipped_commits "$bisect_tried"
457 echo "$bisect_rev is first bad commit"
458 git diff-tree --pretty $bisect_rev
459 exit 0
462 # We should exit here only if the "bad"
463 # commit is also a "skip" commit (see above).
464 exit_if_skipped_commits "$bisect_rev"
466 bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
469 bisect_visualize() {
470 bisect_next_check fail
472 if test $# = 0
473 then
474 case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
475 '') set git log ;;
476 set*) set gitk ;;
477 esac
478 else
479 case "$1" in
480 git*|tig) ;;
481 -*) set git log "$@" ;;
482 *) set git "$@" ;;
483 esac
486 not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
487 eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
490 bisect_reset() {
491 test -s "$GIT_DIR/BISECT_START" || {
492 echo "We are not bisecting."
493 return
495 case "$#" in
496 0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
497 1) git show-ref --verify --quiet -- "refs/heads/$1" ||
498 die "$1 does not seem to be a valid branch"
499 branch="$1" ;;
501 usage ;;
502 esac
503 git checkout "$branch" && bisect_clean_state
506 bisect_clean_state() {
507 # There may be some refs packed during bisection.
508 git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
509 while read ref hash
511 git update-ref -d $ref $hash || exit
512 done
513 rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
514 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
515 rm -f "$GIT_DIR/BISECT_LOG" &&
516 rm -f "$GIT_DIR/BISECT_NAMES" &&
517 rm -f "$GIT_DIR/BISECT_RUN" &&
518 # Cleanup head-name if it got left by an old version of git-bisect
519 rm -f "$GIT_DIR/head-name" &&
521 rm -f "$GIT_DIR/BISECT_START"
524 bisect_replay () {
525 test -r "$1" || die "cannot read $1 for replaying"
526 bisect_reset
527 while read git bisect command rev
529 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
530 if test "$git" = "git-bisect"; then
531 rev="$command"
532 command="$bisect"
534 case "$command" in
535 start)
536 cmd="bisect_start $rev"
537 eval "$cmd" ;;
538 good|bad|skip)
539 bisect_write "$command" "$rev" ;;
541 die "?? what are you talking about?" ;;
542 esac
543 done <"$1"
544 bisect_auto_next
547 bisect_run () {
548 bisect_next_check fail
550 while true
552 echo "running $@"
553 "$@"
554 res=$?
556 # Check for really bad run error.
557 if [ $res -lt 0 -o $res -ge 128 ]; then
558 echo >&2 "bisect run failed:"
559 echo >&2 "exit code $res from '$@' is < 0 or >= 128"
560 exit $res
563 # Find current state depending on run success or failure.
564 # A special exit code of 125 means cannot test.
565 if [ $res -eq 125 ]; then
566 state='skip'
567 elif [ $res -gt 0 ]; then
568 state='bad'
569 else
570 state='good'
573 # We have to use a subshell because "bisect_state" can exit.
574 ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
575 res=$?
577 cat "$GIT_DIR/BISECT_RUN"
579 if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
580 > /dev/null; then
581 echo >&2 "bisect run cannot continue any more"
582 exit $res
585 if [ $res -ne 0 ]; then
586 echo >&2 "bisect run failed:"
587 echo >&2 "'bisect_state $state' exited with error code $res"
588 exit $res
591 if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
592 echo "bisect run success"
593 exit 0;
596 done
600 case "$#" in
602 usage ;;
604 cmd="$1"
605 shift
606 case "$cmd" in
607 help)
608 git bisect -h ;;
609 start)
610 bisect_start "$@" ;;
611 bad|good|skip)
612 bisect_state "$cmd" "$@" ;;
613 next)
614 # Not sure we want "next" at the UI level anymore.
615 bisect_next "$@" ;;
616 visualize|view)
617 bisect_visualize "$@" ;;
618 reset)
619 bisect_reset "$@" ;;
620 replay)
621 bisect_replay "$@" ;;
622 log)
623 cat "$GIT_DIR/BISECT_LOG" ;;
624 run)
625 bisect_run "$@" ;;
627 usage ;;
628 esac
629 esac