bisect: make skipped array functions more generic
[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 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
50                 fi
51         }
52 }
53
54 bisect_start() {
55         #
56         # Verify HEAD.
57         #
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"
61
62         #
63         # Check if we are bisecting.
64         #
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/}"
81                         ;;
82                 *)
83                         die "Bad HEAD - strange symbolic ref"
84                         ;;
85                 esac
86         fi
87
88         #
89         # Get rid of any old bisect state.
90         #
91         bisect_clean_state || exit
92
93         #
94         # Check for one bad and then some good revisions.
95         #
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
106             --)
107                 shift
108                 break
109                 ;;
110             *)
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
115                 }
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
122                 ;;
123             esac
124         done
125
126         #
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".
132         #
133         trap 'bisect_clean_state' 0
134         trap 'exit 255' 1 2 3 15
135
136         #
137         # Write new start state.
138         #
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
143         #
144         # Check if we can proceed to the next bisect state.
145         #
146         bisect_auto_next
147
148         trap '-' 0
149 }
150
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"
163 }
164
165 is_expected_rev() {
166         test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
167         test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
168 }
169
170 mark_expected_rev() {
171         echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
172 }
173
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
180                 fi
181         done
182 }
183
184 bisect_skip() {
185         all=''
186         for arg in "$@"
187         do
188             case "$arg" in
189             *..*)
190                 revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
191             *)
192                 revs=$(git rev-parse --sq-quote "$arg") ;;
193             esac
194             all="$all $revs"
195         done
196         eval bisect_state 'skip' $all
197 }
198
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 "$@"
214                 do
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." ;;
223         *)
224                 usage ;;
225         esac
226         bisect_auto_next
227 }
228
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
233
234         case "$missing_good,$missing_bad,$1" in
235         ,,*)
236                 : have both good and bad - ok
237                 ;;
238         *,)
239                 # do not have both but not asked to fail - just report.
240                 false
241                 ;;
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
251                 fi
252                 : bisect without good...
253                 ;;
254         *)
255                 THEN=''
256                 test -s "$GIT_DIR/BISECT_START" || {
257                         echo >&2 'You need to start by "git bisect start".'
258                         THEN='then '
259                 }
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
266 }
267
268 bisect_auto_next() {
269         bisect_next_check && bisect_next || :
270 }
271
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"
279 }
280
281 is_among() {
282         _rev="$1"
283         _list="$2"
284         case "$_list" in *$_rev*) return 0 ;; esac
285         return 1
286 }
287
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].
295 EOF
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?
302 EOF
303                 exit 1
304         fi
305 }
306
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.
315 EOF
316 }
317
318 #
319 # "check_merge_bases" checks that merge bases are not "bad".
320 #
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.
326 #
327 # In the last case we will return 1, and otherwise 0.
328 #
329 check_merge_bases() {
330         _bad="$1"
331         _good="$2"
332         _skip="$3"
333         for _mb in $(git merge-base --all $_bad $_good)
334         do
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
344                 fi
345         done
346         return 0
347 }
348
349 #
350 # "check_good_are_ancestors_of_bad" checks that all "good" revs are
351 # ancestor of the "bad" rev.
352 #
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.
356 #
357 check_good_are_ancestors_of_bad() {
358         test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
359                 return
360
361         _bad="$1"
362         _good=$(echo $2 | sed -e 's/\^//g')
363         _skip="$3"
364
365         # Bisecting with no good rev is ok
366         test -z "$_good" && return
367
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
372         fi
373
374         : > "$GIT_DIR/BISECT_ANCESTORS_OK"
375
376         return 0
377 }
378
379 bisect_next() {
380         case "$#" in 0) ;; *) usage ;; esac
381         bisect_autostart
382         bisect_next_check good
383
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
390
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
395
396         # Perform bisection computation, display and checkout
397         git bisect--helper --next-exit
398         res=$?
399
400         # Check if we should exit because bisection is finished
401         test $res -eq 10 && exit 0
402
403         # Check for an error in the bisection process
404         test $res -ne 0 && exit $res
405
406         return 0
407 }
408
409 bisect_visualize() {
410         bisect_next_check fail
411
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
424         fi
425
426         not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
427         eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
428 }
429
430 bisect_reset() {
431         test -s "$GIT_DIR/BISECT_START" || {
432                 echo "We are not bisecting."
433                 return
434         }
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" ;;
440         *)
441             usage ;;
442         esac
443         git checkout "$branch" -- && bisect_clean_state
444 }
445
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
450         do
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" &&
460
461         rm -f "$GIT_DIR/BISECT_START"
462 }
463
464 bisect_replay () {
465         test -r "$1" || die "cannot read $1 for replaying"
466         bisect_reset
467         while read git bisect command rev
468         do
469                 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
470                 if test "$git" = "git-bisect"; then
471                         rev="$command"
472                         command="$bisect"
473                 fi
474                 case "$command" in
475                 start)
476                         cmd="bisect_start $rev"
477                         eval "$cmd" ;;
478                 good|bad|skip)
479                         bisect_write "$command" "$rev" ;;
480                 *)
481                         die "?? what are you talking about?" ;;
482                 esac
483         done <"$1"
484         bisect_auto_next
485 }
486
487 bisect_run () {
488     bisect_next_check fail
489
490     while true
491     do
492       echo "running $@"
493       "$@"
494       res=$?
495
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
501       fi
502
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'
511       fi
512
513       # We have to use a subshell because "bisect_state" can exit.
514       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
515       res=$?
516
517       cat "$GIT_DIR/BISECT_RUN"
518
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
523       fi
524
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
529       fi
530
531       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
532           echo "bisect run success"
533           exit 0;
534       fi
535
536     done
537 }
538
539
540 case "$#" in
541 0)
542     usage ;;
543 *)
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 "$@" ;;
568     *)
569         usage ;;
570     esac
571 esac