cmd_diff(): use an object_array for holding trees
[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         if test $res -eq 10
315         then
316                 bad_rev=$(git show-ref --hash --verify refs/bisect/bad)
317                 bad_commit=$(git show-branch $bad_rev)
318                 echo "# first bad commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
319                 exit 0
320         elif test $res -eq 2
321         then
322                 echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
323                 good_revs=$(git for-each-ref --format="--not %(objectname)" "refs/bisect/good-*")
324                 for skipped in $(git rev-list refs/bisect/bad $good_revs)
325                 do
326                         skipped_commit=$(git show-branch $skipped)
327                         echo "# possible first bad commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
328                 done
329                 exit $res
330         fi
331
332         # Check for an error in the bisection process
333         test $res -ne 0 && exit $res
334
335         return 0
336 }
337
338 bisect_visualize() {
339         bisect_next_check fail
340
341         if test $# = 0
342         then
343                 if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
344                         type gitk >/dev/null 2>&1
345                 then
346                         set gitk
347                 else
348                         set git log
349                 fi
350         else
351                 case "$1" in
352                 git*|tig) ;;
353                 -*)     set git log "$@" ;;
354                 *)      set git "$@" ;;
355                 esac
356         fi
357
358         eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
359 }
360
361 bisect_reset() {
362         test -s "$GIT_DIR/BISECT_START" || {
363                 gettextln "We are not bisecting."
364                 return
365         }
366         case "$#" in
367         0) branch=$(cat "$GIT_DIR/BISECT_START") ;;
368         1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || {
369                         invalid="$1"
370                         die "$(eval_gettext "'\$invalid' is not a valid commit")"
371                 }
372                 branch="$1" ;;
373         *)
374                 usage ;;
375         esac
376
377         if ! test -f "$GIT_DIR/BISECT_HEAD" && ! git checkout "$branch" --
378         then
379                 die "$(eval_gettext "Could not check out original HEAD '\$branch'.
380 Try 'git bisect reset <commit>'.")"
381         fi
382         bisect_clean_state
383 }
384
385 bisect_clean_state() {
386         # There may be some refs packed during bisection.
387         git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
388         while read ref hash
389         do
390                 git update-ref -d $ref $hash || exit
391         done
392         rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
393         rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
394         rm -f "$GIT_DIR/BISECT_LOG" &&
395         rm -f "$GIT_DIR/BISECT_NAMES" &&
396         rm -f "$GIT_DIR/BISECT_RUN" &&
397         # Cleanup head-name if it got left by an old version of git-bisect
398         rm -f "$GIT_DIR/head-name" &&
399         git update-ref -d --no-deref BISECT_HEAD &&
400         # clean up BISECT_START last
401         rm -f "$GIT_DIR/BISECT_START"
402 }
403
404 bisect_replay () {
405         file="$1"
406         test "$#" -eq 1 || die "$(gettext "No logfile given")"
407         test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
408         bisect_reset
409         while read git bisect command rev
410         do
411                 test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue
412                 if test "$git" = "git-bisect"
413                 then
414                         rev="$command"
415                         command="$bisect"
416                 fi
417                 case "$command" in
418                 start)
419                         cmd="bisect_start $rev"
420                         eval "$cmd" ;;
421                 good|bad|skip)
422                         bisect_write "$command" "$rev" ;;
423                 *)
424                         die "$(gettext "?? what are you talking about?")" ;;
425                 esac
426         done <"$file"
427         bisect_auto_next
428 }
429
430 bisect_run () {
431         bisect_next_check fail
432
433         while true
434         do
435                 command="$@"
436                 eval_gettextln "running \$command"
437                 "$@"
438                 res=$?
439
440                 # Check for really bad run error.
441                 if [ $res -lt 0 -o $res -ge 128 ]
442                 then
443                         eval_gettextln "bisect run failed:
444 exit code \$res from '\$command' is < 0 or >= 128" >&2
445                         exit $res
446                 fi
447
448                 # Find current state depending on run success or failure.
449                 # A special exit code of 125 means cannot test.
450                 if [ $res -eq 125 ]
451                 then
452                         state='skip'
453                 elif [ $res -gt 0 ]
454                 then
455                         state='bad'
456                 else
457                         state='good'
458                 fi
459
460                 # We have to use a subshell because "bisect_state" can exit.
461                 ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
462                 res=$?
463
464                 cat "$GIT_DIR/BISECT_RUN"
465
466                 if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
467                         > /dev/null
468                 then
469                         gettextln "bisect run cannot continue any more" >&2
470                         exit $res
471                 fi
472
473                 if [ $res -ne 0 ]
474                 then
475                         eval_gettextln "bisect run failed:
476 'bisect_state \$state' exited with error code \$res" >&2
477                         exit $res
478                 fi
479
480                 if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null
481                 then
482                         gettextln "bisect run success"
483                         exit 0;
484                 fi
485
486         done
487 }
488
489 bisect_log () {
490         test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")"
491         cat "$GIT_DIR/BISECT_LOG"
492 }
493
494 case "$#" in
495 0)
496         usage ;;
497 *)
498         cmd="$1"
499         shift
500         case "$cmd" in
501         help)
502                 git bisect -h ;;
503         start)
504                 bisect_start "$@" ;;
505         bad|good)
506                 bisect_state "$cmd" "$@" ;;
507         skip)
508                 bisect_skip "$@" ;;
509         next)
510                 # Not sure we want "next" at the UI level anymore.
511                 bisect_next "$@" ;;
512         visualize|view)
513                 bisect_visualize "$@" ;;
514         reset)
515                 bisect_reset "$@" ;;
516         replay)
517                 bisect_replay "$@" ;;
518         log)
519                 bisect_log ;;
520         run)
521                 bisect_run "$@" ;;
522         *)
523                 usage ;;
524         esac
525 esac