]> rtime.felk.cvut.cz Git - git.git/blob - git-bisect.sh
submodule: Fix documentation of update subcommand
[git.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 <valid-branch>'.")"
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 cg-seek'ed 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                 hash_list=''
241                 for rev in "$@"
242                 do
243                         sha=$(git rev-parse --verify "$rev^{commit}") ||
244                                 die "$(eval_gettext "Bad rev input: \$rev")"
245                         hash_list="$hash_list $sha"
246                 done
247                 for rev in $hash_list
248                 do
249                         bisect_write "$state" "$rev"
250                 done
251                 check_expected_revs $hash_list ;;
252         *,bad)
253                 die "$(gettext "'git bisect bad' can take only one argument.")" ;;
254         *)
255                 usage ;;
256         esac
257         bisect_auto_next
258 }
259
260 bisect_next_check() {
261         missing_good= missing_bad=
262         git show-ref -q --verify refs/bisect/bad || missing_bad=t
263         test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
264
265         case "$missing_good,$missing_bad,$1" in
266         ,,*)
267                 : have both good and bad - ok
268                 ;;
269         *,)
270                 # do not have both but not asked to fail - just report.
271                 false
272                 ;;
273         t,,good)
274                 # have bad but not good.  we could bisect although
275                 # this is less optimum.
276                 gettextln "Warning: bisecting only with a bad commit." >&2
277                 if test -t 0
278                 then
279                         # TRANSLATORS: Make sure to include [Y] and [n] in your
280                         # translation. The program will only accept English input
281                         # at this point.
282                         gettext "Are you sure [Y/n]? " >&2
283                         read yesno
284                         case "$yesno" in [Nn]*) exit 1 ;; esac
285                 fi
286                 : bisect without good...
287                 ;;
288         *)
289
290                 if test -s "$GIT_DIR/BISECT_START"
291                 then
292                         gettextln "You need to give me at least one good and one bad revision.
293 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
294                 else
295                         gettextln "You need to start by \"git bisect start\".
296 You then need to give me at least one good and one bad revision.
297 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
298                 fi
299                 exit 1 ;;
300         esac
301 }
302
303 bisect_auto_next() {
304         bisect_next_check && bisect_next || :
305 }
306
307 bisect_next() {
308         case "$#" in 0) ;; *) usage ;; esac
309         bisect_autostart
310         bisect_next_check good
311
312         # Perform all bisection computation, display and checkout
313         git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
314         res=$?
315
316         # Check if we should exit because bisection is finished
317         if test $res -eq 10
318         then
319                 bad_rev=$(git show-ref --hash --verify refs/bisect/bad)
320                 bad_commit=$(git show-branch $bad_rev)
321                 echo "# first bad commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
322                 exit 0
323         elif test $res -eq 2
324         then
325                 echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
326                 good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/good-*")
327                 for skipped in $(git rev-list refs/bisect/bad --not $good_revs)
328                 do
329                         skipped_commit=$(git show-branch $skipped)
330                         echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
331                 done
332                 exit $res
333         fi
334
335         # Check for an error in the bisection process
336         test $res -ne 0 && exit $res
337
338         return 0
339 }
340
341 bisect_visualize() {
342         bisect_next_check fail
343
344         if test $# = 0
345         then
346                 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
347                         type gitk >/dev/null 2>&1
348                 then
349                         set gitk
350                 else
351                         set git log
352                 fi
353         else
354                 case "$1" in
355                 git*|tig) ;;
356                 -*)     set git log "$@" ;;
357                 *)      set git "$@" ;;
358                 esac
359         fi
360
361         eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
362 }
363
364 bisect_reset() {
365         test -s "$GIT_DIR/BISECT_START" || {
366                 gettextln "We are not bisecting."
367                 return
368         }
369         case "$#" in
370         0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
371         1) git rev-parse --quiet --verify "$1^{commit}" >/dev/null || {
372                         invalid="$1"
373                         die "$(eval_gettext "'\$invalid' is not a valid commit")"
374                 }
375                 branch="$1" ;;
376         *)
377                 usage ;;
378         esac
379
380         if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
381         then
382                 die "$(eval_gettext "Could not check out original HEAD '\$branch'.
383 Try 'git bisect reset <commit>'.")"
384         fi
385         bisect_clean_state
386 }
387
388 bisect_clean_state() {
389         # There may be some refs packed during bisection.
390         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
391         while read ref hash
392         do
393                 git update-ref -d $ref $hash || exit
394         done
395         rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
396         rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
397         rm -f "$GIT_DIR/BISECT_LOG" &&
398         rm -f "$GIT_DIR/BISECT_NAMES" &&
399         rm -f "$GIT_DIR/BISECT_RUN" &&
400         # Cleanup head-name if it got left by an old version of git-bisect
401         rm -f "$GIT_DIR/head-name" &&
402         git update-ref -d --no-deref BISECT_HEAD &&
403         # clean up BISECT_START last
404         rm -f "$GIT_DIR/BISECT_START"
405 }
406
407 bisect_replay () {
408         file="$1"
409         test "$#" -eq 1 || die "$(gettext "No logfile given")"
410         test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
411         bisect_reset
412         while read git bisect command rev
413         do
414                 test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
415                 if test "$git" = "git-bisect"
416                 then
417                         rev="$command"
418                         command="$bisect"
419                 fi
420                 case "$command" in
421                 start)
422                         cmd="bisect_start $rev"
423                         eval "$cmd" ;;
424                 good|bad|skip)
425                         bisect_write "$command" "$rev" ;;
426                 *)
427                         die "$(gettext "?? what are you talking about?")" ;;
428                 esac
429         done <"$file"
430         bisect_auto_next
431 }
432
433 bisect_run () {
434         bisect_next_check fail
435
436         while true
437         do
438                 command="$@"
439                 eval_gettextln "running \$command"
440                 "$@"
441                 res=$?
442
443                 # Check for really bad run error.
444                 if [ $res -lt 0 -o $res -ge 128 ]
445                 then
446                         eval_gettextln "bisect run failed:
447 exit code \$res from '\$command' is < 0 or >= 128" >&2
448                         exit $res
449                 fi
450
451                 # Find current state depending on run success or failure.
452                 # A special exit code of 125 means cannot test.
453                 if [ $res -eq 125 ]
454                 then
455                         state='skip'
456                 elif [ $res -gt 0 ]
457                 then
458                         state='bad'
459                 else
460                         state='good'
461                 fi
462
463                 # We have to use a subshell because "bisect_state" can exit.
464                 ( bisect_state $state >"$GIT_DIR/BISECT_RUN" )
465                 res=$?
466
467                 cat "$GIT_DIR/BISECT_RUN"
468
469                 if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
470                         >/dev/null
471                 then
472                         gettextln "bisect run cannot continue any more" >&2
473                         exit $res
474                 fi
475
476                 if [ $res -ne 0 ]
477                 then
478                         eval_gettextln "bisect run failed:
479 'bisect_state \$state' exited with error code \$res" >&2
480                         exit $res
481                 fi
482
483                 if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" >/dev/null
484                 then
485                         gettextln "bisect run success"
486                         exit 0;
487                 fi
488
489         done
490 }
491
492 bisect_log () {
493         test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
494         cat "$GIT_DIR/BISECT_LOG"
495 }
496
497 case "$#" in
498 0)
499         usage ;;
500 *)
501         cmd="$1"
502         shift
503         case "$cmd" in
504         help)
505                 git bisect -h ;;
506         start)
507                 bisect_start "$@" ;;
508         bad|good)
509                 bisect_state "$cmd" "$@" ;;
510         skip)
511                 bisect_skip "$@" ;;
512         next)
513                 # Not sure we want "next" at the UI level anymore.
514                 bisect_next "$@" ;;
515         visualize|view)
516                 bisect_visualize "$@" ;;
517         reset)
518                 bisect_reset "$@" ;;
519         replay)
520                 bisect_replay "$@" ;;
521         log)
522                 bisect_log ;;
523         run)
524                 bisect_run "$@" ;;
525         *)
526                 usage ;;
527         esac
528 esac