]> asedeno.scripts.mit.edu Git - git.git/blob - git-bisect.sh
bisect: test merge base if good rev is not an ancestor of bad rev
[git.git] / git-bisect.sh
1 #!/bin/sh
2
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.
26
27 Please use "git help bisect" to get the full man page.'
28
29 OPTIONS_SPEC=
30 . git-sh-setup
31 require_work_tree
32
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"
35
36 sq() {
37         @@PERL@@ -e '
38                 for (@ARGV) {
39                         s/'\''/'\'\\\\\'\''/g;
40                         print " '\''$_'\''";
41                 }
42                 print "\n";
43         ' "$@"
44 }
45
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
60                 fi
61         }
62 }
63
64 bisect_start() {
65         #
66         # Verify HEAD.
67         #
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"
71
72         #
73         # Check if we are bisecting.
74         #
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/}"
91                         ;;
92                 *)
93                         die "Bad HEAD - strange symbolic ref"
94                         ;;
95                 esac
96         fi
97
98         #
99         # Get rid of any old bisect state.
100         #
101         bisect_clean_state || exit
102
103         #
104         # Check for one bad and then some good revisions.
105         #
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
116             --)
117                 shift
118                 break
119                 ;;
120             *)
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
125                 }
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
132                 ;;
133             esac
134         done
135
136         #
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".
142         #
143         trap 'bisect_clean_state' 0
144         trap 'exit 255' 1 2 3 15
145
146         #
147         # Write new start state.
148         #
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
153         #
154         # Check if we can proceed to the next bisect state.
155         #
156         bisect_auto_next
157
158         trap '-' 0
159 }
160
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"
173 }
174
175 bisect_state() {
176         bisect_autostart
177         state=$1
178         case "$#,$state" in
179         0,*)
180                 die "Please call 'bisect_state' with at least one argument." ;;
181         1,bad|1,good|1,skip)
182                 rev=$(git rev-parse --verify HEAD) ||
183                         die "Bad rev input: HEAD"
184                 bisect_write "$state" "$rev" ;;
185         2,bad|*,good|*,skip)
186                 shift
187                 eval=''
188                 for rev in "$@"
189                 do
190                         sha=$(git rev-parse --verify "$rev^{commit}") ||
191                                 die "Bad rev input: $rev"
192                         eval="$eval bisect_write '$state' '$sha'; "
193                 done
194                 eval "$eval" ;;
195         *,bad)
196                 die "'git bisect bad' can take only one argument." ;;
197         *)
198                 usage ;;
199         esac
200         bisect_auto_next
201 }
202
203 bisect_next_check() {
204         missing_good= missing_bad=
205         git show-ref -q --verify refs/bisect/bad || missing_bad=t
206         test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
207
208         case "$missing_good,$missing_bad,$1" in
209         ,,*)
210                 : have both good and bad - ok
211                 ;;
212         *,)
213                 # do not have both but not asked to fail - just report.
214                 false
215                 ;;
216         t,,good)
217                 # have bad but not good.  we could bisect although
218                 # this is less optimum.
219                 echo >&2 'Warning: bisecting only with a bad commit.'
220                 if test -t 0
221                 then
222                         printf >&2 'Are you sure [Y/n]? '
223                         read yesno
224                         case "$yesno" in [Nn]*) exit 1 ;; esac
225                 fi
226                 : bisect without good...
227                 ;;
228         *)
229                 THEN=''
230                 test -s "$GIT_DIR/BISECT_START" || {
231                         echo >&2 'You need to start by "git bisect start".'
232                         THEN='then '
233                 }
234                 echo >&2 'You '$THEN'need to give me at least one good' \
235                         'and one bad revisions.'
236                 echo >&2 '(You can use "git bisect bad" and' \
237                         '"git bisect good" for that.)'
238                 exit 1 ;;
239         esac
240 }
241
242 bisect_auto_next() {
243         bisect_next_check && bisect_next || :
244 }
245
246 filter_skipped() {
247         _eval="$1"
248         _skip="$2"
249
250         if [ -z "$_skip" ]; then
251                 eval "$_eval"
252                 return
253         fi
254
255         # Let's parse the output of:
256         # "git rev-list --bisect-vars --bisect-all ..."
257         eval "$_eval" | while read hash line
258         do
259                 case "$VARS,$FOUND,$TRIED,$hash" in
260                         # We display some vars.
261                         1,*,*,*) echo "$hash $line" ;;
262
263                         # Split line.
264                         ,*,*,---*) ;;
265
266                         # We had nothing to search.
267                         ,,,bisect_rev*)
268                                 echo "bisect_rev="
269                                 VARS=1
270                                 ;;
271
272                         # We did not find a good bisect rev.
273                         # This should happen only if the "bad"
274                         # commit is also a "skip" commit.
275                         ,,*,bisect_rev*)
276                                 echo "bisect_rev=$TRIED"
277                                 VARS=1
278                                 ;;
279
280                         # We are searching.
281                         ,,*,*)
282                                 TRIED="${TRIED:+$TRIED|}$hash"
283                                 case "$_skip" in
284                                 *$hash*) ;;
285                                 *)
286                                         echo "bisect_rev=$hash"
287                                         echo "bisect_tried=\"$TRIED\""
288                                         FOUND=1
289                                         ;;
290                                 esac
291                                 ;;
292
293                         # We have already found a rev to be tested.
294                         ,1,*,bisect_rev*) VARS=1 ;;
295                         ,1,*,*) ;;
296
297                         # ???
298                         *) die "filter_skipped error " \
299                             "VARS: '$VARS' " \
300                             "FOUND: '$FOUND' " \
301                             "TRIED: '$TRIED' " \
302                             "hash: '$hash' " \
303                             "line: '$line'"
304                         ;;
305                 esac
306         done
307 }
308
309 exit_if_skipped_commits () {
310         _tried=$1
311         if expr "$_tried" : ".*[|].*" > /dev/null ; then
312                 echo "There are only 'skip'ped commit left to test."
313                 echo "The first bad commit could be any of:"
314                 echo "$_tried" | tr '[|]' '[\012]'
315                 echo "We cannot bisect more!"
316                 exit 2
317         fi
318 }
319
320 bisect_checkout() {
321         _rev="$1"
322         _msg="$2"
323         echo "Bisecting: $_msg"
324         git checkout -q "$_rev" || exit
325         git show-branch "$_rev"
326 }
327
328 is_among() {
329         _rev="$1"
330         _list="$2"
331         case "$_list" in *$_rev*) return 0 ;; esac
332         return 1
333 }
334
335 is_testing_merge_base() {
336         grep "^testing $1$" "$GIT_DIR/BISECT_MERGE_BASES" >/dev/null 2>&1
337 }
338
339 mark_testing_merge_base() {
340         echo "testing $1" >> "$GIT_DIR/BISECT_MERGE_BASES"
341 }
342
343 handle_bad_merge_base() {
344         _badmb="$1"
345         _good="$2"
346         if is_testing_merge_base "$_badmb"; then
347                 cat >&2 <<EOF
348 The merge base $_badmb is bad.
349 This means the bug has been fixed between $_badmb and [$_good].
350 EOF
351                 exit 3
352         else
353                 cat >&2 <<EOF
354 Some good revs are not ancestor of the bad rev.
355 git bisect cannot work properly in this case.
356 Maybe you mistake good and bad revs?
357 EOF
358                 exit 1
359         fi
360 }
361
362 handle_skipped_merge_base() {
363         _mb="$1"
364         _bad="$2"
365         _good="$3"
366         cat >&2 <<EOF
367 Warning: the merge base between $_bad and [$_good] must be skipped.
368 So we cannot be sure the first bad commit is between $_mb and $_bad.
369 We continue anyway.
370 EOF
371 }
372
373 check_merge_bases() {
374         _bad="$1"
375         _good="$2"
376         _skip="$3"
377         for _mb in $(git merge-base --all $_bad $_good)
378         do
379                 if is_among "$_mb" "$_good"; then
380                         continue
381                 elif test "$_mb" = "$_bad"; then
382                         handle_bad_merge_base "$_bad" "$_good"
383                 elif is_among "$_mb" "$_skip"; then
384                         handle_skipped_merge_base "$_mb" "$_bad" "$_good"
385                 else
386                         mark_testing_merge_base "$_mb"
387                         bisect_checkout "$_mb" "a merge base must be tested"
388                         checkout_done=1
389                         return
390                 fi
391         done
392 }
393
394 check_good_are_ancestors_of_bad() {
395         _bad="$1"
396         _good=$(echo $2 | sed -e 's/\^//g')
397         _skip="$3"
398
399         # Bisecting with no good rev is ok
400         test -z "$_good" && return
401
402         _side=$(git rev-list $_good ^$_bad)
403         if test -n "$_side"; then
404                 check_merge_bases "$_bad" "$_good" "$_skip"
405         fi
406 }
407
408 bisect_next() {
409         case "$#" in 0) ;; *) usage ;; esac
410         bisect_autostart
411         bisect_next_check good
412
413         # Get bad, good and skipped revs
414         bad=$(git rev-parse --verify refs/bisect/bad) &&
415         good=$(git for-each-ref --format='^%(objectname)' \
416                 "refs/bisect/good-*" | tr '\012' ' ') &&
417         skip=$(git for-each-ref --format='%(objectname)' \
418                 "refs/bisect/skip-*" | tr '\012' ' ') &&
419
420         # Maybe some merge bases must be tested first
421         check_good_are_ancestors_of_bad "$bad" "$good" "$skip" || exit
422         test "$checkout_done" -eq "1" && checkout_done='' && return
423
424         # Get bisection information
425         BISECT_OPT=''
426         test -n "$skip" && BISECT_OPT='--bisect-all'
427         eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
428         eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
429         eval=$(filter_skipped "$eval" "$skip") &&
430         eval "$eval" || exit
431
432         if [ -z "$bisect_rev" ]; then
433                 echo "$bad was both good and bad"
434                 exit 1
435         fi
436         if [ "$bisect_rev" = "$bad" ]; then
437                 exit_if_skipped_commits "$bisect_tried"
438                 echo "$bisect_rev is first bad commit"
439                 git diff-tree --pretty $bisect_rev
440                 exit 0
441         fi
442
443         # We should exit here only if the "bad"
444         # commit is also a "skip" commit (see above).
445         exit_if_skipped_commits "$bisect_rev"
446
447         bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
448 }
449
450 bisect_visualize() {
451         bisect_next_check fail
452
453         if test $# = 0
454         then
455                 case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
456                 '')     set git log ;;
457                 set*)   set gitk ;;
458                 esac
459         else
460                 case "$1" in
461                 git*|tig) ;;
462                 -*)     set git log "$@" ;;
463                 *)      set git "$@" ;;
464                 esac
465         fi
466
467         not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
468         eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
469 }
470
471 bisect_reset() {
472         test -s "$GIT_DIR/BISECT_START" || {
473                 echo "We are not bisecting."
474                 return
475         }
476         case "$#" in
477         0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
478         1) git show-ref --verify --quiet -- "refs/heads/$1" ||
479                die "$1 does not seem to be a valid branch"
480            branch="$1" ;;
481         *)
482             usage ;;
483         esac
484         git checkout "$branch" && bisect_clean_state
485 }
486
487 bisect_clean_state() {
488         # There may be some refs packed during bisection.
489         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
490         while read ref hash
491         do
492                 git update-ref -d $ref $hash || exit
493         done
494         rm -f "$GIT_DIR/BISECT_MERGE_BASES" &&
495         rm -f "$GIT_DIR/BISECT_LOG" &&
496         rm -f "$GIT_DIR/BISECT_NAMES" &&
497         rm -f "$GIT_DIR/BISECT_RUN" &&
498         # Cleanup head-name if it got left by an old version of git-bisect
499         rm -f "$GIT_DIR/head-name" &&
500
501         rm -f "$GIT_DIR/BISECT_START"
502 }
503
504 bisect_replay () {
505         test -r "$1" || die "cannot read $1 for replaying"
506         bisect_reset
507         while read git bisect command rev
508         do
509                 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
510                 if test "$git" = "git-bisect"; then
511                         rev="$command"
512                         command="$bisect"
513                 fi
514                 case "$command" in
515                 start)
516                         cmd="bisect_start $rev"
517                         eval "$cmd" ;;
518                 good|bad|skip)
519                         bisect_write "$command" "$rev" ;;
520                 *)
521                         die "?? what are you talking about?" ;;
522                 esac
523         done <"$1"
524         bisect_auto_next
525 }
526
527 bisect_run () {
528     bisect_next_check fail
529
530     while true
531     do
532       echo "running $@"
533       "$@"
534       res=$?
535
536       # Check for really bad run error.
537       if [ $res -lt 0 -o $res -ge 128 ]; then
538           echo >&2 "bisect run failed:"
539           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
540           exit $res
541       fi
542
543       # Find current state depending on run success or failure.
544       # A special exit code of 125 means cannot test.
545       if [ $res -eq 125 ]; then
546           state='skip'
547       elif [ $res -gt 0 ]; then
548           state='bad'
549       else
550           state='good'
551       fi
552
553       # We have to use a subshell because "bisect_state" can exit.
554       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
555       res=$?
556
557       cat "$GIT_DIR/BISECT_RUN"
558
559       if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
560                 > /dev/null; then
561           echo >&2 "bisect run cannot continue any more"
562           exit $res
563       fi
564
565       if [ $res -ne 0 ]; then
566           echo >&2 "bisect run failed:"
567           echo >&2 "'bisect_state $state' exited with error code $res"
568           exit $res
569       fi
570
571       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
572           echo "bisect run success"
573           exit 0;
574       fi
575
576     done
577 }
578
579
580 case "$#" in
581 0)
582     usage ;;
583 *)
584     cmd="$1"
585     shift
586     case "$cmd" in
587     help)
588         git bisect -h ;;
589     start)
590         bisect_start "$@" ;;
591     bad|good|skip)
592         bisect_state "$cmd" "$@" ;;
593     next)
594         # Not sure we want "next" at the UI level anymore.
595         bisect_next "$@" ;;
596     visualize|view)
597         bisect_visualize "$@" ;;
598     reset)
599         bisect_reset "$@" ;;
600     replay)
601         bisect_replay "$@" ;;
602     log)
603         cat "$GIT_DIR/BISECT_LOG" ;;
604     run)
605         bisect_run "$@" ;;
606     *)
607         usage ;;
608     esac
609 esac