]> git.ipfire.org Git - thirdparty/git.git/blob - git-bisect.sh
GIT-VERSION-GEN: support non-standard $GIT_DIR path
[thirdparty/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 <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