bisect: further style nitpicks
[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 [--no-checkout] [<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>|<range>)...]
13         mark <rev>... untestable revisions.
14 git bisect next
15         find next bisection to test and check it out.
16 git bisect reset [<commit>]
17         finish bisection search and go back to commit.
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 . git-sh-i18n
32 require_work_tree
33
34 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
35 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
36
37 bisect_head()
38 {
39         if test -f "$GIT_DIR/BISECT_HEAD"
40         then
41                 echo BISECT_HEAD
42         else
43                 echo HEAD
44         fi
45 }
46
47 bisect_autostart() {
48         test -s "$GIT_DIR/BISECT_START" || {
49                 (
50                         gettext "You need to start by \"git bisect start\"" &&
51                         echo
52                 ) >&2
53                 if test -t 0
54                 then
55                         # TRANSLATORS: Make sure to include [Y] and [n] in your
56                         # translation. The program will only accept English input
57                         # at this point.
58                         gettext "Do you want me to do it for you [Y/n]? " >&2
59                         read yesno
60                         case "$yesno" in
61                         [Nn]*)
62                                 exit ;;
63                         esac
64                         bisect_start
65                 else
66                         exit 1
67                 fi
68         }
69 }
70
71 bisect_start() {
72         #
73         # Check for one bad and then some good revisions.
74         #
75         has_double_dash=0
76         for arg; do
77                 case "$arg" in --) has_double_dash=1; break ;; esac
78         done
79         orig_args=$(git rev-parse --sq-quote "$@")
80         bad_seen=0
81         eval=''
82         mode=''
83         while [ $# -gt 0 ]; do
84                 arg="$1"
85                 case "$arg" in
86                 --)
87                         shift
88                         break
89                 ;;
90                 --no-checkout)
91                         mode=--no-checkout
92                         shift ;;
93                 --*)
94                         die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
95                 *)
96                         rev=$(git rev-parse -q --verify "$arg^{commit}") || {
97                                 test $has_double_dash -eq 1 &&
98                                 die "$(eval_gettext "'\$arg' does not appear to be a valid revision")"
99                                 break
100                         }
101                         case $bad_seen in
102                         0) state='bad' ; bad_seen=1 ;;
103                         *) state='good' ;;
104                         esac
105                         eval="$eval bisect_write '$state' '$rev' 'nolog' &&"
106                         shift
107                         ;;
108                 esac
109         done
110
111         #
112         # Verify HEAD.
113         #
114         head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) ||
115         head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) ||
116         die "$(gettext "Bad HEAD - I need a HEAD")"
117
118         #
119         # Check if we are bisecting.
120         #
121         start_head=''
122         if test -s "$GIT_DIR/BISECT_START"
123         then
124                 # Reset to the rev from where we started.
125                 start_head=$(cat "$GIT_DIR/BISECT_START")
126                 if test "z$mode" != "z--no-checkout"
127                 then
128                         git checkout "$start_head" --
129                 fi
130         else
131                 # Get rev from where we start.
132                 case "$head" in
133                 refs/heads/*|$_x40)
134                         # This error message should only be triggered by
135                         # cogito usage, and cogito users should understand
136                         # it relates to cg-seek.
137                         [ -s "$GIT_DIR/head-name" ] &&
138                                 die "$(gettext "won't bisect on seeked tree")"
139                         start_head="${head#refs/heads/}"
140                         ;;
141                 *)
142                         die "$(gettext "Bad HEAD - strange symbolic ref")"
143                         ;;
144                 esac
145         fi
146
147         #
148         # Get rid of any old bisect state.
149         #
150         bisect_clean_state || exit
151
152         #
153         # Change state.
154         # In case of mistaken revs or checkout error, or signals received,
155         # "bisect_auto_next" below may exit or misbehave.
156         # We have to trap this to be able to clean up using
157         # "bisect_clean_state".
158         #
159         trap 'bisect_clean_state' 0
160         trap 'exit 255' 1 2 3 15
161
162         #
163         # Write new start state.
164         #
165         echo "$start_head" >"$GIT_DIR/BISECT_START" && {
166                 test "z$mode" != "z--no-checkout" ||
167                 git update-ref --no-deref BISECT_HEAD "$start_head"
168         } &&
169         git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
170         eval "$eval true" &&
171         echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
172         #
173         # Check if we can proceed to the next bisect state.
174         #
175         bisect_auto_next
176
177         trap '-' 0
178 }
179
180 bisect_write() {
181         state="$1"
182         rev="$2"
183         nolog="$3"
184         case "$state" in
185                 bad)            tag="$state" ;;
186                 good|skip)      tag="$state"-"$rev" ;;
187                 *)              die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
188         esac
189         git update-ref "refs/bisect/$tag" "$rev" || exit
190         echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
191         test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
192 }
193
194 is_expected_rev() {
195         test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
196         test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
197 }
198
199 check_expected_revs() {
200         for _rev in "$@"; do
201                 if ! is_expected_rev "$_rev"
202                 then
203                         rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
204                         rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
205                         return
206                 fi
207         done
208 }
209
210 bisect_skip() {
211         all=''
212         for arg in "$@"
213         do
214                 case "$arg" in
215                 *..*)
216                         revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
217                 *)
218                         revs=$(git rev-parse --sq-quote "$arg") ;;
219                 esac
220                 all="$all $revs"
221         done
222         eval bisect_state 'skip' $all
223 }
224
225 bisect_state() {
226         bisect_autostart
227         state=$1
228         case "$#,$state" in
229         0,*)
230                 die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
231         1,bad|1,good|1,skip)
232                 rev=$(git rev-parse --verify $(bisect_head)) ||
233                         die "$(gettext "Bad rev input: $(bisect_head)")"
234                 bisect_write "$state" "$rev"
235                 check_expected_revs "$rev" ;;
236         2,bad|*,good|*,skip)
237                 shift
238                 eval=''
239                 for rev in "$@"
240                 do
241                         sha=$(git rev-parse --verify "$rev^{commit}") ||
242                                 die "$(eval_gettext "Bad rev input: \$rev")"
243                         eval="$eval bisect_write '$state' '$sha'; "
244                 done
245                 eval "$eval"
246                 check_expected_revs "$@" ;;
247         *,bad)
248                 die "$(gettext "'git bisect bad' can take only one argument.")" ;;
249         *)
250                 usage ;;
251         esac
252         bisect_auto_next
253 }
254
255 bisect_next_check() {
256         missing_good= missing_bad=
257         git show-ref -q --verify refs/bisect/bad || missing_bad=t
258         test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
259
260         case "$missing_good,$missing_bad,$1" in
261         ,,*)
262                 : have both good and bad - ok
263                 ;;
264         *,)
265                 # do not have both but not asked to fail - just report.
266                 false
267                 ;;
268         t,,good)
269                 # have bad but not good.  we could bisect although
270                 # this is less optimum.
271                 (
272                         gettext "Warning: bisecting only with a bad commit." &&
273                         echo
274                 ) >&2
275                 if test -t 0
276                 then
277                         # TRANSLATORS: Make sure to include [Y] and [n] in your
278                         # translation. The program will only accept English input
279                         # at this point.
280                         gettext "Are you sure [Y/n]? " >&2
281                         read yesno
282                         case "$yesno" in [Nn]*) exit 1 ;; esac
283                 fi
284                 : bisect without good...
285                 ;;
286         *)
287
288                 if test -s "$GIT_DIR/BISECT_START"
289                 then
290                         (
291                                 gettext "You need to give me at least one good and one bad revisions.
292 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
293                                 echo
294                         ) >&2
295                 else
296                         (
297                                 gettext "You need to start by \"git bisect start\".
298 You then need to give me at least one good and one bad revisions.
299 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
300                                 echo
301                         ) >&2
302                 fi
303                 exit 1 ;;
304         esac
305 }
306
307 bisect_auto_next() {
308         bisect_next_check && bisect_next || :
309 }
310
311 bisect_next() {
312         case "$#" in 0) ;; *) usage ;; esac
313         bisect_autostart
314         bisect_next_check good
315
316         # Perform all bisection computation, display and checkout
317         git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
318         res=$?
319
320         # Check if we should exit because bisection is finished
321         test $res -eq 10 && exit 0
322
323         # Check for an error in the bisection process
324         test $res -ne 0 && exit $res
325
326         return 0
327 }
328
329 bisect_visualize() {
330         bisect_next_check fail
331
332         if test $# = 0
333         then
334                 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
335                         type gitk >/dev/null 2>&1
336                 then
337                         set gitk
338                 else
339                         set git log
340                 fi
341         else
342                 case "$1" in
343                 git*|tig) ;;
344                 -*)     set git log "$@" ;;
345                 *)      set git "$@" ;;
346                 esac
347         fi
348
349         eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
350 }
351
352 bisect_reset() {
353         test -s "$GIT_DIR/BISECT_START" || {
354                 gettext "We are not bisecting."; echo
355                 return
356         }
357         case "$#" in
358         0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
359         1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
360                         invalid="$1"
361                         die "$(eval_gettext "'\$invalid' is not a valid commit")"
362                 }
363                 branch="$1" ;;
364         *)
365                 usage ;;
366         esac
367
368         if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
369         then
370                 die "$(eval_gettext "Could not check out original HEAD '\$branch'.
371 Try 'git bisect reset <commit>'.")"
372         fi
373         bisect_clean_state
374 }
375
376 bisect_clean_state() {
377         # There may be some refs packed during bisection.
378         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
379         while read ref hash
380         do
381                 git update-ref -d $ref $hash || exit
382         done
383         rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
384         rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
385         rm -f "$GIT_DIR/BISECT_LOG" &&
386         rm -f "$GIT_DIR/BISECT_NAMES" &&
387         rm -f "$GIT_DIR/BISECT_RUN" &&
388         # Cleanup head-name if it got left by an old version of git-bisect
389         rm -f "$GIT_DIR/head-name" &&
390         git update-ref -d --no-deref BISECT_HEAD &&
391         # clean up BISECT_START last
392         rm -f "$GIT_DIR/BISECT_START"
393 }
394
395 bisect_replay () {
396         file="$1"
397         test "$#" -eq 1 || die "$(gettext "No logfile given")"
398         test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
399         bisect_reset
400         while read git bisect command rev
401         do
402                 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
403                 if test "$git" = "git-bisect"
404                 then
405                         rev="$command"
406                         command="$bisect"
407                 fi
408                 case "$command" in
409                 start)
410                         cmd="bisect_start $rev"
411                         eval "$cmd" ;;
412                 good|bad|skip)
413                         bisect_write "$command" "$rev" ;;
414                 *)
415                         die "$(gettext "?? what are you talking about?")" ;;
416                 esac
417         done <"$file"
418         bisect_auto_next
419 }
420
421 bisect_run () {
422         bisect_next_check fail
423
424         while true
425         do
426                 command="$@"
427                 eval_gettext "running \$command"; echo
428                 "$@"
429                 res=$?
430
431                 # Check for really bad run error.
432                 if [ $res -lt 0 -o $res -ge 128 ]
433                 then
434                         (
435                                 eval_gettext "bisect run failed:
436 exit code \$res from '\$command' is < 0 or >= 128" &&
437                                 echo
438                         ) >&2
439                         exit $res
440                 fi
441
442                 # Find current state depending on run success or failure.
443                 # A special exit code of 125 means cannot test.
444                 if [ $res -eq 125 ]
445                 then
446                         state='skip'
447                 elif [ $res -gt 0 ]
448                 then
449                         state='bad'
450                 else
451                         state='good'
452                 fi
453
454                 # We have to use a subshell because "bisect_state" can exit.
455                 ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
456                 res=$?
457
458                 cat "$GIT_DIR/BISECT_RUN"
459
460                 if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
461                         > /dev/null
462                 then
463                         (
464                                 gettext "bisect run cannot continue any more" &&
465                                 echo
466                         ) >&2
467                         exit $res
468                 fi
469
470                 if [ $res -ne 0 ]
471                 then
472                         (
473                                 eval_gettext "bisect run failed:
474 'bisect_state \$state' exited with error code \$res" &&
475                                 echo
476                         ) >&2
477                         exit $res
478                 fi
479
480                 if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
481                 then
482                         gettext "bisect run success"; echo
483                         exit 0;
484                 fi
485
486         done
487 }
488
489 bisect_log () {
490         test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
491         cat "$GIT_DIR/BISECT_LOG"
492 }
493
494 case "$#" in
495 0)
496         usage ;;
497 *)
498         cmd="$1"
499         shift
500         case "$cmd" in
501         help)
502                 git bisect -h ;;
503         start)
504                 bisect_start "$@" ;;
505         bad|good)
506                 bisect_state "$cmd" "$@" ;;
507         skip)
508                 bisect_skip "$@" ;;
509         next)
510                 # Not sure we want "next" at the UI level anymore.
511                 bisect_next "$@" ;;
512         visualize|view)
513                 bisect_visualize "$@" ;;
514         reset)
515                 bisect_reset "$@" ;;
516         replay)
517                 bisect_replay "$@" ;;
518         log)
519                 bisect_log ;;
520         run)
521                 bisect_run "$@" ;;
522         *)
523                 usage ;;
524         esac
525 esac