Code

git-bisect: modernization
[git.git] / git-bisect.sh
1 #!/bin/sh
3 USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
4 LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
5         reset bisect state and start bisection.
6 git bisect bad [<rev>]
7         mark <rev> a known-bad revision.
8 git bisect good [<rev>...]
9         mark <rev>... known-good revisions.
10 git bisect next
11         find next bisection to test and check it out.
12 git bisect reset [<branch>]
13         finish bisection search and go back to branch.
14 git bisect visualize
15         show bisect status in gitk.
16 git bisect replay <logfile>
17         replay bisection log.
18 git bisect log
19         show bisect log.
20 git bisect run <cmd>...
21         use <cmd>... to automatically bisect.'
23 . git-sh-setup
24 require_work_tree
26 sq() {
27         @@PERL@@ -e '
28                 for (@ARGV) {
29                         s/'\''/'\'\\\\\'\''/g;
30                         print " '\''$_'\''";
31                 }
32                 print "\n";
33         ' "$@"
34 }
36 bisect_autostart() {
37         test -d "$GIT_DIR/refs/bisect" || {
38                 echo >&2 'You need to start by "git bisect start"'
39                 if test -t 0
40                 then
41                         echo >&2 -n 'Do you want me to do it for you [Y/n]? '
42                         read yesno
43                         case "$yesno" in
44                         [Nn]*)
45                                 exit ;;
46                         esac
47                         bisect_start
48                 else
49                         exit 1
50                 fi
51         }
52 }
54 bisect_start() {
55         #
56         # Verify HEAD. If we were bisecting before this, reset to the
57         # top-of-line master first!
58         #
59         head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
60         die "Bad HEAD - I need a symbolic ref"
61         case "$head" in
62         refs/heads/bisect)
63                 if [ -s "$GIT_DIR/head-name" ]; then
64                     branch=`cat "$GIT_DIR/head-name"`
65                 else
66                     branch=master
67                 fi
68                 git checkout $branch || exit
69                 ;;
70         refs/heads/*)
71                 [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
72                 echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
73                 ;;
74         *)
75                 die "Bad HEAD - strange symbolic ref"
76                 ;;
77         esac
79         #
80         # Get rid of any old bisect state
81         #
82         bisect_clean_state
83         mkdir "$GIT_DIR/refs/bisect"
85         #
86         # Check for one bad and then some good revisions.
87         #
88         has_double_dash=0
89         for arg; do
90             case "$arg" in --) has_double_dash=1; break ;; esac
91         done
92         orig_args=$(sq "$@")
93         bad_seen=0
94         while [ $# -gt 0 ]; do
95             arg="$1"
96             case "$arg" in
97             --)
98                 shift
99                 break
100                 ;;
101             *)
102                 rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
103                     test $has_double_dash -eq 1 &&
104                         die "'$arg' does not appear to be a valid revision"
105                     break
106                 }
107                 if [ $bad_seen -eq 0 ]; then
108                     bad_seen=1
109                     bisect_write_bad "$rev"
110                 else
111                     bisect_write_good "$rev"
112                 fi
113                 shift
114                 ;;
115             esac
116         done
118         sq "$@" >"$GIT_DIR/BISECT_NAMES"
119         {
120             printf "git-bisect start"
121             echo "$orig_args"
122         } >>"$GIT_DIR/BISECT_LOG"
123         bisect_auto_next
126 bisect_bad() {
127         bisect_autostart
128         case "$#" in
129         0)
130                 rev=$(git-rev-parse --verify HEAD) ;;
131         1)
132                 rev=$(git-rev-parse --verify "$1^{commit}") ;;
133         *)
134                 usage ;;
135         esac || exit
136         bisect_write_bad "$rev"
137         echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
138         bisect_auto_next
141 bisect_write_bad() {
142         rev="$1"
143         echo "$rev" >"$GIT_DIR/refs/bisect/bad"
144         echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
147 bisect_good() {
148         bisect_autostart
149         case "$#" in
150         0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
151         *)    revs=$(git-rev-parse --revs-only --no-flags "$@") &&
152                 test '' != "$revs" || die "Bad rev input: $@" ;;
153         esac
154         for rev in $revs
155         do
156                 rev=$(git-rev-parse --verify "$rev^{commit}") || exit
157                 bisect_write_good "$rev"
158                 echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
160         done
161         bisect_auto_next
164 bisect_write_good() {
165         rev="$1"
166         echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
167         echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
170 bisect_next_check() {
171         next_ok=no
172         git show-ref -q --verify refs/bisect/bad &&
173         test -n "$(git for-each-ref "refs/bisect/good-*")" &&
174         next_ok=yes
176         case "$next_ok,$1" in
177         no,) false ;;
178         no,fail)
179             THEN=''
180             test -d "$GIT_DIR/refs/bisect" || {
181                 echo >&2 'You need to start by "git bisect start".'
182                 THEN='then '
183             }
184             echo >&2 'You '$THEN'need to give me at least one good' \
185                 'and one bad revisions.'
186             echo >&2 '(You can use "git bisect bad" and' \
187                 '"git bisect good" for that.)'
188             exit 1 ;;
189         *)
190             true ;;
191         esac
194 bisect_auto_next() {
195         bisect_next_check && bisect_next || :
198 bisect_next() {
199         case "$#" in 0) ;; *) usage ;; esac
200         bisect_autostart
201         bisect_next_check fail
202         bad=$(git-rev-parse --verify refs/bisect/bad) &&
203         good=$(git-rev-parse --sq --revs-only --not \
204                 $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
205         rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit
206         if [ -z "$rev" ]; then
207             echo "$bad was both good and bad"
208             exit 1
209         fi
210         if [ "$rev" = "$bad" ]; then
211             echo "$rev is first bad commit"
212             git-diff-tree --pretty $rev
213             exit 0
214         fi
215         nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit
216         echo "Bisecting: $nr revisions left to test after this"
217         echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
218         git checkout -q new-bisect || exit
219         mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
220         GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
221         git-show-branch "$rev"
224 bisect_visualize() {
225         bisect_next_check fail
226         not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
227         eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
230 bisect_reset() {
231         case "$#" in
232         0) if [ -s "$GIT_DIR/head-name" ]; then
233                branch=`cat "$GIT_DIR/head-name"`
234            else
235                branch=master
236            fi ;;
237         1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
238                echo >&2 "$1 does not seem to be a valid branch"
239                exit 1
240            }
241            branch="$1" ;;
242         *)
243             usage ;;
244         esac
245         if git checkout "$branch"; then
246                 rm -f "$GIT_DIR/head-name"
247                 bisect_clean_state
248         fi
251 bisect_clean_state() {
252         rm -fr "$GIT_DIR/refs/bisect"
253         rm -f "$GIT_DIR/refs/heads/bisect"
254         rm -f "$GIT_DIR/BISECT_LOG"
255         rm -f "$GIT_DIR/BISECT_NAMES"
256         rm -f "$GIT_DIR/BISECT_RUN"
259 bisect_replay () {
260         test -r "$1" || {
261                 echo >&2 "cannot read $1 for replaying"
262                 exit 1
263         }
264         bisect_reset
265         while read bisect command rev
266         do
267                 test "$bisect" = "git-bisect" || continue
268                 case "$command" in
269                 start)
270                         cmd="bisect_start $rev"
271                         eval "$cmd"
272                         ;;
273                 good)
274                         echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
275                         echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
276                         echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
277                         ;;
278                 bad)
279                         echo "$rev" >"$GIT_DIR/refs/bisect/bad"
280                         echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
281                         echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
282                         ;;
283                 *)
284                         echo >&2 "?? what are you talking about?"
285                         exit 1 ;;
286                 esac
287         done <"$1"
288         bisect_auto_next
291 bisect_run () {
292     bisect_next_check fail
294     while true
295     do
296       echo "running $@"
297       "$@"
298       res=$?
300       # Check for really bad run error.
301       if [ $res -lt 0 -o $res -ge 128 ]; then
302           echo >&2 "bisect run failed:"
303           echo >&2 "exit code $res from '$@' is < 0 or >= 128"
304           exit $res
305       fi
307       # Use "bisect_good" or "bisect_bad"
308       # depending on run success or failure.
309       if [ $res -gt 0 ]; then
310           next_bisect='bisect_bad'
311       else
312           next_bisect='bisect_good'
313       fi
315       # We have to use a subshell because bisect_good or
316       # bisect_bad functions can exit.
317       ( $next_bisect > "$GIT_DIR/BISECT_RUN" )
318       res=$?
320       cat "$GIT_DIR/BISECT_RUN"
322       if [ $res -ne 0 ]; then
323           echo >&2 "bisect run failed:"
324           echo >&2 "$next_bisect exited with error code $res"
325           exit $res
326       fi
328       if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
329           echo "bisect run success"
330           exit 0;
331       fi
333     done
337 case "$#" in
338 0)
339     usage ;;
340 *)
341     cmd="$1"
342     shift
343     case "$cmd" in
344     start)
345         bisect_start "$@" ;;
346     bad)
347         bisect_bad "$@" ;;
348     good)
349         bisect_good "$@" ;;
350     next)
351         # Not sure we want "next" at the UI level anymore.
352         bisect_next "$@" ;;
353     visualize)
354         bisect_visualize "$@" ;;
355     reset)
356         bisect_reset "$@" ;;
357     replay)
358         bisect_replay "$@" ;;
359     log)
360         cat "$GIT_DIR/BISECT_LOG" ;;
361     run)
362         bisect_run "$@" ;;
363     *)
364         usage ;;
365     esac
366 esac