bisect: add 'git bisect terms' to view the current terms
[git/debian.git] / git-bisect.sh
blob89255a333f4fe39675bf249376c328a497671371
1 #!/bin/sh
3 USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
4 LONG_USAGE='git bisect help
5 print this long help message.
6 git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
7 reset bisect state and start bisection.
8 git bisect (bad|new) [<rev>]
9 mark <rev> a known-bad revision/
10 a revision after change in a given property.
11 git bisect (good|old) [<rev>...]
12 mark <rev>... known-good revisions/
13 revisions before change in a given property.
14 git bisect terms [--term-good | --term-bad]
15 show the terms used for old and new commits (default: bad, good)
16 git bisect skip [(<rev>|<range>)...]
17 mark <rev>... untestable revisions.
18 git bisect next
19 find next bisection to test and check it out.
20 git bisect reset [<commit>]
21 finish bisection search and go back to commit.
22 git bisect visualize
23 show bisect status in gitk.
24 git bisect replay <logfile>
25 replay bisection log.
26 git bisect log
27 show bisect log.
28 git bisect run <cmd>...
29 use <cmd>... to automatically bisect.
31 Please use "git help bisect" to get the full man page.'
33 OPTIONS_SPEC=
34 . git-sh-setup
35 . git-sh-i18n
37 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
38 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
39 TERM_BAD=bad
40 TERM_GOOD=good
42 bisect_head()
44 if test -f "$GIT_DIR/BISECT_HEAD"
45 then
46 echo BISECT_HEAD
47 else
48 echo HEAD
52 bisect_autostart() {
53 test -s "$GIT_DIR/BISECT_START" || {
54 gettextln "You need to start by \"git bisect start\"" >&2
55 if test -t 0
56 then
57 # TRANSLATORS: Make sure to include [Y] and [n] in your
58 # translation. The program will only accept English input
59 # at this point.
60 gettext "Do you want me to do it for you [Y/n]? " >&2
61 read yesno
62 case "$yesno" in
63 [Nn]*)
64 exit ;;
65 esac
66 bisect_start
67 else
68 exit 1
73 bisect_start() {
75 # Check for one bad and then some good revisions.
77 has_double_dash=0
78 for arg; do
79 case "$arg" in --) has_double_dash=1; break ;; esac
80 done
81 orig_args=$(git rev-parse --sq-quote "$@")
82 bad_seen=0
83 eval=''
84 must_write_terms=0
85 revs=''
86 if test "z$(git rev-parse --is-bare-repository)" != zfalse
87 then
88 mode=--no-checkout
89 else
90 mode=''
92 while [ $# -gt 0 ]; do
93 arg="$1"
94 case "$arg" in
95 --)
96 shift
97 break
99 --no-checkout)
100 mode=--no-checkout
101 shift ;;
102 --*)
103 die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
105 rev=$(git rev-parse -q --verify "$arg^{commit}") || {
106 test $has_double_dash -eq 1 &&
107 die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
108 break
110 revs="$revs $rev"
111 shift
113 esac
114 done
116 for rev in $revs
118 # The user ran "git bisect start <sha1>
119 # <sha1>", hence did not explicitly specify
120 # the terms, but we are already starting to
121 # set references named with the default terms,
122 # and won't be able to change afterwards.
123 must_write_terms=1
125 case $bad_seen in
126 0) state=$TERM_BAD ; bad_seen=1 ;;
127 *) state=$TERM_GOOD ;;
128 esac
129 eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
130 done
132 # Verify HEAD.
134 head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
135 head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
136 die "$(gettext "Bad HEAD - I need a HEAD")"
139 # Check if we are bisecting.
141 start_head=''
142 if test -s "$GIT_DIR/BISECT_START"
143 then
144 # Reset to the rev from where we started.
145 start_head=$(cat "$GIT_DIR/BISECT_START")
146 if test "z$mode" != "z--no-checkout"
147 then
148 git checkout "$start_head" -- ||
149 die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")"
151 else
152 # Get rev from where we start.
153 case "$head" in
154 refs/heads/*|$_x40)
155 # This error message should only be triggered by
156 # cogito usage, and cogito users should understand
157 # it relates to cg-seek.
158 [ -s "$GIT_DIR/head-name" ] &&
159 die "$(gettext "won't bisect on cg-seek'ed tree")"
160 start_head="${head#refs/heads/}"
163 die "$(gettext "Bad HEAD - strange symbolic ref")"
165 esac
169 # Get rid of any old bisect state.
171 bisect_clean_state || exit
174 # Change state.
175 # In case of mistaken revs or checkout error, or signals received,
176 # "bisect_auto_next" below may exit or misbehave.
177 # We have to trap this to be able to clean up using
178 # "bisect_clean_state".
180 trap 'bisect_clean_state' 0
181 trap 'exit 255' 1 2 3 15
184 # Write new start state.
186 echo "$start_head" >"$GIT_DIR/BISECT_START" && {
187 test "z$mode" != "z--no-checkout" ||
188 git update-ref --no-deref BISECT_HEAD "$start_head"
189 } &&
190 git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
191 eval "$eval true" &&
192 if test $must_write_terms -eq 1
193 then
194 write_terms "$TERM_BAD" "$TERM_GOOD"
195 fi &&
196 echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
198 # Check if we can proceed to the next bisect state.
200 bisect_auto_next
202 trap '-' 0
205 bisect_write() {
206 state="$1"
207 rev="$2"
208 nolog="$3"
209 case "$state" in
210 "$TERM_BAD")
211 tag="$state" ;;
212 "$TERM_GOOD"|skip)
213 tag="$state"-"$rev" ;;
215 die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
216 esac
217 git update-ref "refs/bisect/$tag" "$rev" || exit
218 echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
219 test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
222 is_expected_rev() {
223 test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
224 test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
227 check_expected_revs() {
228 for _rev in "$@"; do
229 if ! is_expected_rev "$_rev"
230 then
231 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
232 rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
233 return
235 done
238 bisect_skip() {
239 all=''
240 for arg in "$@"
242 case "$arg" in
243 *..*)
244 revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
246 revs=$(git rev-parse --sq-quote "$arg") ;;
247 esac
248 all="$all $revs"
249 done
250 eval bisect_state 'skip' $all
253 bisect_state() {
254 bisect_autostart
255 state=$1
256 check_and_set_terms $state
257 case "$#,$state" in
258 0,*)
259 die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
260 1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
261 rev=$(git rev-parse --verify $(bisect_head)) ||
262 die "$(gettext "Bad rev input: $(bisect_head)")"
263 bisect_write "$state" "$rev"
264 check_expected_revs "$rev" ;;
265 2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
266 shift
267 hash_list=''
268 for rev in "$@"
270 sha=$(git rev-parse --verify "$rev^{commit}") ||
271 die "$(eval_gettext "Bad rev input: \$rev")"
272 hash_list="$hash_list $sha"
273 done
274 for rev in $hash_list
276 bisect_write "$state" "$rev"
277 done
278 check_expected_revs $hash_list ;;
279 *,"$TERM_BAD")
280 die "$(eval_gettext "'git bisect \$TERM_BAD' can take only one argument.")" ;;
282 usage ;;
283 esac
284 bisect_auto_next
287 bisect_next_check() {
288 missing_good= missing_bad=
289 git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t
290 test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t
292 case "$missing_good,$missing_bad,$1" in
293 ,,*)
294 : have both $TERM_GOOD and $TERM_BAD - ok
297 # do not have both but not asked to fail - just report.
298 false
300 t,,"$TERM_GOOD")
301 # have bad (or new) but not good (or old). we could bisect although
302 # this is less optimum.
303 eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
304 if test -t 0
305 then
306 # TRANSLATORS: Make sure to include [Y] and [n] in your
307 # translation. The program will only accept English input
308 # at this point.
309 gettext "Are you sure [Y/n]? " >&2
310 read yesno
311 case "$yesno" in [Nn]*) exit 1 ;; esac
313 : bisect without $TERM_GOOD...
316 bad_syn=$(bisect_voc bad)
317 good_syn=$(bisect_voc good)
318 if test -s "$GIT_DIR/BISECT_START"
319 then
321 eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision.
322 (You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
323 else
324 eval_gettextln "You need to start by \"git bisect start\".
325 You then need to give me at least one \$good_syn and one \$bad_syn revision.
326 (You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
328 exit 1 ;;
329 esac
332 bisect_auto_next() {
333 bisect_next_check && bisect_next || :
336 bisect_next() {
337 case "$#" in 0) ;; *) usage ;; esac
338 bisect_autostart
339 bisect_next_check $TERM_GOOD
341 # Perform all bisection computation, display and checkout
342 git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
343 res=$?
345 # Check if we should exit because bisection is finished
346 if test $res -eq 10
347 then
348 bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
349 bad_commit=$(git show-branch $bad_rev)
350 echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
351 exit 0
352 elif test $res -eq 2
353 then
354 echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
355 good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
356 for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
358 skipped_commit=$(git show-branch $skipped)
359 echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
360 done
361 exit $res
364 # Check for an error in the bisection process
365 test $res -ne 0 && exit $res
367 return 0
370 bisect_visualize() {
371 bisect_next_check fail
373 if test $# = 0
374 then
375 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
376 type gitk >/dev/null 2>&1
377 then
378 set gitk
379 else
380 set git log
382 else
383 case "$1" in
384 git*|tig) ;;
385 -*) set git log "$@" ;;
386 *) set git "$@" ;;
387 esac
390 eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
393 bisect_reset() {
394 test -s "$GIT_DIR/BISECT_START" || {
395 gettextln "We are not bisecting."
396 return
398 case "$#" in
399 0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
400 1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
401 invalid="$1"
402 die "$(eval_gettext "'\$invalid' is not a valid commit")"
404 branch="$1" ;;
406 usage ;;
407 esac
409 if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
410 then
411 die "$(eval_gettext "Could not check out original HEAD '\$branch'.
412 Try 'git bisect reset <commit>'.")"
414 bisect_clean_state
417 bisect_clean_state() {
418 # There may be some refs packed during bisection.
419 git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
420 while read ref hash
422 git update-ref -d $ref $hash || exit
423 done
424 rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
425 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
426 rm -f "$GIT_DIR/BISECT_LOG" &&
427 rm -f "$GIT_DIR/BISECT_NAMES" &&
428 rm -f "$GIT_DIR/BISECT_RUN" &&
429 rm -f "$GIT_DIR/BISECT_TERMS" &&
430 # Cleanup head-name if it got left by an old version of git-bisect
431 rm -f "$GIT_DIR/head-name" &&
432 git update-ref -d --no-deref BISECT_HEAD &&
433 # clean up BISECT_START last
434 rm -f "$GIT_DIR/BISECT_START"
437 bisect_replay () {
438 file="$1"
439 test "$#" -eq 1 || die "$(gettext "No logfile given")"
440 test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
441 bisect_reset
442 while read git bisect command rev
444 test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
445 if test "$git" = "git-bisect"
446 then
447 rev="$command"
448 command="$bisect"
450 get_terms
451 check_and_set_terms "$command"
452 case "$command" in
453 start)
454 cmd="bisect_start $rev"
455 eval "$cmd" ;;
456 "$TERM_GOOD"|"$TERM_BAD"|skip)
457 bisect_write "$command" "$rev" ;;
458 terms)
459 bisect_terms $rev ;;
461 die "$(gettext "?? what are you talking about?")" ;;
462 esac
463 done <"$file"
464 bisect_auto_next
467 bisect_run () {
468 bisect_next_check fail
470 while true
472 command="$@"
473 eval_gettextln "running \$command"
474 "$@"
475 res=$?
477 # Check for really bad run error.
478 if [ $res -lt 0 -o $res -ge 128 ]
479 then
480 eval_gettextln "bisect run failed:
481 exit code \$res from '\$command' is < 0 or >= 128" >&2
482 exit $res
485 # Find current state depending on run success or failure.
486 # A special exit code of 125 means cannot test.
487 if [ $res -eq 125 ]
488 then
489 state='skip'
490 elif [ $res -gt 0 ]
491 then
492 state="$TERM_BAD"
493 else
494 state="$TERM_GOOD"
497 # We have to use a subshell because "bisect_state" can exit.
498 ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
499 res=$?
501 cat "$GIT_DIR/BISECT_RUN"
503 if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \
504 >/dev/null
505 then
506 gettextln "bisect run cannot continue any more" >&2
507 exit $res
510 if [ $res -ne 0 ]
511 then
512 eval_gettextln "bisect run failed:
513 'bisect_state \$state' exited with error code \$res" >&2
514 exit $res
517 if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null
518 then
519 gettextln "bisect run success"
520 exit 0;
523 done
526 bisect_log () {
527 test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
528 cat "$GIT_DIR/BISECT_LOG"
531 get_terms () {
532 if test -s "$GIT_DIR/BISECT_TERMS"
533 then
535 read TERM_BAD
536 read TERM_GOOD
537 } <"$GIT_DIR/BISECT_TERMS"
541 write_terms () {
542 TERM_BAD=$1
543 TERM_GOOD=$2
544 if test "$TERM_BAD" = "$TERM_GOOD"
545 then
546 die "$(gettext "please use two different terms")"
548 check_term_format "$TERM_BAD" bad
549 check_term_format "$TERM_GOOD" good
550 printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
553 check_term_format () {
554 term=$1
555 git check-ref-format refs/bisect/"$term" ||
556 die "$(eval_gettext "'\$term' is not a valid term")"
557 case "$term" in
558 help|start|terms|skip|next|reset|visualize|replay|log|run)
559 die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
561 bad|new)
562 if test "$2" != bad
563 then
564 # In theory, nothing prevents swapping
565 # completely good and bad, but this situation
566 # could be confusing and hasn't been tested
567 # enough. Forbid it for now.
568 die "$(eval_gettext "can't change the meaning of term '\$term'")"
571 good|old)
572 if test "$2" != good
573 then
574 die "$(eval_gettext "can't change the meaning of term '\$term'")"
577 esac
580 check_and_set_terms () {
581 cmd="$1"
582 case "$cmd" in
583 skip|start|terms) ;;
585 if test -s "$GIT_DIR/BISECT_TERMS" && test "$cmd" != "$TERM_BAD" && test "$cmd" != "$TERM_GOOD"
586 then
587 die "$(eval_gettext "Invalid command: you're currently in a \$TERM_BAD/\$TERM_GOOD bisect.")"
589 case "$cmd" in
590 bad|good)
591 if ! test -s "$GIT_DIR/BISECT_TERMS"
592 then
593 write_terms bad good
596 new|old)
597 if ! test -s "$GIT_DIR/BISECT_TERMS"
598 then
599 write_terms new old
602 esac ;;
603 esac
606 bisect_voc () {
607 case "$1" in
608 bad) echo "bad|new" ;;
609 good) echo "good|old" ;;
610 esac
613 bisect_terms () {
614 get_terms
615 if ! test -s "$GIT_DIR/BISECT_TERMS"
616 then
617 die "$(gettext "no terms defined")"
619 case "$#" in
621 gettextln "Your current terms are $TERM_GOOD for the old state
622 and $TERM_BAD for the new state."
625 arg=$1
626 case "$arg" in
627 --term-good|--term-old)
628 printf '%s\n' "$TERM_GOOD"
630 --term-bad|--term-new)
631 printf '%s\n' "$TERM_BAD"
634 die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
635 Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
637 esac
640 usage ;;
641 esac
644 case "$#" in
646 usage ;;
648 cmd="$1"
649 get_terms
650 shift
651 case "$cmd" in
652 help)
653 git bisect -h ;;
654 start)
655 bisect_start "$@" ;;
656 bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
657 bisect_state "$cmd" "$@" ;;
658 skip)
659 bisect_skip "$@" ;;
660 next)
661 # Not sure we want "next" at the UI level anymore.
662 bisect_next "$@" ;;
663 visualize|view)
664 bisect_visualize "$@" ;;
665 reset)
666 bisect_reset "$@" ;;
667 replay)
668 bisect_replay "$@" ;;
669 log)
670 bisect_log ;;
671 run)
672 bisect_run "$@" ;;
673 terms)
674 bisect_terms "$@" ;;
676 usage ;;
677 esac
678 esac