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