Code

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