Bisect: teach "bisect start" to optionally use one bad and many good revs.
[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 run <cmd>...
21         use <cmd>... to automatically bisect.'
22
23 . git-sh-setup
24 require_work_tree
25
26 sq() {
27         @@PERL@@ -e '
28                 for (@ARGV) {
29                         s/'\''/'\'\\\\\'\''/g;
30                         print " '\''$_'\''";
31                 }
32                 print "\n";
33         ' "$@"
34 }
35
36 bisect_autostart() {
37         test -d "$GIT_DIR/refs/bisect" || {
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. If we were bisecting before this, reset to the
57         # top-of-line master first!
58         #
59         head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
60         die "Bad HEAD - I need a symbolic ref"
61         case "$head" in
62         refs/heads/bisect)
63                 if [ -s "$GIT_DIR/head-name" ]; then
64                     branch=`cat "$GIT_DIR/head-name"`
65                 else
66                     branch=master
67                 fi
68                 git checkout $branch || exit
69                 ;;
70         refs/heads/*)
71                 [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
72                 echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
73                 ;;
74         *)
75                 die "Bad HEAD - strange symbolic ref"
76                 ;;
77         esac
78
79         #
80         # Get rid of any old bisect state
81         #
82         bisect_clean_state
83         mkdir "$GIT_DIR/refs/bisect"
84
85         #
86         # Check for one bad and then some good revisions.
87         #
88         has_double_dash=0
89         for arg; do
90             case "$arg" in --) has_double_dash=1; break ;; esac
91         done
92         orig_args=$(sq "$@")
93         bad_seen=0
94         while [ $# -gt 0 ]; do
95             arg="$1"
96             case "$arg" in
97             --)
98                 shift
99                 break
100                 ;;
101             *)
102                 rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
103                     test $has_double_dash -eq 1 &&
104                         die "'$arg' does not appear to be a valid revision"
105                     break
106                 }
107                 if [ $bad_seen -eq 0 ]; then
108                     bad_seen=1
109                     bisect_write_bad "$rev"
110                 else
111                     bisect_write_good "$rev"
112                 fi
113                 shift
114                 ;;
115             esac
116         done
117
118         sq "$@" >"$GIT_DIR/BISECT_NAMES"
119         {
120             printf "git-bisect start"
121             echo "$orig_args"
122         } >>"$GIT_DIR/BISECT_LOG"
123         bisect_auto_next
124 }
125
126 bisect_bad() {
127         bisect_autostart
128         case "$#" in
129         0)
130                 rev=$(git-rev-parse --verify HEAD) ;;
131         1)
132                 rev=$(git-rev-parse --verify "$1^{commit}") ;;
133         *)
134                 usage ;;
135         esac || exit
136         bisect_write_bad "$rev"
137         echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
138         bisect_auto_next
139 }
140
141 bisect_write_bad() {
142         rev="$1"
143         echo "$rev" >"$GIT_DIR/refs/bisect/bad"
144         echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
145 }
146
147 bisect_good() {
148         bisect_autostart
149         case "$#" in
150         0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
151         *)    revs=$(git-rev-parse --revs-only --no-flags "$@") &&
152                 test '' != "$revs" || die "Bad rev input: $@" ;;
153         esac
154         for rev in $revs
155         do
156                 rev=$(git-rev-parse --verify "$rev^{commit}") || exit
157                 bisect_write_good "$rev"
158                 echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
159
160         done
161         bisect_auto_next
162 }
163
164 bisect_write_good() {
165         rev="$1"
166         echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
167         echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
168 }
169
170 bisect_next_check() {
171         next_ok=no
172         test -f "$GIT_DIR/refs/bisect/bad" &&
173         case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
174         refs/bisect/good-\*) ;;
175         *) next_ok=yes ;;
176         esac
177         case "$next_ok,$1" in
178         no,) false ;;
179         no,fail)
180             THEN=''
181             test -d "$GIT_DIR/refs/bisect" || {
182                 echo >&2 'You need to start by "git bisect start".'
183                 THEN='then '
184             }
185             echo >&2 'You '$THEN'need to give me at least one good' \
186                 'and one bad revisions.'
187             echo >&2 '(You can use "git bisect bad" and' \
188                 '"git bisect good" for that.)'
189             exit 1 ;;
190         *)
191             true ;;
192         esac
193 }
194
195 bisect_auto_next() {
196         bisect_next_check && bisect_next || :
197 }
198
199 bisect_next() {
200         case "$#" in 0) ;; *) usage ;; esac
201         bisect_autostart
202         bisect_next_check fail
203         bad=$(git-rev-parse --verify refs/bisect/bad) &&
204         good=$(git-rev-parse --sq --revs-only --not \
205                 $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
206         rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit
207         if [ -z "$rev" ]; then
208             echo "$bad was both good and bad"
209             exit 1
210         fi
211         if [ "$rev" = "$bad" ]; then
212             echo "$rev is first bad commit"
213             git-diff-tree --pretty $rev
214             exit 0
215         fi
216         nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit
217         echo "Bisecting: $nr revisions left to test after this"
218         echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
219         git checkout -q new-bisect || exit
220         mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
221         GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
222         git-show-branch "$rev"
223 }
224
225 bisect_visualize() {
226         bisect_next_check fail
227         not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
228         eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
229 }
230
231 bisect_reset() {
232         case "$#" in
233         0) if [ -s "$GIT_DIR/head-name" ]; then
234                branch=`cat "$GIT_DIR/head-name"`
235            else
236                branch=master
237            fi ;;
238         1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
239                echo >&2 "$1 does not seem to be a valid branch"
240                exit 1
241            }
242            branch="$1" ;;
243         *)
244             usage ;;
245         esac
246         if git checkout "$branch"; then
247                 rm -f "$GIT_DIR/head-name"
248                 bisect_clean_state
249         fi
250 }
251
252 bisect_clean_state() {
253         rm -fr "$GIT_DIR/refs/bisect"
254         rm -f "$GIT_DIR/refs/heads/bisect"
255         rm -f "$GIT_DIR/BISECT_LOG"
256         rm -f "$GIT_DIR/BISECT_NAMES"
257         rm -f "$GIT_DIR/BISECT_RUN"
258 }
259
260 bisect_replay () {
261         test -r "$1" || {
262                 echo >&2 "cannot read $1 for replaying"
263                 exit 1
264         }
265         bisect_reset
266         while read bisect command rev
267         do
268                 test "$bisect" = "git-bisect" || continue
269                 case "$command" in
270                 start)
271                         cmd="bisect_start $rev"
272                         eval "$cmd"
273                         ;;
274                 good)
275                         echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
276                         echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
277                         echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
278                         ;;
279                 bad)
280                         echo "$rev" >"$GIT_DIR/refs/bisect/bad"
281                         echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
282                         echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
283                         ;;
284                 *)
285                         echo >&2 "?? what are you talking about?"
286                         exit 1 ;;
287                 esac
288         done <"$1"
289         bisect_auto_next
290 }
291
292 bisect_run () {
293     bisect_next_check fail
294
295     while true
296     do
297       echo "running $@"
298       "$@"
299       res=$?
300
301       # Check for really bad run error.
302       if [ $res -lt 0 -o $res -ge 128 ]; then
303           echo >&2 "bisect run failed:"
304           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
305           exit $res
306       fi
307
308       # Use "bisect_good" or "bisect_bad"
309       # depending on run success or failure.
310       if [ $res -gt 0 ]; then
311           next_bisect='bisect_bad'
312       else
313           next_bisect='bisect_good'
314       fi
315
316       # We have to use a subshell because bisect_good or
317       # bisect_bad functions can exit.
318       ( $next_bisect > "$GIT_DIR/BISECT_RUN" )
319       res=$?
320
321       cat "$GIT_DIR/BISECT_RUN"
322
323       if [ $res -ne 0 ]; then
324           echo >&2 "bisect run failed:"
325           echo >&2 "$next_bisect exited with error code $res"
326           exit $res
327       fi
328
329       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
330           echo "bisect run success"
331           exit 0;
332       fi
333
334     done
335 }
336
337
338 case "$#" in
339 0)
340     usage ;;
341 *)
342     cmd="$1"
343     shift
344     case "$cmd" in
345     start)
346         bisect_start "$@" ;;
347     bad)
348         bisect_bad "$@" ;;
349     good)
350         bisect_good "$@" ;;
351     next)
352         # Not sure we want "next" at the UI level anymore.
353         bisect_next "$@" ;;
354     visualize)
355         bisect_visualize "$@" ;;
356     reset)
357         bisect_reset "$@" ;;
358     replay)
359         bisect_replay "$@" ;;
360     log)
361         cat "$GIT_DIR/BISECT_LOG" ;;
362     run)
363         bisect_run "$@" ;;
364     *)
365         usage ;;
366     esac
367 esac