bisect: add "check_good_are_ancestors_of_bad" function
[git/dscho.git] / git-bisect.sh
blob786b7b9110a096fa7810e0dcc51ad8321f1a6f95
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>|<range>)...]
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 bisect_autostart() {
37 test -s "$GIT_DIR/BISECT_START" || {
38 echo >&2 'You need to start by "git bisect start"'
39 if test -t 0
40 then
41 echo >&2 -n 'Do you want me to do it for you [Y/n]? '
42 read yesno
43 case "$yesno" in
44 [Nn]*)
45 exit ;;
46 esac
47 bisect_start
48 else
49 exit 1
54 bisect_start() {
56 # Verify HEAD.
58 head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
59 head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
60 die "Bad HEAD - I need a HEAD"
63 # Check if we are bisecting.
65 start_head=''
66 if test -s "$GIT_DIR/BISECT_START"
67 then
68 # Reset to the rev from where we started.
69 start_head=$(cat "$GIT_DIR/BISECT_START")
70 git checkout "$start_head" -- || exit
71 else
72 # Get rev from where we start.
73 case "$head" in
74 refs/heads/*|$_x40)
75 # This error message should only be triggered by
76 # cogito usage, and cogito users should understand
77 # it relates to cg-seek.
78 [ -s "$GIT_DIR/head-name" ] &&
79 die "won't bisect on seeked tree"
80 start_head="${head#refs/heads/}"
83 die "Bad HEAD - strange symbolic ref"
85 esac
89 # Get rid of any old bisect state.
91 bisect_clean_state || exit
94 # Check for one bad and then some good revisions.
96 has_double_dash=0
97 for arg; do
98 case "$arg" in --) has_double_dash=1; break ;; esac
99 done
100 orig_args=$(git rev-parse --sq-quote "$@")
101 bad_seen=0
102 eval=''
103 while [ $# -gt 0 ]; do
104 arg="$1"
105 case "$arg" in
107 shift
108 break
111 rev=$(git rev-parse -q --verify "$arg^{commit}") || {
112 test $has_double_dash -eq 1 &&
113 die "'$arg' does not appear to be a valid revision"
114 break
116 case $bad_seen in
117 0) state='bad' ; bad_seen=1 ;;
118 *) state='good' ;;
119 esac
120 eval="$eval bisect_write '$state' '$rev' 'nolog'; "
121 shift
123 esac
124 done
127 # Change state.
128 # In case of mistaken revs or checkout error, or signals received,
129 # "bisect_auto_next" below may exit or misbehave.
130 # We have to trap this to be able to clean up using
131 # "bisect_clean_state".
133 trap 'bisect_clean_state' 0
134 trap 'exit 255' 1 2 3 15
137 # Write new start state.
139 echo "$start_head" >"$GIT_DIR/BISECT_START" &&
140 git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
141 eval "$eval" &&
142 echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
144 # Check if we can proceed to the next bisect state.
146 bisect_auto_next
148 trap '-' 0
151 bisect_write() {
152 state="$1"
153 rev="$2"
154 nolog="$3"
155 case "$state" in
156 bad) tag="$state" ;;
157 good|skip) tag="$state"-"$rev" ;;
158 *) die "Bad bisect_write argument: $state" ;;
159 esac
160 git update-ref "refs/bisect/$tag" "$rev" || exit
161 echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
162 test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
165 is_expected_rev() {
166 test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
167 test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
170 mark_expected_rev() {
171 echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
174 check_expected_revs() {
175 for _rev in "$@"; do
176 if ! is_expected_rev "$_rev"; then
177 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
178 rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
179 return
181 done
184 bisect_skip() {
185 all=''
186 for arg in "$@"
188 case "$arg" in
189 *..*)
190 revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
192 revs=$(git rev-parse --sq-quote "$arg") ;;
193 esac
194 all="$all $revs"
195 done
196 eval bisect_state 'skip' $all
199 bisect_state() {
200 bisect_autostart
201 state=$1
202 case "$#,$state" in
203 0,*)
204 die "Please call 'bisect_state' with at least one argument." ;;
205 1,bad|1,good|1,skip)
206 rev=$(git rev-parse --verify HEAD) ||
207 die "Bad rev input: HEAD"
208 bisect_write "$state" "$rev"
209 check_expected_revs "$rev" ;;
210 2,bad|*,good|*,skip)
211 shift
212 eval=''
213 for rev in "$@"
215 sha=$(git rev-parse --verify "$rev^{commit}") ||
216 die "Bad rev input: $rev"
217 eval="$eval bisect_write '$state' '$sha'; "
218 done
219 eval "$eval"
220 check_expected_revs "$@" ;;
221 *,bad)
222 die "'git bisect bad' can take only one argument." ;;
224 usage ;;
225 esac
226 bisect_auto_next
229 bisect_next_check() {
230 missing_good= missing_bad=
231 git show-ref -q --verify refs/bisect/bad || missing_bad=t
232 test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
234 case "$missing_good,$missing_bad,$1" in
235 ,,*)
236 : have both good and bad - ok
239 # do not have both but not asked to fail - just report.
240 false
242 t,,good)
243 # have bad but not good. we could bisect although
244 # this is less optimum.
245 echo >&2 'Warning: bisecting only with a bad commit.'
246 if test -t 0
247 then
248 printf >&2 'Are you sure [Y/n]? '
249 read yesno
250 case "$yesno" in [Nn]*) exit 1 ;; esac
252 : bisect without good...
255 THEN=''
256 test -s "$GIT_DIR/BISECT_START" || {
257 echo >&2 'You need to start by "git bisect start".'
258 THEN='then '
260 echo >&2 'You '$THEN'need to give me at least one good' \
261 'and one bad revisions.'
262 echo >&2 '(You can use "git bisect bad" and' \
263 '"git bisect good" for that.)'
264 exit 1 ;;
265 esac
268 bisect_auto_next() {
269 bisect_next_check && bisect_next || :
272 bisect_checkout() {
273 _rev="$1"
274 _msg="$2"
275 echo "Bisecting: $_msg"
276 mark_expected_rev "$_rev"
277 git checkout -q "$_rev" -- || exit
278 git show-branch "$_rev"
281 is_among() {
282 _rev="$1"
283 _list="$2"
284 case "$_list" in *$_rev*) return 0 ;; esac
285 return 1
288 handle_bad_merge_base() {
289 _badmb="$1"
290 _good="$2"
291 if is_expected_rev "$_badmb"; then
292 cat >&2 <<EOF
293 The merge base $_badmb is bad.
294 This means the bug has been fixed between $_badmb and [$_good].
296 exit 3
297 else
298 cat >&2 <<EOF
299 Some good revs are not ancestor of the bad rev.
300 git bisect cannot work properly in this case.
301 Maybe you mistake good and bad revs?
303 exit 1
307 handle_skipped_merge_base() {
308 _mb="$1"
309 _bad="$2"
310 _good="$3"
311 cat >&2 <<EOF
312 Warning: the merge base between $_bad and [$_good] must be skipped.
313 So we cannot be sure the first bad commit is between $_mb and $_bad.
314 We continue anyway.
319 # "check_merge_bases" checks that merge bases are not "bad".
321 # - If one is "good", that's good, we have nothing to do.
322 # - If one is "bad", it means the user assumed something wrong
323 # and we must exit.
324 # - If one is "skipped", we can't know but we should warn.
325 # - If we don't know, we should check it out and ask the user to test.
327 # In the last case we will return 1, and otherwise 0.
329 check_merge_bases() {
330 _bad="$1"
331 _good="$2"
332 _skip="$3"
333 for _mb in $(git merge-base --all $_bad $_good)
335 if is_among "$_mb" "$_good"; then
336 continue
337 elif test "$_mb" = "$_bad"; then
338 handle_bad_merge_base "$_bad" "$_good"
339 elif is_among "$_mb" "$_skip"; then
340 handle_skipped_merge_base "$_mb" "$_bad" "$_good"
341 else
342 bisect_checkout "$_mb" "a merge base must be tested"
343 return 1
345 done
346 return 0
350 # "check_good_are_ancestors_of_bad" checks that all "good" revs are
351 # ancestor of the "bad" rev.
353 # If that's not the case, we need to check the merge bases.
354 # If a merge base must be tested by the user we return 1 and
355 # otherwise 0.
357 check_good_are_ancestors_of_bad() {
358 test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
359 return
361 _bad="$1"
362 _good=$(echo $2 | sed -e 's/\^//g')
363 _skip="$3"
365 # Bisecting with no good rev is ok
366 test -z "$_good" && return
368 _side=$(git rev-list $_good ^$_bad)
369 if test -n "$_side"; then
370 # Return if a checkout was done
371 check_merge_bases "$_bad" "$_good" "$_skip" || return
374 : > "$GIT_DIR/BISECT_ANCESTORS_OK"
376 return 0
379 bisect_next() {
380 case "$#" in 0) ;; *) usage ;; esac
381 bisect_autostart
382 bisect_next_check good
384 # Get bad, good and skipped revs
385 bad=$(git rev-parse --verify refs/bisect/bad) &&
386 good=$(git for-each-ref --format='^%(objectname)' \
387 "refs/bisect/good-*" | tr '\012' ' ') &&
388 skip=$(git for-each-ref --format='%(objectname)' \
389 "refs/bisect/skip-*" | tr '\012' ' ') || exit
391 # Maybe some merge bases must be tested first
392 check_good_are_ancestors_of_bad "$bad" "$good" "$skip"
393 # Return now if a checkout has already been done
394 test "$?" -eq "1" && return
396 # Perform bisection computation, display and checkout
397 git bisect--helper --next-exit
398 res=$?
400 # Check if we should exit because bisection is finished
401 test $res -eq 10 && exit 0
403 # Check for an error in the bisection process
404 test $res -ne 0 && exit $res
406 return 0
409 bisect_visualize() {
410 bisect_next_check fail
412 if test $# = 0
413 then
414 case "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
415 '') set git log ;;
416 set*) set gitk ;;
417 esac
418 else
419 case "$1" in
420 git*|tig) ;;
421 -*) set git log "$@" ;;
422 *) set git "$@" ;;
423 esac
426 not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
427 eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
430 bisect_reset() {
431 test -s "$GIT_DIR/BISECT_START" || {
432 echo "We are not bisecting."
433 return
435 case "$#" in
436 0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
437 1) git show-ref --verify --quiet -- "refs/heads/$1" ||
438 die "$1 does not seem to be a valid branch"
439 branch="$1" ;;
441 usage ;;
442 esac
443 git checkout "$branch" -- && bisect_clean_state
446 bisect_clean_state() {
447 # There may be some refs packed during bisection.
448 git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
449 while read ref hash
451 git update-ref -d $ref $hash || exit
452 done
453 rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
454 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
455 rm -f "$GIT_DIR/BISECT_LOG" &&
456 rm -f "$GIT_DIR/BISECT_NAMES" &&
457 rm -f "$GIT_DIR/BISECT_RUN" &&
458 # Cleanup head-name if it got left by an old version of git-bisect
459 rm -f "$GIT_DIR/head-name" &&
461 rm -f "$GIT_DIR/BISECT_START"
464 bisect_replay () {
465 test -r "$1" || die "cannot read $1 for replaying"
466 bisect_reset
467 while read git bisect command rev
469 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
470 if test "$git" = "git-bisect"; then
471 rev="$command"
472 command="$bisect"
474 case "$command" in
475 start)
476 cmd="bisect_start $rev"
477 eval "$cmd" ;;
478 good|bad|skip)
479 bisect_write "$command" "$rev" ;;
481 die "?? what are you talking about?" ;;
482 esac
483 done <"$1"
484 bisect_auto_next
487 bisect_run () {
488 bisect_next_check fail
490 while true
492 echo "running $@"
493 "$@"
494 res=$?
496 # Check for really bad run error.
497 if [ $res -lt 0 -o $res -ge 128 ]; then
498 echo >&2 "bisect run failed:"
499 echo >&2 "exit code $res from '$@' is < 0 or >= 128"
500 exit $res
503 # Find current state depending on run success or failure.
504 # A special exit code of 125 means cannot test.
505 if [ $res -eq 125 ]; then
506 state='skip'
507 elif [ $res -gt 0 ]; then
508 state='bad'
509 else
510 state='good'
513 # We have to use a subshell because "bisect_state" can exit.
514 ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
515 res=$?
517 cat "$GIT_DIR/BISECT_RUN"
519 if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
520 > /dev/null; then
521 echo >&2 "bisect run cannot continue any more"
522 exit $res
525 if [ $res -ne 0 ]; then
526 echo >&2 "bisect run failed:"
527 echo >&2 "'bisect_state $state' exited with error code $res"
528 exit $res
531 if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
532 echo "bisect run success"
533 exit 0;
536 done
540 case "$#" in
542 usage ;;
544 cmd="$1"
545 shift
546 case "$cmd" in
547 help)
548 git bisect -h ;;
549 start)
550 bisect_start "$@" ;;
551 bad|good)
552 bisect_state "$cmd" "$@" ;;
553 skip)
554 bisect_skip "$@" ;;
555 next)
556 # Not sure we want "next" at the UI level anymore.
557 bisect_next "$@" ;;
558 visualize|view)
559 bisect_visualize "$@" ;;
560 reset)
561 bisect_reset "$@" ;;
562 replay)
563 bisect_replay "$@" ;;
564 log)
565 cat "$GIT_DIR/BISECT_LOG" ;;
566 run)
567 bisect_run "$@" ;;
569 usage ;;
570 esac
571 esac