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