Merge branch 'cc/bisect'
* cc/bisect: bisect: remove "checkout_done" variable used when checking merge bases bisect: only check merge bases when needed bisect: test merge base if good rev is not an ancestor of bad rev
This commit is contained in:
commit
cb2c7daf52
175
git-bisect.sh
175
git-bisect.sh
@ -172,6 +172,25 @@ bisect_write() {
|
||||
test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
|
||||
}
|
||||
|
||||
is_expected_rev() {
|
||||
test -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
|
||||
test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV")
|
||||
}
|
||||
|
||||
mark_expected_rev() {
|
||||
echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV"
|
||||
}
|
||||
|
||||
check_expected_revs() {
|
||||
for _rev in "$@"; do
|
||||
if ! is_expected_rev "$_rev"; then
|
||||
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK"
|
||||
rm -f "$GIT_DIR/BISECT_EXPECTED_REV"
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
bisect_state() {
|
||||
bisect_autostart
|
||||
state=$1
|
||||
@ -181,7 +200,8 @@ bisect_state() {
|
||||
1,bad|1,good|1,skip)
|
||||
rev=$(git rev-parse --verify HEAD) ||
|
||||
die "Bad rev input: HEAD"
|
||||
bisect_write "$state" "$rev" ;;
|
||||
bisect_write "$state" "$rev"
|
||||
check_expected_revs "$rev" ;;
|
||||
2,bad|*,good|*,skip)
|
||||
shift
|
||||
eval=''
|
||||
@ -191,7 +211,8 @@ bisect_state() {
|
||||
die "Bad rev input: $rev"
|
||||
eval="$eval bisect_write '$state' '$sha'; "
|
||||
done
|
||||
eval "$eval" ;;
|
||||
eval "$eval"
|
||||
check_expected_revs "$@" ;;
|
||||
*,bad)
|
||||
die "'git bisect bad' can take only one argument." ;;
|
||||
*)
|
||||
@ -243,33 +264,18 @@ bisect_auto_next() {
|
||||
bisect_next_check && bisect_next || :
|
||||
}
|
||||
|
||||
eval_rev_list() {
|
||||
_eval="$1"
|
||||
|
||||
eval $_eval
|
||||
res=$?
|
||||
|
||||
if [ $res -ne 0 ]; then
|
||||
echo >&2 "'git rev-list --bisect-vars' failed:"
|
||||
echo >&2 "maybe you mistake good and bad revs?"
|
||||
exit $res
|
||||
fi
|
||||
|
||||
return $res
|
||||
}
|
||||
|
||||
filter_skipped() {
|
||||
_eval="$1"
|
||||
_skip="$2"
|
||||
|
||||
if [ -z "$_skip" ]; then
|
||||
eval_rev_list "$_eval"
|
||||
eval "$_eval"
|
||||
return
|
||||
fi
|
||||
|
||||
# Let's parse the output of:
|
||||
# "git rev-list --bisect-vars --bisect-all ..."
|
||||
eval_rev_list "$_eval" | while read hash line
|
||||
eval "$_eval" | while read hash line
|
||||
do
|
||||
case "$VARS,$FOUND,$TRIED,$hash" in
|
||||
# We display some vars.
|
||||
@ -332,20 +338,133 @@ exit_if_skipped_commits () {
|
||||
fi
|
||||
}
|
||||
|
||||
bisect_checkout() {
|
||||
_rev="$1"
|
||||
_msg="$2"
|
||||
echo "Bisecting: $_msg"
|
||||
mark_expected_rev "$_rev"
|
||||
git checkout -q "$_rev" || exit
|
||||
git show-branch "$_rev"
|
||||
}
|
||||
|
||||
is_among() {
|
||||
_rev="$1"
|
||||
_list="$2"
|
||||
case "$_list" in *$_rev*) return 0 ;; esac
|
||||
return 1
|
||||
}
|
||||
|
||||
handle_bad_merge_base() {
|
||||
_badmb="$1"
|
||||
_good="$2"
|
||||
if is_expected_rev "$_badmb"; then
|
||||
cat >&2 <<EOF
|
||||
The merge base $_badmb is bad.
|
||||
This means the bug has been fixed between $_badmb and [$_good].
|
||||
EOF
|
||||
exit 3
|
||||
else
|
||||
cat >&2 <<EOF
|
||||
Some good revs are not ancestor of the bad rev.
|
||||
git bisect cannot work properly in this case.
|
||||
Maybe you mistake good and bad revs?
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
handle_skipped_merge_base() {
|
||||
_mb="$1"
|
||||
_bad="$2"
|
||||
_good="$3"
|
||||
cat >&2 <<EOF
|
||||
Warning: the merge base between $_bad and [$_good] must be skipped.
|
||||
So we cannot be sure the first bad commit is between $_mb and $_bad.
|
||||
We continue anyway.
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# "check_merge_bases" checks that merge bases are not "bad".
|
||||
#
|
||||
# - If one is "good", that's good, we have nothing to do.
|
||||
# - If one is "bad", it means the user assumed something wrong
|
||||
# and we must exit.
|
||||
# - If one is "skipped", we can't know but we should warn.
|
||||
# - If we don't know, we should check it out and ask the user to test.
|
||||
#
|
||||
# In the last case we will return 1, and otherwise 0.
|
||||
#
|
||||
check_merge_bases() {
|
||||
_bad="$1"
|
||||
_good="$2"
|
||||
_skip="$3"
|
||||
for _mb in $(git merge-base --all $_bad $_good)
|
||||
do
|
||||
if is_among "$_mb" "$_good"; then
|
||||
continue
|
||||
elif test "$_mb" = "$_bad"; then
|
||||
handle_bad_merge_base "$_bad" "$_good"
|
||||
elif is_among "$_mb" "$_skip"; then
|
||||
handle_skipped_merge_base "$_mb" "$_bad" "$_good"
|
||||
else
|
||||
bisect_checkout "$_mb" "a merge base must be tested"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# "check_good_are_ancestors_of_bad" checks that all "good" revs are
|
||||
# ancestor of the "bad" rev.
|
||||
#
|
||||
# If that's not the case, we need to check the merge bases.
|
||||
# If a merge base must be tested by the user we return 1 and
|
||||
# otherwise 0.
|
||||
#
|
||||
check_good_are_ancestors_of_bad() {
|
||||
test -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
|
||||
return
|
||||
|
||||
_bad="$1"
|
||||
_good=$(echo $2 | sed -e 's/\^//g')
|
||||
_skip="$3"
|
||||
|
||||
# Bisecting with no good rev is ok
|
||||
test -z "$_good" && return
|
||||
|
||||
_side=$(git rev-list $_good ^$_bad)
|
||||
if test -n "$_side"; then
|
||||
# Return if a checkout was done
|
||||
check_merge_bases "$_bad" "$_good" "$_skip" || return
|
||||
fi
|
||||
|
||||
: > "$GIT_DIR/BISECT_ANCESTORS_OK"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
bisect_next() {
|
||||
case "$#" in 0) ;; *) usage ;; esac
|
||||
bisect_autostart
|
||||
bisect_next_check good
|
||||
|
||||
skip=$(git for-each-ref --format='%(objectname)' \
|
||||
"refs/bisect/skip-*" | tr '\012' ' ') || exit
|
||||
|
||||
BISECT_OPT=''
|
||||
test -n "$skip" && BISECT_OPT='--bisect-all'
|
||||
|
||||
# Get bad, good and skipped revs
|
||||
bad=$(git rev-parse --verify refs/bisect/bad) &&
|
||||
good=$(git for-each-ref --format='^%(objectname)' \
|
||||
"refs/bisect/good-*" | tr '\012' ' ') &&
|
||||
skip=$(git for-each-ref --format='%(objectname)' \
|
||||
"refs/bisect/skip-*" | tr '\012' ' ') &&
|
||||
|
||||
# Maybe some merge bases must be tested first
|
||||
check_good_are_ancestors_of_bad "$bad" "$good" "$skip"
|
||||
# Return now if a checkout has already been done
|
||||
test "$?" -eq "1" && return
|
||||
|
||||
# Get bisection information
|
||||
BISECT_OPT=''
|
||||
test -n "$skip" && BISECT_OPT='--bisect-all'
|
||||
eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
|
||||
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
|
||||
eval=$(filter_skipped "$eval" "$skip") &&
|
||||
@ -366,9 +485,7 @@ bisect_next() {
|
||||
# commit is also a "skip" commit (see above).
|
||||
exit_if_skipped_commits "$bisect_rev"
|
||||
|
||||
echo "Bisecting: $bisect_nr revisions left to test after this"
|
||||
git checkout -q "$bisect_rev" || exit
|
||||
git show-branch "$bisect_rev"
|
||||
bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
|
||||
}
|
||||
|
||||
bisect_visualize() {
|
||||
@ -415,6 +532,8 @@ bisect_clean_state() {
|
||||
do
|
||||
git update-ref -d $ref $hash || exit
|
||||
done
|
||||
rm -f "$GIT_DIR/BISECT_EXPECTED_REV" &&
|
||||
rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" &&
|
||||
rm -f "$GIT_DIR/BISECT_LOG" &&
|
||||
rm -f "$GIT_DIR/BISECT_NAMES" &&
|
||||
rm -f "$GIT_DIR/BISECT_RUN" &&
|
||||
|
@ -350,6 +350,120 @@ test_expect_success 'bisect does not create a "bisect" branch' '
|
||||
git branch -D bisect
|
||||
'
|
||||
|
||||
# This creates a "side" branch to test "siblings" cases.
|
||||
#
|
||||
# H1-H2-H3-H4-H5-H6-H7 <--other
|
||||
# \
|
||||
# S5-S6-S7 <--side
|
||||
#
|
||||
test_expect_success 'side branch creation' '
|
||||
git bisect reset &&
|
||||
git checkout -b side $HASH4 &&
|
||||
add_line_into_file "5(side): first line on a side branch" hello2 &&
|
||||
SIDE_HASH5=$(git rev-parse --verify HEAD) &&
|
||||
add_line_into_file "6(side): second line on a side branch" hello2 &&
|
||||
SIDE_HASH6=$(git rev-parse --verify HEAD) &&
|
||||
add_line_into_file "7(side): third line on a side branch" hello2 &&
|
||||
SIDE_HASH7=$(git rev-parse --verify HEAD)
|
||||
'
|
||||
|
||||
test_expect_success 'good merge base when good and bad are siblings' '
|
||||
git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
|
||||
grep "merge base must be tested" my_bisect_log.txt &&
|
||||
grep $HASH4 my_bisect_log.txt &&
|
||||
git bisect good > my_bisect_log.txt &&
|
||||
test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
|
||||
grep $HASH6 my_bisect_log.txt &&
|
||||
git bisect reset
|
||||
'
|
||||
test_expect_success 'skipped merge base when good and bad are siblings' '
|
||||
git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt &&
|
||||
grep "merge base must be tested" my_bisect_log.txt &&
|
||||
grep $HASH4 my_bisect_log.txt &&
|
||||
git bisect skip > my_bisect_log.txt 2>&1 &&
|
||||
grep "Warning" my_bisect_log.txt &&
|
||||
grep $SIDE_HASH6 my_bisect_log.txt &&
|
||||
git bisect reset
|
||||
'
|
||||
|
||||
test_expect_success 'bad merge base when good and bad are siblings' '
|
||||
git bisect start "$HASH7" HEAD > my_bisect_log.txt &&
|
||||
grep "merge base must be tested" my_bisect_log.txt &&
|
||||
grep $HASH4 my_bisect_log.txt &&
|
||||
test_must_fail git bisect bad > my_bisect_log.txt 2>&1 &&
|
||||
grep "merge base $HASH4 is bad" my_bisect_log.txt &&
|
||||
grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
|
||||
git bisect reset
|
||||
'
|
||||
|
||||
# This creates a few more commits (A and B) to test "siblings" cases
|
||||
# when a good and a bad rev have many merge bases.
|
||||
#
|
||||
# We should have the following:
|
||||
#
|
||||
# H1-H2-H3-H4-H5-H6-H7
|
||||
# \ \ \
|
||||
# S5-A \
|
||||
# \ \
|
||||
# S6-S7----B
|
||||
#
|
||||
# And there A and B have 2 merge bases (S5 and H5) that should be
|
||||
# reported by "git merge-base --all A B".
|
||||
#
|
||||
test_expect_success 'many merge bases creation' '
|
||||
git checkout "$SIDE_HASH5" &&
|
||||
git merge -m "merge HASH5 and SIDE_HASH5" "$HASH5" &&
|
||||
A_HASH=$(git rev-parse --verify HEAD) &&
|
||||
git checkout side &&
|
||||
git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" &&
|
||||
B_HASH=$(git rev-parse --verify HEAD) &&
|
||||
git merge-base --all "$A_HASH" "$B_HASH" > merge_bases.txt &&
|
||||
test $(wc -l < merge_bases.txt) = "2" &&
|
||||
grep "$HASH5" merge_bases.txt &&
|
||||
grep "$SIDE_HASH5" merge_bases.txt
|
||||
'
|
||||
|
||||
test_expect_success 'good merge bases when good and bad are siblings' '
|
||||
git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
|
||||
grep "merge base must be tested" my_bisect_log.txt &&
|
||||
git bisect good > my_bisect_log2.txt &&
|
||||
grep "merge base must be tested" my_bisect_log2.txt &&
|
||||
{
|
||||
{
|
||||
grep "$SIDE_HASH5" my_bisect_log.txt &&
|
||||
grep "$HASH5" my_bisect_log2.txt
|
||||
} || {
|
||||
grep "$SIDE_HASH5" my_bisect_log2.txt &&
|
||||
grep "$HASH5" my_bisect_log.txt
|
||||
}
|
||||
} &&
|
||||
git bisect reset
|
||||
'
|
||||
|
||||
check_trace() {
|
||||
grep "$1" "$GIT_TRACE" | grep "\^$2" | grep "$3" >/dev/null
|
||||
}
|
||||
|
||||
test_expect_success 'optimized merge base checks' '
|
||||
GIT_TRACE="$(pwd)/trace.log" &&
|
||||
export GIT_TRACE &&
|
||||
git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
|
||||
grep "merge base must be tested" my_bisect_log.txt &&
|
||||
grep "$HASH4" my_bisect_log.txt &&
|
||||
check_trace "rev-list" "$HASH7" "$SIDE_HASH7" &&
|
||||
git bisect good > my_bisect_log2.txt &&
|
||||
test -f ".git/BISECT_ANCESTORS_OK" &&
|
||||
test "$HASH6" = $(git rev-parse --verify HEAD) &&
|
||||
: > "$GIT_TRACE" &&
|
||||
git bisect bad > my_bisect_log3.txt &&
|
||||
test_must_fail check_trace "rev-list" "$HASH6" "$SIDE_HASH7" &&
|
||||
git bisect good "$A_HASH" > my_bisect_log4.txt &&
|
||||
grep "merge base must be tested" my_bisect_log4.txt &&
|
||||
test_must_fail test -f ".git/BISECT_ANCESTORS_OK" &&
|
||||
check_trace "rev-list" "$HASH6" "$A_HASH" &&
|
||||
unset GIT_TRACE
|
||||
'
|
||||
|
||||
#
|
||||
#
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user