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"
|
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_state() {
|
||||||
bisect_autostart
|
bisect_autostart
|
||||||
state=$1
|
state=$1
|
||||||
@ -181,7 +200,8 @@ bisect_state() {
|
|||||||
1,bad|1,good|1,skip)
|
1,bad|1,good|1,skip)
|
||||||
rev=$(git rev-parse --verify HEAD) ||
|
rev=$(git rev-parse --verify HEAD) ||
|
||||||
die "Bad rev input: HEAD"
|
die "Bad rev input: HEAD"
|
||||||
bisect_write "$state" "$rev" ;;
|
bisect_write "$state" "$rev"
|
||||||
|
check_expected_revs "$rev" ;;
|
||||||
2,bad|*,good|*,skip)
|
2,bad|*,good|*,skip)
|
||||||
shift
|
shift
|
||||||
eval=''
|
eval=''
|
||||||
@ -191,7 +211,8 @@ bisect_state() {
|
|||||||
die "Bad rev input: $rev"
|
die "Bad rev input: $rev"
|
||||||
eval="$eval bisect_write '$state' '$sha'; "
|
eval="$eval bisect_write '$state' '$sha'; "
|
||||||
done
|
done
|
||||||
eval "$eval" ;;
|
eval "$eval"
|
||||||
|
check_expected_revs "$@" ;;
|
||||||
*,bad)
|
*,bad)
|
||||||
die "'git bisect bad' can take only one argument." ;;
|
die "'git bisect bad' can take only one argument." ;;
|
||||||
*)
|
*)
|
||||||
@ -243,33 +264,18 @@ bisect_auto_next() {
|
|||||||
bisect_next_check && bisect_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() {
|
filter_skipped() {
|
||||||
_eval="$1"
|
_eval="$1"
|
||||||
_skip="$2"
|
_skip="$2"
|
||||||
|
|
||||||
if [ -z "$_skip" ]; then
|
if [ -z "$_skip" ]; then
|
||||||
eval_rev_list "$_eval"
|
eval "$_eval"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Let's parse the output of:
|
# Let's parse the output of:
|
||||||
# "git rev-list --bisect-vars --bisect-all ..."
|
# "git rev-list --bisect-vars --bisect-all ..."
|
||||||
eval_rev_list "$_eval" | while read hash line
|
eval "$_eval" | while read hash line
|
||||||
do
|
do
|
||||||
case "$VARS,$FOUND,$TRIED,$hash" in
|
case "$VARS,$FOUND,$TRIED,$hash" in
|
||||||
# We display some vars.
|
# We display some vars.
|
||||||
@ -332,20 +338,133 @@ exit_if_skipped_commits () {
|
|||||||
fi
|
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() {
|
bisect_next() {
|
||||||
case "$#" in 0) ;; *) usage ;; esac
|
case "$#" in 0) ;; *) usage ;; esac
|
||||||
bisect_autostart
|
bisect_autostart
|
||||||
bisect_next_check good
|
bisect_next_check good
|
||||||
|
|
||||||
skip=$(git for-each-ref --format='%(objectname)' \
|
# Get bad, good and skipped revs
|
||||||
"refs/bisect/skip-*" | tr '\012' ' ') || exit
|
|
||||||
|
|
||||||
BISECT_OPT=''
|
|
||||||
test -n "$skip" && BISECT_OPT='--bisect-all'
|
|
||||||
|
|
||||||
bad=$(git rev-parse --verify refs/bisect/bad) &&
|
bad=$(git rev-parse --verify refs/bisect/bad) &&
|
||||||
good=$(git for-each-ref --format='^%(objectname)' \
|
good=$(git for-each-ref --format='^%(objectname)' \
|
||||||
"refs/bisect/good-*" | tr '\012' ' ') &&
|
"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="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
|
||||||
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
|
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
|
||||||
eval=$(filter_skipped "$eval" "$skip") &&
|
eval=$(filter_skipped "$eval" "$skip") &&
|
||||||
@ -366,9 +485,7 @@ bisect_next() {
|
|||||||
# commit is also a "skip" commit (see above).
|
# commit is also a "skip" commit (see above).
|
||||||
exit_if_skipped_commits "$bisect_rev"
|
exit_if_skipped_commits "$bisect_rev"
|
||||||
|
|
||||||
echo "Bisecting: $bisect_nr revisions left to test after this"
|
bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
|
||||||
git checkout -q "$bisect_rev" || exit
|
|
||||||
git show-branch "$bisect_rev"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bisect_visualize() {
|
bisect_visualize() {
|
||||||
@ -415,6 +532,8 @@ bisect_clean_state() {
|
|||||||
do
|
do
|
||||||
git update-ref -d $ref $hash || exit
|
git update-ref -d $ref $hash || exit
|
||||||
done
|
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_LOG" &&
|
||||||
rm -f "$GIT_DIR/BISECT_NAMES" &&
|
rm -f "$GIT_DIR/BISECT_NAMES" &&
|
||||||
rm -f "$GIT_DIR/BISECT_RUN" &&
|
rm -f "$GIT_DIR/BISECT_RUN" &&
|
||||||
|
@ -350,6 +350,120 @@ test_expect_success 'bisect does not create a "bisect" branch' '
|
|||||||
git branch -D bisect
|
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
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user