@@ -172,6 +172,25 @@ bisect_write() {
172172 test -n " $nolog " || echo " git bisect $state $rev " >> " $GIT_DIR /BISECT_LOG"
173173}
174174
175+ is_expected_rev () {
176+ test -f " $GIT_DIR /BISECT_EXPECTED_REV" &&
177+ test " $1 " = $( cat " $GIT_DIR /BISECT_EXPECTED_REV" )
178+ }
179+
180+ mark_expected_rev () {
181+ echo " $1 " > " $GIT_DIR /BISECT_EXPECTED_REV"
182+ }
183+
184+ check_expected_revs () {
185+ for _rev in " $@ " ; do
186+ if ! is_expected_rev " $_rev " ; then
187+ rm -f " $GIT_DIR /BISECT_ANCESTORS_OK"
188+ rm -f " $GIT_DIR /BISECT_EXPECTED_REV"
189+ return
190+ fi
191+ done
192+ }
193+
175194bisect_state () {
176195 bisect_autostart
177196 state=$1
@@ -181,7 +200,8 @@ bisect_state() {
181200 1,bad|1,good|1,skip)
182201 rev=$( git rev-parse --verify HEAD) ||
183202 die " Bad rev input: HEAD"
184- bisect_write " $state " " $rev " ;;
203+ bisect_write " $state " " $rev "
204+ check_expected_revs " $rev " ;;
185205 2,bad|* ,good|* ,skip)
186206 shift
187207 eval=' '
@@ -191,7 +211,8 @@ bisect_state() {
191211 die " Bad rev input: $rev "
192212 eval=" $eval bisect_write '$state ' '$sha '; "
193213 done
194- eval " $eval " ;;
214+ eval " $eval "
215+ check_expected_revs " $@ " ;;
195216 * ,bad)
196217 die " 'git bisect bad' can take only one argument." ;;
197218 * )
@@ -243,33 +264,18 @@ bisect_auto_next() {
243264 bisect_next_check && bisect_next || :
244265}
245266
246- eval_rev_list () {
247- _eval=" $1 "
248-
249- eval $_eval
250- res=$?
251-
252- if [ $res -ne 0 ]; then
253- echo >&2 " 'git rev-list --bisect-vars' failed:"
254- echo >&2 " maybe you mistake good and bad revs?"
255- exit $res
256- fi
257-
258- return $res
259- }
260-
261267filter_skipped () {
262268 _eval=" $1 "
263269 _skip=" $2 "
264270
265271 if [ -z " $_skip " ]; then
266- eval_rev_list " $_eval "
272+ eval " $_eval "
267273 return
268274 fi
269275
270276 # Let's parse the output of:
271277 # "git rev-list --bisect-vars --bisect-all ..."
272- eval_rev_list " $_eval " | while read hash line
278+ eval " $_eval " | while read hash line
273279 do
274280 case " $VARS ,$FOUND ,$TRIED ,$hash " in
275281 # We display some vars.
@@ -332,20 +338,133 @@ exit_if_skipped_commits () {
332338 fi
333339}
334340
341+ bisect_checkout () {
342+ _rev=" $1 "
343+ _msg=" $2 "
344+ echo " Bisecting: $_msg "
345+ mark_expected_rev " $_rev "
346+ git checkout -q " $_rev " || exit
347+ git show-branch " $_rev "
348+ }
349+
350+ is_among () {
351+ _rev=" $1 "
352+ _list=" $2 "
353+ case " $_list " in * $_rev * ) return 0 ;; esac
354+ return 1
355+ }
356+
357+ handle_bad_merge_base () {
358+ _badmb=" $1 "
359+ _good=" $2 "
360+ if is_expected_rev " $_badmb " ; then
361+ cat >&2 << EOF
362+ The merge base $_badmb is bad.
363+ This means the bug has been fixed between $_badmb and [$_good ].
364+ EOF
365+ exit 3
366+ else
367+ cat >&2 << EOF
368+ Some good revs are not ancestor of the bad rev.
369+ git bisect cannot work properly in this case.
370+ Maybe you mistake good and bad revs?
371+ EOF
372+ exit 1
373+ fi
374+ }
375+
376+ handle_skipped_merge_base () {
377+ _mb=" $1 "
378+ _bad=" $2 "
379+ _good=" $3 "
380+ cat >&2 << EOF
381+ Warning: the merge base between $_bad and [$_good ] must be skipped.
382+ So we cannot be sure the first bad commit is between $_mb and $_bad .
383+ We continue anyway.
384+ EOF
385+ }
386+
387+ #
388+ # "check_merge_bases" checks that merge bases are not "bad".
389+ #
390+ # - If one is "good", that's good, we have nothing to do.
391+ # - If one is "bad", it means the user assumed something wrong
392+ # and we must exit.
393+ # - If one is "skipped", we can't know but we should warn.
394+ # - If we don't know, we should check it out and ask the user to test.
395+ #
396+ # In the last case we will return 1, and otherwise 0.
397+ #
398+ check_merge_bases () {
399+ _bad=" $1 "
400+ _good=" $2 "
401+ _skip=" $3 "
402+ for _mb in $( git merge-base --all $_bad $_good )
403+ do
404+ if is_among " $_mb " " $_good " ; then
405+ continue
406+ elif test " $_mb " = " $_bad " ; then
407+ handle_bad_merge_base " $_bad " " $_good "
408+ elif is_among " $_mb " " $_skip " ; then
409+ handle_skipped_merge_base " $_mb " " $_bad " " $_good "
410+ else
411+ bisect_checkout " $_mb " " a merge base must be tested"
412+ return 1
413+ fi
414+ done
415+ return 0
416+ }
417+
418+ #
419+ # "check_good_are_ancestors_of_bad" checks that all "good" revs are
420+ # ancestor of the "bad" rev.
421+ #
422+ # If that's not the case, we need to check the merge bases.
423+ # If a merge base must be tested by the user we return 1 and
424+ # otherwise 0.
425+ #
426+ check_good_are_ancestors_of_bad () {
427+ test -f " $GIT_DIR /BISECT_ANCESTORS_OK" &&
428+ return
429+
430+ _bad=" $1 "
431+ _good=$( echo $2 | sed -e ' s/\^//g' )
432+ _skip=" $3 "
433+
434+ # Bisecting with no good rev is ok
435+ test -z " $_good " && return
436+
437+ _side=$( git rev-list $_good ^$_bad )
438+ if test -n " $_side " ; then
439+ # Return if a checkout was done
440+ check_merge_bases " $_bad " " $_good " " $_skip " || return
441+ fi
442+
443+ : > " $GIT_DIR /BISECT_ANCESTORS_OK"
444+
445+ return 0
446+ }
447+
335448bisect_next () {
336449 case " $# " in 0) ;; * ) usage ;; esac
337450 bisect_autostart
338451 bisect_next_check good
339452
453+ # Get bad, good and skipped revs
454+ bad=$( git rev-parse --verify refs/bisect/bad) &&
455+ good=$( git for-each-ref --format=' ^%(objectname)' \
456+ " refs/bisect/good-*" | tr ' \012' ' ' ) &&
340457 skip=$( git for-each-ref --format=' %(objectname)' \
341- " refs/bisect/skip-*" | tr ' \012' ' ' ) || exit
458+ " refs/bisect/skip-*" | tr ' \012' ' ' ) &&
459+
460+ # Maybe some merge bases must be tested first
461+ check_good_are_ancestors_of_bad " $bad " " $good " " $skip "
462+ # Return now if a checkout has already been done
463+ test " $? " -eq " 1" && return
342464
465+ # Get bisection information
343466 BISECT_OPT=' '
344467 test -n " $skip " && BISECT_OPT=' --bisect-all'
345-
346- bad=$( git rev-parse --verify refs/bisect/bad) &&
347- good=$( git for-each-ref --format=' ^%(objectname)' \
348- " refs/bisect/good-*" | tr ' \012' ' ' ) &&
349468 eval=" git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
350469 eval=" $eval $( cat " $GIT_DIR /BISECT_NAMES" ) " &&
351470 eval=$( filter_skipped " $eval " " $skip " ) &&
@@ -366,9 +485,7 @@ bisect_next() {
366485 # commit is also a "skip" commit (see above).
367486 exit_if_skipped_commits " $bisect_rev "
368487
369- echo " Bisecting: $bisect_nr revisions left to test after this"
370- git checkout -q " $bisect_rev " || exit
371- git show-branch " $bisect_rev "
488+ bisect_checkout " $bisect_rev " " $bisect_nr revisions left to test after this"
372489}
373490
374491bisect_visualize () {
@@ -415,6 +532,8 @@ bisect_clean_state() {
415532 do
416533 git update-ref -d $ref $hash || exit
417534 done
535+ rm -f " $GIT_DIR /BISECT_EXPECTED_REV" &&
536+ rm -f " $GIT_DIR /BISECT_ANCESTORS_OK" &&
418537 rm -f " $GIT_DIR /BISECT_LOG" &&
419538 rm -f " $GIT_DIR /BISECT_NAMES" &&
420539 rm -f " $GIT_DIR /BISECT_RUN" &&
0 commit comments