Code

b9c18dd2071cbfa5a94ab32700f535c0d6acf528
[git.git] / git-bisect.sh
1 #!/bin/sh
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.
27 Please use "git help bisect" to get the full man page.'
29 OPTIONS_SPEC=
30 . git-sh-setup
31 . git-sh-i18n
32 require_work_tree
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"
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 }
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 }
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
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")"
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
147         #
148         # Get rid of any old bisect state.
149         #
150         bisect_clean_state || exit
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
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
177         trap '-' 0
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"
194 is_expected_rev() {
195         test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
196         test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
199 check_expected_revs() {
200         for _rev in "$@"; do
201                 if ! is_expected_rev "$_rev"; then
202                         rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
203                         rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
204                         return
205                 fi
206         done
209 bisect_skip() {
210         all=''
211         for arg in "$@"
212         do
213             case "$arg" in
214             *..*)
215                 revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
216             *)
217                 revs=$(git rev-parse --sq-quote "$arg") ;;
218             esac
219             all="$all $revs"
220         done
221         eval bisect_state 'skip' $all
224 bisect_state() {
225         bisect_autostart
226         state=$1
227         case "$#,$state" in
228         0,*)
229                 die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
230         1,bad|1,good|1,skip)
231                 rev=$(git rev-parse --verify $(bisect_head)) ||
232                         die "$(gettext "Bad rev input: $(bisect_head)")"
233                 bisect_write "$state" "$rev"
234                 check_expected_revs "$rev" ;;
235         2,bad|*,good|*,skip)
236                 shift
237                 eval=''
238                 for rev in "$@"
239                 do
240                         sha=$(git rev-parse --verify "$rev^{commit}") ||
241                                 die "$(eval_gettext "Bad rev input: \$rev")"
242                         eval="$eval bisect_write '$state' '$sha'; "
243                 done
244                 eval "$eval"
245                 check_expected_revs "$@" ;;
246         *,bad)
247                 die "$(gettext "'git bisect bad' can take only one argument.")" ;;
248         *)
249                 usage ;;
250         esac
251         bisect_auto_next
254 bisect_next_check() {
255         missing_good= missing_bad=
256         git show-ref -q --verify refs/bisect/bad || missing_bad=t
257         test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
259         case "$missing_good,$missing_bad,$1" in
260         ,,*)
261                 : have both good and bad - ok
262                 ;;
263         *,)
264                 # do not have both but not asked to fail - just report.
265                 false
266                 ;;
267         t,,good)
268                 # have bad but not good.  we could bisect although
269                 # this is less optimum.
270                 (
271                         gettext "Warning: bisecting only with a bad commit." &&
272                         echo
273                 ) >&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         *)
287                 if test -s "$GIT_DIR/BISECT_START"
288                 then
289                         (
290                                 gettext "You need to give me at least one good and one bad revisions.
291 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
292                                 echo
293                         ) >&2
294                 else
295                         (
296                                 gettext "You need to start by \"git bisect start\".
297 You then need to give me at least one good and one bad revisions.
298 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" &&
299                                 echo
300                         ) >&2
301                 fi
302                 exit 1 ;;
303         esac
306 bisect_auto_next() {
307         bisect_next_check && bisect_next || :
310 bisect_next() {
311         case "$#" in 0) ;; *) usage ;; esac
312         bisect_autostart
313         bisect_next_check good
315         # Perform all bisection computation, display and checkout
316         git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
317         res=$?
319         # Check if we should exit because bisection is finished
320         test $res -eq 10 && exit 0
322         # Check for an error in the bisection process
323         test $res -ne 0 && exit $res
325         return 0
328 bisect_visualize() {
329         bisect_next_check fail
331         if test $# = 0
332         then
333                 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
334                    type gitk >/dev/null 2>&1; then
335                         set gitk
336                 else
337                         set git log
338                 fi
339         else
340                 case "$1" in
341                 git*|tig) ;;
342                 -*)     set git log "$@" ;;
343                 *)      set git "$@" ;;
344                 esac
345         fi
347         eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
350 bisect_reset() {
351         test -s "$GIT_DIR/BISECT_START" || {
352                 gettext "We are not bisecting."; echo
353                 return
354         }
355         case "$#" in
356         0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
357         1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
358                invalid="$1"
359                die "$(eval_gettext "'\$invalid' is not a valid commit")"
360            }
361            branch="$1" ;;
362         *)
363             usage ;;
364         esac
365         if ! test -f "$GIT_DIR/BISECT_HEAD"
366         then
367                 if ! git checkout "$branch" --
368                 then
369                         die "$(eval_gettext "Could not check out original HEAD '\$branch'.
370 Try 'git bisect reset <commit>'.")"
371                 fi
372         fi
373         bisect_clean_state
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"
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"; then
404                         rev="$command"
405                         command="$bisect"
406                 fi
407                 case "$command" in
408                 start)
409                         cmd="bisect_start $rev"
410                         eval "$cmd" ;;
411                 good|bad|skip)
412                         bisect_write "$command" "$rev" ;;
413                 *)
414                         die "$(gettext "?? what are you talking about?")" ;;
415                 esac
416         done <"$file"
417         bisect_auto_next
420 bisect_run () {
421     bisect_next_check fail
423     while true
424     do
425       command="$@"
426       eval_gettext "running \$command"; echo
427       "$@"
428       res=$?
430       # Check for really bad run error.
431       if [ $res -lt 0 -o $res -ge 128 ]; then
432           (
433             eval_gettext "bisect run failed:
434 exit code \$res from '\$command' is < 0 or >= 128" &&
435             echo
436           ) >&2
437           exit $res
438       fi
440       # Find current state depending on run success or failure.
441       # A special exit code of 125 means cannot test.
442       if [ $res -eq 125 ]; then
443           state='skip'
444       elif [ $res -gt 0 ]; then
445           state='bad'
446       else
447           state='good'
448       fi
450       # We have to use a subshell because "bisect_state" can exit.
451       ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
452       res=$?
454       cat "$GIT_DIR/BISECT_RUN"
456       if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
457                 > /dev/null; then
458           (
459               gettext "bisect run cannot continue any more" &&
460               echo
461           ) >&2
462           exit $res
463       fi
465       if [ $res -ne 0 ]; then
466           (
467               eval_gettext "bisect run failed:
468 'bisect_state \$state' exited with error code \$res" &&
469               echo
470           ) >&2
471           exit $res
472       fi
474       if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
475           gettext "bisect run success"; echo
476           exit 0;
477       fi
479     done
482 bisect_log () {
483         test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
484         cat "$GIT_DIR/BISECT_LOG"
487 case "$#" in
488 0)
489     usage ;;
490 *)
491     cmd="$1"
492     shift
493     case "$cmd" in
494     help)
495         git bisect -h ;;
496     start)
497         bisect_start "$@" ;;
498     bad|good)
499         bisect_state "$cmd" "$@" ;;
500     skip)
501         bisect_skip "$@" ;;
502     next)
503         # Not sure we want "next" at the UI level anymore.
504         bisect_next "$@" ;;
505     visualize|view)
506         bisect_visualize "$@" ;;
507     reset)
508         bisect_reset "$@" ;;
509     replay)
510         bisect_replay "$@" ;;
511     log)
512         bisect_log ;;
513     run)
514         bisect_run "$@" ;;
515     *)
516         usage ;;
517     esac
518 esac