OSDN Git Service

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