Bisect run: "skip" current commit if script exit code is 125.
[git] / git-bisect.sh
1 #!/bin/sh
2
3 USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
4 LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
5         reset bisect state and start bisection.
6 git bisect bad [<rev>]
7         mark <rev> a known-bad revision.
8 git bisect good [<rev>...]
9         mark <rev>... known-good revisions.
10 git bisect next
11         find next bisection to test and check it out.
12 git bisect reset [<branch>]
13         finish bisection search and go back to branch.
14 git bisect visualize
15         show bisect status in gitk.
16 git bisect replay <logfile>
17         replay bisection log.
18 git bisect log
19         show bisect log.
20 git bisect skip [<rev>...]
21         mark <rev>... untestable revisions.
22 git bisect run <cmd>...
23         use <cmd>... to automatically bisect.'
24
25 . git-sh-setup
26 require_work_tree
27
28 sq() {
29         @@PERL@@ -e '
30                 for (@ARGV) {
31                         s/'\''/'\'\\\\\'\''/g;
32                         print " '\''$_'\''";
33                 }
34                 print "\n";
35         ' "$@"
36 }
37
38 bisect_autostart() {
39         test -d "$GIT_DIR/refs/bisect" || {
40                 echo >&2 'You need to start by "git bisect start"'
41                 if test -t 0
42                 then
43                         echo >&2 -n 'Do you want me to do it for you [Y/n]? '
44                         read yesno
45                         case "$yesno" in
46                         [Nn]*)
47                                 exit ;;
48                         esac
49                         bisect_start
50                 else
51                         exit 1
52                 fi
53         }
54 }
55
56 bisect_start() {
57         #
58         # Verify HEAD. If we were bisecting before this, reset to the
59         # top-of-line master first!
60         #
61         head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) ||
62         die "Bad HEAD - I need a symbolic ref"
63         case "$head" in
64         refs/heads/bisect)
65                 if [ -s "$GIT_DIR/head-name" ]; then
66                     branch=`cat "$GIT_DIR/head-name"`
67                 else
68                     branch=master
69                 fi
70                 git checkout $branch || exit
71                 ;;
72         refs/heads/*)
73                 [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
74                 echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
75                 ;;
76         *)
77                 die "Bad HEAD - strange symbolic ref"
78                 ;;
79         esac
80
81         #
82         # Get rid of any old bisect state
83         #
84         bisect_clean_state
85         mkdir "$GIT_DIR/refs/bisect"
86
87         #
88         # Check for one bad and then some good revisions.
89         #
90         has_double_dash=0
91         for arg; do
92             case "$arg" in --) has_double_dash=1; break ;; esac
93         done
94         orig_args=$(sq "$@")
95         bad_seen=0
96         while [ $# -gt 0 ]; do
97             arg="$1"
98             case "$arg" in
99             --)
100                 shift
101                 break
102                 ;;
103             *)
104                 rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
105                     test $has_double_dash -eq 1 &&
106                         die "'$arg' does not appear to be a valid revision"
107                     break
108                 }
109                 case $bad_seen in
110                 0) state='bad' ; bad_seen=1 ;;
111                 *) state='good' ;;
112                 esac
113                 bisect_write "$state" "$rev" 'nolog'
114                 shift
115                 ;;
116             esac
117         done
118
119         sq "$@" >"$GIT_DIR/BISECT_NAMES"
120         echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
121         bisect_auto_next
122 }
123
124 bisect_write() {
125         state="$1"
126         rev="$2"
127         nolog="$3"
128         case "$state" in
129                 bad)            tag="$state" ;;
130                 good|skip)      tag="$state"-"$rev" ;;
131                 *)              die "Bad bisect_write argument: $state" ;;
132         esac
133         echo "$rev" >"$GIT_DIR/refs/bisect/$tag"
134         echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
135         test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
136 }
137
138 bisect_state() {
139         bisect_autostart
140         state=$1
141         case "$#,$state" in
142         0,*)
143                 die "Please call 'bisect_state' with at least one argument." ;;
144         1,bad|1,good|1,skip)
145                 rev=$(git rev-parse --verify HEAD) ||
146                         die "Bad rev input: HEAD"
147                 bisect_write "$state" "$rev" ;;
148         2,bad)
149                 rev=$(git rev-parse --verify "$2^{commit}") ||
150                         die "Bad rev input: $2"
151                 bisect_write "$state" "$rev" ;;
152         *,good|*,skip)
153                 shift
154                 revs=$(git rev-parse --revs-only --no-flags "$@") &&
155                         test '' != "$revs" || die "Bad rev input: $@"
156                 for rev in $revs
157                 do
158                         rev=$(git rev-parse --verify "$rev^{commit}") ||
159                                 die "Bad rev commit: $rev^{commit}"
160                         bisect_write "$state" "$rev"
161                 done ;;
162         *)
163                 usage ;;
164         esac
165         bisect_auto_next
166 }
167
168 bisect_next_check() {
169         missing_good= missing_bad=
170         git show-ref -q --verify refs/bisect/bad || missing_bad=t
171         test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
172
173         case "$missing_good,$missing_bad,$1" in
174         ,,*)
175                 : have both good and bad - ok
176                 ;;
177         *,)
178                 # do not have both but not asked to fail - just report.
179                 false
180                 ;;
181         t,,good)
182                 # have bad but not good.  we could bisect although
183                 # this is less optimum.
184                 echo >&2 'Warning: bisecting only with a bad commit.'
185                 if test -t 0
186                 then
187                         printf >&2 'Are you sure [Y/n]? '
188                         case "$(read yesno)" in [Nn]*) exit 1 ;; esac
189                 fi
190                 : bisect without good...
191                 ;;
192         *)
193                 THEN=''
194                 test -d "$GIT_DIR/refs/bisect" || {
195                         echo >&2 'You need to start by "git bisect start".'
196                         THEN='then '
197                 }
198                 echo >&2 'You '$THEN'need to give me at least one good' \
199                         'and one bad revisions.'
200                 echo >&2 '(You can use "git bisect bad" and' \
201                         '"git bisect good" for that.)'
202                 exit 1 ;;
203         esac
204 }
205
206 bisect_auto_next() {
207         bisect_next_check && bisect_next || :
208 }
209
210 filter_skipped() {
211         _eval="$1"
212         _skip="$2"
213
214         if [ -z "$_skip" ]; then
215                 eval $_eval
216                 return
217         fi
218
219         # Let's parse the output of:
220         # "git rev-list --bisect-vars --bisect-all ..."
221         eval $_eval | while read hash line
222         do
223                 case "$VARS,$FOUND,$TRIED,$hash" in
224                         # We display some vars.
225                         1,*,*,*) echo "$hash $line" ;;
226
227                         # Split line.
228                         ,*,*,---*) ;;
229
230                         # We had nothing to search.
231                         ,,,bisect_rev*)
232                                 echo "bisect_rev="
233                                 VARS=1
234                                 ;;
235
236                         # We did not find a good bisect rev.
237                         # This should happen only if the "bad"
238                         # commit is also a "skip" commit.
239                         ,,*,bisect_rev*)
240                                 echo "bisect_rev=$TRIED"
241                                 VARS=1
242                                 ;;
243
244                         # We are searching.
245                         ,,*,*)
246                                 TRIED="${TRIED:+$TRIED|}$hash"
247                                 case "$_skip" in
248                                 *$hash*) ;;
249                                 *)
250                                         echo "bisect_rev=$hash"
251                                         echo "bisect_tried=\"$TRIED\""
252                                         FOUND=1
253                                         ;;
254                                 esac
255                                 ;;
256
257                         # We have already found a rev to be tested.
258                         ,1,*,bisect_rev*) VARS=1 ;;
259                         ,1,*,*) ;;
260
261                         # ???
262                         *) die "filter_skipped error " \
263                             "VARS: '$VARS' " \
264                             "FOUND: '$FOUND' " \
265                             "TRIED: '$TRIED' " \
266                             "hash: '$hash' " \
267                             "line: '$line'"
268                         ;;
269                 esac
270         done
271 }
272
273 exit_if_skipped_commits () {
274         _tried=$1
275         if expr "$_tried" : ".*[|].*" > /dev/null ; then
276                 echo "There are only 'skip'ped commit left to test."
277                 echo "The first bad commit could be any of:"
278                 echo "$_tried" | sed -e 's/[|]/\n/g'
279                 echo "We cannot bisect more!"
280                 exit 2
281         fi
282 }
283
284 bisect_next() {
285         case "$#" in 0) ;; *) usage ;; esac
286         bisect_autostart
287         bisect_next_check good
288
289         skip=$(git for-each-ref --format='%(objectname)' \
290                 "refs/bisect/skip-*" | tr '[\012]' ' ') || exit
291
292         BISECT_OPT=''
293         test -n "$skip" && BISECT_OPT='--bisect-all'
294
295         bad=$(git rev-parse --verify refs/bisect/bad) &&
296         good=$(git for-each-ref --format='^%(objectname)' \
297                 "refs/bisect/good-*" | tr '[\012]' ' ') &&
298         eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
299         eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
300         eval=$(filter_skipped "$eval" "$skip") &&
301         eval "$eval" || exit
302
303         if [ -z "$bisect_rev" ]; then
304                 echo "$bad was both good and bad"
305                 exit 1
306         fi
307         if [ "$bisect_rev" = "$bad" ]; then
308                 exit_if_skipped_commits "$bisect_tried"
309                 echo "$bisect_rev is first bad commit"
310                 git diff-tree --pretty $bisect_rev
311                 exit 0
312         fi
313
314         # We should exit here only if the "bad"
315         # commit is also a "skip" commit (see above).
316         exit_if_skipped_commits "$bisect_rev"
317
318         echo "Bisecting: $bisect_nr revisions left to test after this"
319         echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
320         git checkout -q new-bisect || exit
321         mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
322         GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect
323         git show-branch "$bisect_rev"
324 }
325
326 bisect_visualize() {
327         bisect_next_check fail
328         not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
329         eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
330 }
331
332 bisect_reset() {
333         case "$#" in
334         0) if [ -s "$GIT_DIR/head-name" ]; then
335                branch=`cat "$GIT_DIR/head-name"`
336            else
337                branch=master
338            fi ;;
339         1) git show-ref --verify --quiet -- "refs/heads/$1" ||
340                die "$1 does not seem to be a valid branch"
341            branch="$1" ;;
342         *)
343             usage ;;
344         esac
345         if git checkout "$branch"; then
346                 rm -f "$GIT_DIR/head-name"
347                 bisect_clean_state
348         fi
349 }
350
351 bisect_clean_state() {
352         rm -fr "$GIT_DIR/refs/bisect"
353         rm -f "$GIT_DIR/refs/heads/bisect"
354         rm -f "$GIT_DIR/BISECT_LOG"
355         rm -f "$GIT_DIR/BISECT_NAMES"
356         rm -f "$GIT_DIR/BISECT_RUN"
357 }
358
359 bisect_replay () {
360         test -r "$1" || die "cannot read $1 for replaying"
361         bisect_reset
362         while read bisect command rev
363         do
364                 test "$bisect" = "git-bisect" || continue
365                 case "$command" in
366                 start)
367                         cmd="bisect_start $rev"
368                         eval "$cmd" ;;
369                 good|bad|skip)
370                         bisect_write "$command" "$rev" ;;
371                 *)
372                         die "?? what are you talking about?" ;;
373                 esac
374         done <"$1"
375         bisect_auto_next
376 }
377
378 bisect_run () {
379     bisect_next_check fail
380
381     while true
382     do
383       echo "running $@"
384       "$@"
385       res=$?
386
387       # Check for really bad run error.
388       if [ $res -lt 0 -o $res -ge 128 ]; then
389           echo >&2 "bisect run failed:"
390           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
391           exit $res
392       fi
393
394       # Find current state depending on run success or failure.
395       # A special exit code of 125 means cannot test.
396       if [ $res -eq 125 ]; then
397           state='skip'
398       elif [ $res -gt 0 ]; then
399           state='bad'
400       else
401           state='good'
402       fi
403
404       # We have to use a subshell because "bisect_state" can exit.
405       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
406       res=$?
407
408       cat "$GIT_DIR/BISECT_RUN"
409
410       if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
411                 > /dev/null; then
412           echo >&2 "bisect run cannot continue any more"
413           exit $res
414       fi
415
416       if [ $res -ne 0 ]; then
417           echo >&2 "bisect run failed:"
418           echo >&2 "'bisect_state $state' exited with error code $res"
419           exit $res
420       fi
421
422       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
423           echo "bisect run success"
424           exit 0;
425       fi
426
427     done
428 }
429
430
431 case "$#" in
432 0)
433     usage ;;
434 *)
435     cmd="$1"
436     shift
437     case "$cmd" in
438     start)
439         bisect_start "$@" ;;
440     bad|good|skip)
441         bisect_state "$cmd" "$@" ;;
442     next)
443         # Not sure we want "next" at the UI level anymore.
444         bisect_next "$@" ;;
445     visualize)
446         bisect_visualize "$@" ;;
447     reset)
448         bisect_reset "$@" ;;
449     replay)
450         bisect_replay "$@" ;;
451     log)
452         cat "$GIT_DIR/BISECT_LOG" ;;
453     run)
454         bisect_run "$@" ;;
455     *)
456         usage ;;
457     esac
458 esac