git-p4 tests: refactor and cleanup
[git/jrn.git] / git-bisect.sh
blob2524060475ef369c0b9a4641aa88dc2ad6083da4
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
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"
36 bisect_head()
38 if test -f "$GIT_DIR/BISECT_HEAD"
39 then
40 echo BISECT_HEAD
41 else
42 echo HEAD
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
67 bisect_start() {
69 # Check for one bad and then some good revisions.
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=''
84 while [ $# -gt 0 ]; do
85 arg="$1"
86 case "$arg" in
87 --)
88 shift
89 break
91 --no-checkout)
92 mode=--no-checkout
93 shift ;;
94 --*)
95 die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
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
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
109 esac
110 done
113 # Verify HEAD.
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")"
120 # Check if we are bisecting.
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" --
131 else
132 # Get rev from where we start.
133 case "$head" in
134 refs/heads/*|$_x40)
135 # This error message should only be triggered by
136 # cogito usage, and cogito users should understand
137 # it relates to cg-seek.
138 [ -s "$GIT_DIR/head-name" ] &&
139 die "$(gettext "won't bisect on seeked tree")"
140 start_head="${head#refs/heads/}"
143 die "$(gettext "Bad HEAD - strange symbolic ref")"
145 esac
149 # Get rid of any old bisect state.
151 bisect_clean_state || exit
154 # Change state.
155 # In case of mistaken revs or checkout error, or signals received,
156 # "bisect_auto_next" below may exit or misbehave.
157 # We have to trap this to be able to clean up using
158 # "bisect_clean_state".
160 trap 'bisect_clean_state' 0
161 trap 'exit 255' 1 2 3 15
164 # Write new start state.
166 echo "$start_head" >"$GIT_DIR/BISECT_START" && {
167 test "z$mode" != "z--no-checkout" ||
168 git update-ref --no-deref BISECT_HEAD "$start_head"
169 } &&
170 git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
171 eval "$eval true" &&
172 echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
174 # Check if we can proceed to the next bisect state.
176 bisect_auto_next
178 trap '-' 0
181 bisect_write() {
182 state="$1"
183 rev="$2"
184 nolog="$3"
185 case "$state" in
186 bad) tag="$state" ;;
187 good|skip) tag="$state"-"$rev" ;;
188 *) die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
189 esac
190 git update-ref "refs/bisect/$tag" "$rev" || exit
191 echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
192 test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
195 is_expected_rev() {
196 test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
197 test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
200 check_expected_revs() {
201 for _rev in "$@"; do
202 if ! is_expected_rev "$_rev"
203 then
204 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
205 rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
206 return
208 done
211 bisect_skip() {
212 all=''
213 for arg in "$@"
215 case "$arg" in
216 *..*)
217 revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;;
219 revs=$(git rev-parse --sq-quote "$arg") ;;
220 esac
221 all="$all $revs"
222 done
223 eval bisect_state 'skip' $all
226 bisect_state() {
227 bisect_autostart
228 state=$1
229 case "$#,$state" in
230 0,*)
231 die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
232 1,bad|1,good|1,skip)
233 rev=$(git rev-parse --verify $(bisect_head)) ||
234 die "$(gettext "Bad rev input: $(bisect_head)")"
235 bisect_write "$state" "$rev"
236 check_expected_revs "$rev" ;;
237 2,bad|*,good|*,skip)
238 shift
239 eval=''
240 for rev in "$@"
242 sha=$(git rev-parse --verify "$rev^{commit}") ||
243 die "$(eval_gettext "Bad rev input: \$rev")"
244 eval="$eval bisect_write '$state' '$sha'; "
245 done
246 eval "$eval"
247 check_expected_revs "$@" ;;
248 *,bad)
249 die "$(gettext "'git bisect bad' can take only one argument.")" ;;
251 usage ;;
252 esac
253 bisect_auto_next
256 bisect_next_check() {
257 missing_good= missing_bad=
258 git show-ref -q --verify refs/bisect/bad || missing_bad=t
259 test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
261 case "$missing_good,$missing_bad,$1" in
262 ,,*)
263 : have both good and bad - ok
266 # do not have both but not asked to fail - just report.
267 false
269 t,,good)
270 # have bad but not good. we could bisect although
271 # this is less optimum.
272 gettextln "Warning: bisecting only with a bad commit." >&2
273 if test -t 0
274 then
275 # TRANSLATORS: Make sure to include [Y] and [n] in your
276 # translation. The program will only accept English input
277 # at this point.
278 gettext "Are you sure [Y/n]? " >&2
279 read yesno
280 case "$yesno" in [Nn]*) exit 1 ;; esac
282 : bisect without good...
286 if test -s "$GIT_DIR/BISECT_START"
287 then
288 gettextln "You need to give me at least one good and one bad revisions.
289 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
290 else
291 gettextln "You need to start by \"git bisect start\".
292 You then need to give me at least one good and one bad revisions.
293 (You can use \"git bisect bad\" and \"git bisect good\" for that.)" >&2
295 exit 1 ;;
296 esac
299 bisect_auto_next() {
300 bisect_next_check && bisect_next || :
303 bisect_next() {
304 case "$#" in 0) ;; *) usage ;; esac
305 bisect_autostart
306 bisect_next_check good
308 # Perform all bisection computation, display and checkout
309 git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
310 res=$?
312 # Check if we should exit because bisection is finished
313 test $res -eq 10 && exit 0
315 # Check for an error in the bisection process
316 test $res -ne 0 && exit $res
318 return 0
321 bisect_visualize() {
322 bisect_next_check fail
324 if test $# = 0
325 then
326 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
327 type gitk >/dev/null 2>&1
328 then
329 set gitk
330 else
331 set git log
333 else
334 case "$1" in
335 git*|tig) ;;
336 -*) set git log "$@" ;;
337 *) set git "$@" ;;
338 esac
341 eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
344 bisect_reset() {
345 test -s "$GIT_DIR/BISECT_START" || {
346 gettextln "We are not bisecting."
347 return
349 case "$#" in
350 0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
351 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
352 invalid="$1"
353 die "$(eval_gettext "'\$invalid' is not a valid commit")"
355 branch="$1" ;;
357 usage ;;
358 esac
360 if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
361 then
362 die "$(eval_gettext "Could not check out original HEAD '\$branch'.
363 Try 'git bisect reset <commit>'.")"
365 bisect_clean_state
368 bisect_clean_state() {
369 # There may be some refs packed during bisection.
370 git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
371 while read ref hash
373 git update-ref -d $ref $hash || exit
374 done
375 rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
376 rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
377 rm -f "$GIT_DIR/BISECT_LOG" &&
378 rm -f "$GIT_DIR/BISECT_NAMES" &&
379 rm -f "$GIT_DIR/BISECT_RUN" &&
380 # Cleanup head-name if it got left by an old version of git-bisect
381 rm -f "$GIT_DIR/head-name" &&
382 git update-ref -d --no-deref BISECT_HEAD &&
383 # clean up BISECT_START last
384 rm -f "$GIT_DIR/BISECT_START"
387 bisect_replay () {
388 file="$1"
389 test "$#" -eq 1 || die "$(gettext "No logfile given")"
390 test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
391 bisect_reset
392 while read git bisect command rev
394 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
395 if test "$git" = "git-bisect"
396 then
397 rev="$command"
398 command="$bisect"
400 case "$command" in
401 start)
402 cmd="bisect_start $rev"
403 eval "$cmd" ;;
404 good|bad|skip)
405 bisect_write "$command" "$rev" ;;
407 die "$(gettext "?? what are you talking about?")" ;;
408 esac
409 done <"$file"
410 bisect_auto_next
413 bisect_run () {
414 bisect_next_check fail
416 while true
418 command="$@"
419 eval_gettextln "running \$command"
420 "$@"
421 res=$?
423 # Check for really bad run error.
424 if [ $res -lt 0 -o $res -ge 128 ]
425 then
426 eval_gettextln "bisect run failed:
427 exit code \$res from '\$command' is < 0 or >= 128" >&2
428 exit $res
431 # Find current state depending on run success or failure.
432 # A special exit code of 125 means cannot test.
433 if [ $res -eq 125 ]
434 then
435 state='skip'
436 elif [ $res -gt 0 ]
437 then
438 state='bad'
439 else
440 state='good'
443 # We have to use a subshell because "bisect_state" can exit.
444 ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
445 res=$?
447 cat "$GIT_DIR/BISECT_RUN"
449 if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
450 > /dev/null
451 then
452 gettextln "bisect run cannot continue any more" >&2
453 exit $res
456 if [ $res -ne 0 ]
457 then
458 eval_gettextln "bisect run failed:
459 'bisect_state \$state' exited with error code \$res" >&2
460 exit $res
463 if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
464 then
465 gettextln "bisect run success"
466 exit 0;
469 done
472 bisect_log () {
473 test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
474 cat "$GIT_DIR/BISECT_LOG"
477 case "$#" in
479 usage ;;
481 cmd="$1"
482 shift
483 case "$cmd" in
484 help)
485 git bisect -h ;;
486 start)
487 bisect_start "$@" ;;
488 bad|good)
489 bisect_state "$cmd" "$@" ;;
490 skip)
491 bisect_skip "$@" ;;
492 next)
493 # Not sure we want "next" at the UI level anymore.
494 bisect_next "$@" ;;
495 visualize|view)
496 bisect_visualize "$@" ;;
497 reset)
498 bisect_reset "$@" ;;
499 replay)
500 bisect_replay "$@" ;;
501 log)
502 bisect_log ;;
503 run)
504 bisect_run "$@" ;;
506 usage ;;
507 esac
508 esac