Merge branch 'jc/spht'
[git] / git-bisect.sh
1 #!/bin/sh
2
3 USAGE='[start|bad|good|skip|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 skip [<rev>...]
11         mark <rev>... untestable revisions.
12 git bisect next
13         find next bisection to test and check it out.
14 git bisect reset [<branch>]
15         finish bisection search and go back to branch.
16 git bisect visualize
17         show bisect status in gitk.
18 git bisect replay <logfile>
19         replay bisection log.
20 git bisect log
21         show bisect log.
22 git bisect run <cmd>...
23         use <cmd>... to automatically bisect.'
24
25 OPTIONS_SPEC=
26 . git-sh-setup
27 require_work_tree
28
29 sq() {
30         @@PERL@@ -e '
31                 for (@ARGV) {
32                         s/'\''/'\'\\\\\'\''/g;
33                         print " '\''$_'\''";
34                 }
35                 print "\n";
36         ' "$@"
37 }
38
39 bisect_autostart() {
40         test -f "$GIT_DIR/BISECT_NAMES" || {
41                 echo >&2 'You need to start by "git bisect start"'
42                 if test -t 0
43                 then
44                         echo >&2 -n 'Do you want me to do it for you [Y/n]? '
45                         read yesno
46                         case "$yesno" in
47                         [Nn]*)
48                                 exit ;;
49                         esac
50                         bisect_start
51                 else
52                         exit 1
53                 fi
54         }
55 }
56
57 bisect_start() {
58         #
59         # Verify HEAD. If we were bisecting before this, reset to the
60         # top-of-line master first!
61         #
62         head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) ||
63         die "Bad HEAD - I need a symbolic ref"
64         case "$head" in
65         refs/heads/bisect)
66                 if [ -s "$GIT_DIR/head-name" ]; then
67                     branch=`cat "$GIT_DIR/head-name"`
68                 else
69                     branch=master
70                 fi
71                 git checkout $branch || exit
72                 ;;
73         refs/heads/*)
74                 [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
75                 echo "${head#refs/heads/}" >"$GIT_DIR/head-name"
76                 ;;
77         *)
78                 die "Bad HEAD - strange symbolic ref"
79                 ;;
80         esac
81
82         #
83         # Get rid of any old bisect state
84         #
85         bisect_clean_state
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         git update-ref "refs/bisect/$tag" "$rev"
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 -f "$GIT_DIR/BISECT_NAMES" || {
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" | tr '[|]' '[\012]'
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         git branch -f new-bisect "$bisect_rev"
320         git checkout -q new-bisect || exit
321         git branch -M new-bisect bisect
322         git show-branch "$bisect_rev"
323 }
324
325 bisect_visualize() {
326         bisect_next_check fail
327
328         if test $# = 0
329         then
330                 case "${DISPLAY+set}" in
331                 '')     set git log ;;
332                 set)    set gitk ;;
333                 esac
334         else
335                 case "$1" in
336                 git*|tig) ;;
337                 -*)     set git log "$@" ;;
338                 *)      set git "$@" ;;
339                 esac
340         fi
341
342         not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
343         eval '"$@"' refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
344 }
345
346 bisect_reset() {
347         test -f "$GIT_DIR/BISECT_NAMES" || {
348                 echo "We are not bisecting."
349                 return
350         }
351         case "$#" in
352         0) if [ -s "$GIT_DIR/head-name" ]; then
353                branch=`cat "$GIT_DIR/head-name"`
354            else
355                branch=master
356            fi ;;
357         1) git show-ref --verify --quiet -- "refs/heads/$1" ||
358                die "$1 does not seem to be a valid branch"
359            branch="$1" ;;
360         *)
361             usage ;;
362         esac
363         if git checkout "$branch"; then
364                 rm -f "$GIT_DIR/head-name"
365                 bisect_clean_state
366         fi
367 }
368
369 bisect_clean_state() {
370         # There may be some refs packed during bisection.
371         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
372         while read ref hash
373         do
374                 git update-ref -d $ref $hash
375         done
376         rm -f "$GIT_DIR/BISECT_LOG"
377         rm -f "$GIT_DIR/BISECT_NAMES"
378         rm -f "$GIT_DIR/BISECT_RUN"
379 }
380
381 bisect_replay () {
382         test -r "$1" || die "cannot read $1 for replaying"
383         bisect_reset
384         while read bisect command rev
385         do
386                 test "$bisect" = "git-bisect" || continue
387                 case "$command" in
388                 start)
389                         cmd="bisect_start $rev"
390                         eval "$cmd" ;;
391                 good|bad|skip)
392                         bisect_write "$command" "$rev" ;;
393                 *)
394                         die "?? what are you talking about?" ;;
395                 esac
396         done <"$1"
397         bisect_auto_next
398 }
399
400 bisect_run () {
401     bisect_next_check fail
402
403     while true
404     do
405       echo "running $@"
406       "$@"
407       res=$?
408
409       # Check for really bad run error.
410       if [ $res -lt 0 -o $res -ge 128 ]; then
411           echo >&2 "bisect run failed:"
412           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
413           exit $res
414       fi
415
416       # Find current state depending on run success or failure.
417       # A special exit code of 125 means cannot test.
418       if [ $res -eq 125 ]; then
419           state='skip'
420       elif [ $res -gt 0 ]; then
421           state='bad'
422       else
423           state='good'
424       fi
425
426       # We have to use a subshell because "bisect_state" can exit.
427       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
428       res=$?
429
430       cat "$GIT_DIR/BISECT_RUN"
431
432       if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
433                 > /dev/null; then
434           echo >&2 "bisect run cannot continue any more"
435           exit $res
436       fi
437
438       if [ $res -ne 0 ]; then
439           echo >&2 "bisect run failed:"
440           echo >&2 "'bisect_state $state' exited with error code $res"
441           exit $res
442       fi
443
444       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
445           echo "bisect run success"
446           exit 0;
447       fi
448
449     done
450 }
451
452
453 case "$#" in
454 0)
455     usage ;;
456 *)
457     cmd="$1"
458     shift
459     case "$cmd" in
460     start)
461         bisect_start "$@" ;;
462     bad|good|skip)
463         bisect_state "$cmd" "$@" ;;
464     next)
465         # Not sure we want "next" at the UI level anymore.
466         bisect_next "$@" ;;
467     visualize|view)
468         bisect_visualize "$@" ;;
469     reset)
470         bisect_reset "$@" ;;
471     replay)
472         bisect_replay "$@" ;;
473     log)
474         cat "$GIT_DIR/BISECT_LOG" ;;
475     run)
476         bisect_run "$@" ;;
477     *)
478         usage ;;
479     esac
480 esac