Skip to content

Commit

Permalink
Bisect: implement "bisect skip" to mark untestable revisions.
Browse files Browse the repository at this point in the history
When there are some "skip"ped revisions, we add the '--bisect-all'
option to "git rev-list --bisect-vars". Then we filter out the
"skip"ped revisions from the result of the rev-list command, and we
modify the "bisect_rev" var accordingly.

We don't always use "--bisect-all" because it is slower
than "--bisect-vars" or "--bisect".

When we cannot find for sure the first bad commit because of
"skip"ped commits, we print the hash of each possible first bad
commit and then we exit with code 2.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
  • Loading branch information
Christian Couder authored and Junio C Hamano committed Oct 27, 2007
1 parent 8fe26f4 commit 97e1c51
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 6 deletions.
125 changes: 119 additions & 6 deletions git-bisect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ git bisect replay <logfile>
replay bisection log.
git bisect log
show bisect log.
git bisect skip [<rev>...]
mark <rev>... untestable revisions.
git bisect run <cmd>...
use <cmd>... to automatically bisect.'

Expand Down Expand Up @@ -163,6 +165,28 @@ bisect_write_good() {
echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}

bisect_skip() {
bisect_autostart
case "$#" in
0) revs=$(git rev-parse --verify HEAD) || exit ;;
*) revs=$(git rev-parse --revs-only --no-flags "$@") &&
test '' != "$revs" || die "Bad rev input: $@" ;;
esac
for rev in $revs
do
rev=$(git rev-parse --verify "$rev^{commit}") || exit
bisect_write_skip "$rev"
echo "git-bisect skip $rev" >>"$GIT_DIR/BISECT_LOG"
done
bisect_auto_next
}

bisect_write_skip() {
rev="$1"
echo "$rev" >"$GIT_DIR/refs/bisect/skip-$rev"
echo "# skip: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
}

bisect_next_check() {
missing_good= missing_bad=
git show-ref -q --verify refs/bisect/bad || missing_bad=t
Expand Down Expand Up @@ -205,29 +229,114 @@ bisect_auto_next() {
bisect_next_check && bisect_next || :
}

filter_skipped() {
_eval="$1"
_skip="$2"

if [ -z "$_skip" ]; then
eval $_eval
return
fi

# Let's parse the output of:
# "git rev-list --bisect-vars --bisect-all ..."
eval $_eval | while read hash line
do
case "$VARS,$FOUND,$TRIED,$hash" in
# We display some vars.
1,*,*,*) echo "$hash $line" ;;

# Split line.
,*,*,---*) ;;

# We had nothing to search.
,,,bisect_rev*)
echo "bisect_rev="
VARS=1
;;

# We did not find a good bisect rev.
# This should happen only if the "bad"
# commit is also a "skip" commit.
,,*,bisect_rev*)
echo "bisect_rev=$TRIED"
VARS=1
;;

# We are searching.
,,*,*)
TRIED="${TRIED:+$TRIED|}$hash"
case "$_skip" in
*$hash*) ;;
*)
echo "bisect_rev=$hash"
echo "bisect_tried=\"$TRIED\""
FOUND=1
;;
esac
;;

# We have already found a rev to be tested.
,1,*,bisect_rev*) VARS=1 ;;
,1,*,*) ;;

# ???
*) die "filter_skipped error " \
"VARS: '$VARS' " \
"FOUND: '$FOUND' " \
"TRIED: '$TRIED' " \
"hash: '$hash' " \
"line: '$line'"
;;
esac
done
}

exit_if_skipped_commits () {
_tried=$1
if expr "$_tried" : ".*[|].*" > /dev/null ; then
echo "There are only 'skip'ped commit left to test."
echo "The first bad commit could be any of:"
echo "$_tried" | sed -e 's/[|]/\n/g'
echo "We cannot bisect more!"
exit 2
fi
}

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'

bad=$(git rev-parse --verify refs/bisect/bad) &&
good=$(git for-each-ref --format='^%(objectname)' \
"refs/bisect/good-*" | tr '[\012]' ' ') &&
eval="git rev-list --bisect-vars $good $bad --" &&
eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
eval=$(eval "$eval") &&
eval=$(filter_skipped "$eval" "$skip") &&
eval "$eval" || exit

if [ -z "$bisect_rev" ]; then
echo "$bad was both good and bad"
exit 1
fi
if [ "$bisect_rev" = "$bad" ]; then
exit_if_skipped_commits "$bisect_tried"
echo "$bisect_rev is first bad commit"
git diff-tree --pretty $bisect_rev
exit 0
fi

# We should exit here only if the "bad"
# commit is also a "skip" commit (see above).
exit_if_skipped_commits "$bisect_rev"

echo "Bisecting: $bisect_nr revisions left to test after this"
echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
git checkout -q new-bisect || exit
Expand Down Expand Up @@ -286,15 +395,17 @@ bisect_replay () {
eval "$cmd"
;;
good)
echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
bisect_write_good "$rev"
echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
;;
bad)
echo "$rev" >"$GIT_DIR/refs/bisect/bad"
echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
bisect_write_bad "$rev"
echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
;;
skip)
bisect_write_skip "$rev"
echo "git-bisect skip $rev" >>"$GIT_DIR/BISECT_LOG"
;;
*)
echo >&2 "?? what are you talking about?"
exit 1 ;;
Expand Down Expand Up @@ -362,6 +473,8 @@ case "$#" in
bisect_bad "$@" ;;
good)
bisect_good "$@" ;;
skip)
bisect_skip "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
bisect_next "$@" ;;
Expand Down
71 changes: 71 additions & 0 deletions t/t6030-bisect-porcelain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,63 @@ test_expect_success 'bisect start with one bad and good' '
git bisect next
'

# $HASH1 is good, $HASH4 is bad, we skip $HASH3
# but $HASH2 is bad,
# so we should find $HASH2 as the first bad commit
test_expect_success 'bisect skip: successfull result' '
git bisect reset &&
git bisect start $HASH4 $HASH1 &&
git bisect skip &&
git bisect bad > my_bisect_log.txt &&
grep "$HASH2 is first bad commit" my_bisect_log.txt &&
git bisect reset
'

# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2
# so we should not be able to tell the first bad commit
# among $HASH2, $HASH3 and $HASH4
test_expect_success 'bisect skip: cannot tell between 3 commits' '
git bisect start $HASH4 $HASH1 &&
git bisect skip || return 1
if git bisect skip > my_bisect_log.txt
then
echo Oops, should have failed.
false
else
test $? -eq 2 &&
grep "first bad commit could be any of" my_bisect_log.txt &&
! grep $HASH1 my_bisect_log.txt &&
grep $HASH2 my_bisect_log.txt &&
grep $HASH3 my_bisect_log.txt &&
grep $HASH4 my_bisect_log.txt &&
git bisect reset
fi
'

# $HASH1 is good, $HASH4 is bad, we skip $HASH3
# but $HASH2 is good,
# so we should not be able to tell the first bad commit
# among $HASH3 and $HASH4
test_expect_success 'bisect skip: cannot tell between 2 commits' '
git bisect start $HASH4 $HASH1 &&
git bisect skip || return 1
if git bisect good > my_bisect_log.txt
then
echo Oops, should have failed.
false
else
test $? -eq 2 &&
grep "first bad commit could be any of" my_bisect_log.txt &&
! grep $HASH1 my_bisect_log.txt &&
! grep $HASH2 my_bisect_log.txt &&
grep $HASH3 my_bisect_log.txt &&
grep $HASH4 my_bisect_log.txt &&
git bisect reset
fi
'

# We want to automatically find the commit that
# introduced "Another" into hello.
test_expect_success \
Expand Down Expand Up @@ -99,6 +156,20 @@ test_expect_success \
grep "$HASH4 is first bad commit" my_bisect_log.txt &&
git bisect reset'

# $HASH1 is good, $HASH5 is bad, we skip $HASH3
# but $HASH4 is good,
# so we should find $HASH5 as the first bad commit
HASH5=
test_expect_success 'bisect skip: add line and then a new test' '
add_line_into_file "5: Another new line." hello &&
HASH5=$(git rev-parse --verify HEAD) &&
git bisect start $HASH5 $HASH1 &&
git bisect skip &&
git bisect good > my_bisect_log.txt &&
grep "$HASH5 is first bad commit" my_bisect_log.txt &&
git bisect reset
'

#
#
test_done

0 comments on commit 97e1c51

Please sign in to comment.