Code

Use {web,instaweb,help}.browser config options.
[git.git] / git-bisect.sh
1 #!/bin/sh
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.'
25 OPTIONS_SPEC=
26 . git-sh-setup
27 require_work_tree
29 sq() {
30         @@PERL@@ -e '
31                 for (@ARGV) {
32                         s/'\''/'\'\\\\\'\''/g;
33                         print " '\''$_'\''";
34                 }
35                 print "\n";
36         ' "$@"
37 }
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 }
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
82         #
83         # Get rid of any old bisect state
84         #
85         bisect_clean_state
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
119         sq "$@" >"$GIT_DIR/BISECT_NAMES"
120         echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
121         bisect_auto_next
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"
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
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
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
206 bisect_auto_next() {
207         bisect_next_check && bisect_next || :
210 filter_skipped() {
211         _eval="$1"
212         _skip="$2"
214         if [ -z "$_skip" ]; then
215                 eval $_eval
216                 return
217         fi
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" ;;
227                         # Split line.
228                         ,*,*,---*) ;;
230                         # We had nothing to search.
231                         ,,,bisect_rev*)
232                                 echo "bisect_rev="
233                                 VARS=1
234                                 ;;
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                                 ;;
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                                 ;;
257                         # We have already found a rev to be tested.
258                         ,1,*,bisect_rev*) VARS=1 ;;
259                         ,1,*,*) ;;
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
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
284 bisect_next() {
285         case "$#" in 0) ;; *) usage ;; esac
286         bisect_autostart
287         bisect_next_check good
289         skip=$(git for-each-ref --format='%(objectname)' \
290                 "refs/bisect/skip-*" | tr '[\012]' ' ') || exit
292         BISECT_OPT=''
293         test -n "$skip" && BISECT_OPT='--bisect-all'
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
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
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"
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"
325 bisect_visualize() {
326         bisect_next_check fail
327         not=$(git for-each-ref --format='%(refname)' "refs/bisect/good-*")
328         eval gitk refs/bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
331 bisect_reset() {
332         test -f "$GIT_DIR/BISECT_NAMES" || {
333                 echo "We are not bisecting."
334                 return
335         }
336         case "$#" in
337         0) if [ -s "$GIT_DIR/head-name" ]; then
338                branch=`cat "$GIT_DIR/head-name"`
339            else
340                branch=master
341            fi ;;
342         1) git show-ref --verify --quiet -- "refs/heads/$1" ||
343                die "$1 does not seem to be a valid branch"
344            branch="$1" ;;
345         *)
346             usage ;;
347         esac
348         if git checkout "$branch"; then
349                 rm -f "$GIT_DIR/head-name"
350                 bisect_clean_state
351         fi
354 bisect_clean_state() {
355         # There may be some refs packed during bisection.
356         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* refs/heads/bisect |
357         while read ref hash
358         do
359                 git update-ref -d $ref $hash
360         done
361         rm -f "$GIT_DIR/BISECT_LOG"
362         rm -f "$GIT_DIR/BISECT_NAMES"
363         rm -f "$GIT_DIR/BISECT_RUN"
366 bisect_replay () {
367         test -r "$1" || die "cannot read $1 for replaying"
368         bisect_reset
369         while read bisect command rev
370         do
371                 test "$bisect" = "git-bisect" || continue
372                 case "$command" in
373                 start)
374                         cmd="bisect_start $rev"
375                         eval "$cmd" ;;
376                 good|bad|skip)
377                         bisect_write "$command" "$rev" ;;
378                 *)
379                         die "?? what are you talking about?" ;;
380                 esac
381         done <"$1"
382         bisect_auto_next
385 bisect_run () {
386     bisect_next_check fail
388     while true
389     do
390       echo "running $@"
391       "$@"
392       res=$?
394       # Check for really bad run error.
395       if [ $res -lt 0 -o $res -ge 128 ]; then
396           echo >&2 "bisect run failed:"
397           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
398           exit $res
399       fi
401       # Find current state depending on run success or failure.
402       # A special exit code of 125 means cannot test.
403       if [ $res -eq 125 ]; then
404           state='skip'
405       elif [ $res -gt 0 ]; then
406           state='bad'
407       else
408           state='good'
409       fi
411       # We have to use a subshell because "bisect_state" can exit.
412       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
413       res=$?
415       cat "$GIT_DIR/BISECT_RUN"
417       if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
418                 > /dev/null; then
419           echo >&2 "bisect run cannot continue any more"
420           exit $res
421       fi
423       if [ $res -ne 0 ]; then
424           echo >&2 "bisect run failed:"
425           echo >&2 "'bisect_state $state' exited with error code $res"
426           exit $res
427       fi
429       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
430           echo "bisect run success"
431           exit 0;
432       fi
434     done
438 case "$#" in
439 0)
440     usage ;;
441 *)
442     cmd="$1"
443     shift
444     case "$cmd" in
445     start)
446         bisect_start "$@" ;;
447     bad|good|skip)
448         bisect_state "$cmd" "$@" ;;
449     next)
450         # Not sure we want "next" at the UI level anymore.
451         bisect_next "$@" ;;
452     visualize)
453         bisect_visualize "$@" ;;
454     reset)
455         bisect_reset "$@" ;;
456     replay)
457         bisect_replay "$@" ;;
458     log)
459         cat "$GIT_DIR/BISECT_LOG" ;;
460     run)
461         bisect_run "$@" ;;
462     *)
463         usage ;;
464     esac
465 esac