From 50e62a8e703b78fbe59d5f98b1bb36464570a815 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:47:56 +0200 Subject: [PATCH 01/11] rev-list: implement --bisect-all This is Junio's patch with some stuff to make --bisect-all compatible with --bisect-vars. This option makes it possible to see all the potential bisection points. The best ones are displayed first. Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce --- builtin-rev-list.c | 100 ++++++++++++++++++++++++++++++++++++++------- log-tree.c | 2 +- log-tree.h | 1 + 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 33726b8d84..44393320e8 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -9,6 +9,7 @@ #include "revision.h" #include "list-objects.h" #include "builtin.h" +#include "log-tree.h" /* bits #0-15 in revision.h */ @@ -38,7 +39,8 @@ static const char rev_list_usage[] = " --left-right\n" " special purpose:\n" " --bisect\n" -" --bisect-vars" +" --bisect-vars\n" +" --bisect-all" ; static struct rev_info revs; @@ -74,6 +76,7 @@ static void show_commit(struct commit *commit) parents = parents->next; } } + show_decorations(commit); if (revs.commit_format == CMIT_FMT_ONELINE) putchar(' '); else @@ -278,6 +281,57 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) return best; } +struct commit_dist { + struct commit *commit; + int distance; +}; + +static int compare_commit_dist(const void *a_, const void *b_) +{ + struct commit_dist *a, *b; + + a = (struct commit_dist *)a_; + b = (struct commit_dist *)b_; + if (a->distance != b->distance) + return b->distance - a->distance; /* desc sort */ + return hashcmp(a->commit->object.sha1, b->commit->object.sha1); +} + +static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +{ + struct commit_list *p; + struct commit_dist *array = xcalloc(nr, sizeof(*array)); + int cnt, i; + + for (p = list, cnt = 0; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (revs.prune_fn && !(flags & TREECHANGE)) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + array[cnt].commit = p->item; + array[cnt].distance = distance; + cnt++; + } + qsort(array, cnt, sizeof(*array), compare_commit_dist); + for (p = list, i = 0; i < cnt; i++) { + struct name_decoration *r = xmalloc(sizeof(*r) + 100); + struct object *obj = &(array[i].commit->object); + + sprintf(r->name, "dist=%d", array[i].distance); + r->next = add_decoration(&name_decoration, obj, r); + p->item = array[i].commit; + p = p->next; + } + if (p) + p->next = NULL; + free(array); + return list; +} + /* * zero or positive weight is the number of interesting commits it can * reach, including itself. Especially, weight = 0 means it does not @@ -292,7 +346,8 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) * or positive distance. */ static struct commit_list *do_find_bisection(struct commit_list *list, - int nr, int *weights) + int nr, int *weights, + int find_all) { int n, counted; struct commit_list *p; @@ -351,7 +406,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list, clear_distance(list); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; counted++; } @@ -389,19 +444,22 @@ static struct commit_list *do_find_bisection(struct commit_list *list, weight_set(p, weight(q)); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; } } show_list("bisection 2 counted all", counted, nr, list); - /* Then find the best one */ - return best_bisection(list, nr); + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); } static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all) + int *reaches, int *all, + int find_all) { int nr, on_list; struct commit_list *p, *best, *next, *last; @@ -434,14 +492,13 @@ static struct commit_list *find_bisection(struct commit_list *list, weights = xcalloc(on_list, sizeof(*weights)); /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights); - + best = do_find_bisection(list, nr, weights, find_all); if (best) { - best->next = NULL; + if (!find_all) + best->next = NULL; *reaches = weight(best); } free(weights); - return best; } @@ -468,6 +525,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int i; int read_from_stdin = 0; int bisect_show_vars = 0; + int bisect_find_all = 0; git_config(git_default_config); init_revisions(&revs, prefix); @@ -490,6 +548,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--bisect-all")) { + bisect_list = 1; + bisect_find_all = 1; + continue; + } if (!strcmp(arg, "--bisect-vars")) { bisect_list = 1; bisect_show_vars = 1; @@ -536,9 +599,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) { int reaches = reaches, all = all; - revs.commits = find_bisection(revs.commits, &reaches, &all); + revs.commits = find_bisection(revs.commits, &reaches, &all, + bisect_find_all); if (bisect_show_vars) { int cnt; + char hex[41]; if (!revs.commits) return 1; /* @@ -550,15 +615,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) * A bisect set of size N has (N-1) commits further * to test, as we already know one bad one. */ - cnt = all-reaches; + cnt = all - reaches; if (cnt < reaches) cnt = reaches; + strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); + + if (bisect_find_all) { + traverse_commit_list(&revs, show_commit, show_object); + printf("------\n"); + } + printf("bisect_rev=%s\n" "bisect_nr=%d\n" "bisect_good=%d\n" "bisect_bad=%d\n" "bisect_all=%d\n", - sha1_to_hex(revs.commits->item->object.sha1), + hex, cnt - 1, all - reaches - 1, reaches - 1, diff --git a/log-tree.c b/log-tree.c index 62edd34455..3763ce94fc 100644 --- a/log-tree.c +++ b/log-tree.c @@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev) } } -static void show_decorations(struct commit *commit) +void show_decorations(struct commit *commit) { const char *prefix; struct name_decoration *decoration; diff --git a/log-tree.h b/log-tree.h index e82b56a20d..b33f7cd7ac 100644 --- a/log-tree.h +++ b/log-tree.h @@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt, const char *sep); +void show_decorations(struct commit *commit); #endif From 3ac9f612cbbb146743c0734c707e6cb512c43aa4 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:48:11 +0200 Subject: [PATCH 02/11] rev-list documentation: add "--bisect-all". Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce --- Documentation/git-rev-list.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 7cd0e8913e..485280423e 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -34,6 +34,7 @@ SYNOPSIS [ \--pretty | \--header ] [ \--bisect ] [ \--bisect-vars ] + [ \--bisect-all ] [ \--merge ] [ \--reverse ] [ \--walk-reflogs ] @@ -354,6 +355,21 @@ the expected number of commits to be tested if `bisect_rev` turns out to be bad to `bisect_bad`, and the number of commits we are bisecting right now to `bisect_all`. +--bisect-all:: + +This outputs all the commit objects between the included and excluded +commits, ordered by their distance to the included and excluded +commits. The farthest from them is displayed first. (This is the only +one displayed by `--bisect`.) + +This is useful because it makes it easy to choose a good commit to +test when you want to avoid to test some of them for some reason (they +may not compile for example). + +This option can be used along with `--bisect-vars`, in this case, +after all the sorted commit objects, there will be the same text as if +`--bisect-vars` had been used alone. + -- Commit Ordering From 8fe26f4481f274a6c752a6c13ac5da0460dbd1b6 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:48:23 +0200 Subject: [PATCH 03/11] Bisect: fix some white spaces and empty lines breakages. Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce --- git-bisect.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 388887a556..436ccf66ff 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -64,7 +64,7 @@ bisect_start() { branch=`cat "$GIT_DIR/head-name"` else branch=master - fi + fi git checkout $branch || exit ;; refs/heads/*) @@ -95,11 +95,11 @@ bisect_start() { arg="$1" case "$arg" in --) - shift + shift break ;; *) - rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { + rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { test $has_double_dash -eq 1 && die "'$arg' does not appear to be a valid revision" break @@ -110,10 +110,10 @@ bisect_start() { else bisect_write_good "$rev" fi - shift + shift ;; esac - done + done sq "$@" >"$GIT_DIR/BISECT_NAMES" echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" @@ -143,7 +143,7 @@ bisect_write_bad() { bisect_good() { bisect_autostart - case "$#" in + case "$#" in 0) revs=$(git rev-parse --verify HEAD) || exit ;; *) revs=$(git rev-parse --revs-only --no-flags "$@") && test '' != "$revs" || die "Bad rev input: $@" ;; @@ -153,7 +153,6 @@ bisect_good() { rev=$(git rev-parse --verify "$rev^{commit}") || exit bisect_write_good "$rev" echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - done bisect_auto_next } @@ -207,7 +206,7 @@ bisect_auto_next() { } bisect_next() { - case "$#" in 0) ;; *) usage ;; esac + case "$#" in 0) ;; *) usage ;; esac bisect_autostart bisect_next_check good @@ -255,7 +254,7 @@ bisect_reset() { exit 1 } branch="$1" ;; - *) + *) usage ;; esac if git checkout "$branch"; then From 97e1c51e15d94f523c67a499ecb633b0e20324cb Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:48:36 +0200 Subject: [PATCH 04/11] Bisect: implement "bisect skip" to mark untestable revisions. 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 Signed-off-by: Shawn O. Pearce --- git-bisect.sh | 125 ++++++++++++++++++++++++++++++++++-- t/t6030-bisect-porcelain.sh | 71 ++++++++++++++++++++ 2 files changed, 190 insertions(+), 6 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 436ccf66ff..cd46190302 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -17,6 +17,8 @@ git bisect replay replay bisection log. git bisect log show bisect log. +git bisect skip [...] + mark ... untestable revisions. git bisect run ... use ... to automatically bisect.' @@ -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 @@ -205,17 +229,97 @@ 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 @@ -223,11 +327,16 @@ bisect_next() { 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 @@ -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 ;; @@ -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 "$@" ;; diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 03cdba5808..db82259afe 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -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 \ @@ -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 From 55624f9af49733ca6ae4abc3cd21e0f9a4f9f486 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 24 Oct 2007 07:01:05 +0200 Subject: [PATCH 05/11] Bisect: refactor "bisect_write_*" functions. Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-bisect.sh | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index cd46190302..82aa40433b 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -108,9 +108,9 @@ bisect_start() { } if [ $bad_seen -eq 0 ]; then bad_seen=1 - bisect_write_bad "$rev" + bisect_write 'bad' "$rev" else - bisect_write_good "$rev" + bisect_write 'good' "$rev" fi shift ;; @@ -122,6 +122,18 @@ bisect_start() { bisect_auto_next } +bisect_write() { + state="$1" + rev="$2" + case "$state" in + bad) tag="$state" ;; + good|skip) tag="$state"-"$rev" ;; + *) die "Bad bisect_write argument: $state" ;; + esac + echo "$rev" >"$GIT_DIR/refs/bisect/$tag" + echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" +} + bisect_bad() { bisect_autostart case "$#" in @@ -132,17 +144,11 @@ bisect_bad() { *) usage ;; esac || exit - bisect_write_bad "$rev" + bisect_write 'bad' "$rev" echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } -bisect_write_bad() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" -} - bisect_good() { bisect_autostart case "$#" in @@ -153,18 +159,12 @@ bisect_good() { for rev in $revs do rev=$(git rev-parse --verify "$rev^{commit}") || exit - bisect_write_good "$rev" + bisect_write 'good' "$rev" echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" done bisect_auto_next } -bisect_write_good() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" -} - bisect_skip() { bisect_autostart case "$#" in @@ -175,18 +175,12 @@ bisect_skip() { for rev in $revs do rev=$(git rev-parse --verify "$rev^{commit}") || exit - bisect_write_skip "$rev" + 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 @@ -395,15 +389,15 @@ bisect_replay () { eval "$cmd" ;; good) - bisect_write_good "$rev" + bisect_write 'good' "$rev" echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" ;; bad) - bisect_write_bad "$rev" + bisect_write 'bad' "$rev" echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" ;; skip) - bisect_write_skip "$rev" + bisect_write 'skip' "$rev" echo "git-bisect skip $rev" >>"$GIT_DIR/BISECT_LOG" ;; *) From 737c74ee4219a81ebb616262579f63ce2a3f9698 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 24 Oct 2007 07:01:13 +0200 Subject: [PATCH 06/11] Bisect: refactor some logging into "bisect_write". Also use "die" instead of "echo >&2 something ; exit 1". And simplify "bisect_replay". Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-bisect.sh | 47 ++++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 82aa40433b..61a2956647 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -106,12 +106,11 @@ bisect_start() { die "'$arg' does not appear to be a valid revision" break } - if [ $bad_seen -eq 0 ]; then - bad_seen=1 - bisect_write 'bad' "$rev" - else - bisect_write 'good' "$rev" - fi + case $bad_seen in + 0) state='bad' ; bad_seen=1 ;; + *) state='good' ;; + esac + bisect_write "$state" "$rev" 'nolog' shift ;; esac @@ -125,6 +124,7 @@ bisect_start() { bisect_write() { state="$1" rev="$2" + nolog="$3" case "$state" in bad) tag="$state" ;; good|skip) tag="$state"-"$rev" ;; @@ -132,6 +132,7 @@ bisect_write() { esac echo "$rev" >"$GIT_DIR/refs/bisect/$tag" echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } bisect_bad() { @@ -145,7 +146,6 @@ bisect_bad() { usage ;; esac || exit bisect_write 'bad' "$rev" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } @@ -160,7 +160,6 @@ bisect_good() { do rev=$(git rev-parse --verify "$rev^{commit}") || exit bisect_write 'good' "$rev" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" done bisect_auto_next } @@ -176,7 +175,6 @@ bisect_skip() { 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 } @@ -352,10 +350,8 @@ bisect_reset() { else branch=master fi ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || { - echo >&2 "$1 does not seem to be a valid branch" - exit 1 - } + 1) git show-ref --verify --quiet -- "refs/heads/$1" || + die "$1 does not seem to be a valid branch" branch="$1" ;; *) usage ;; @@ -375,10 +371,7 @@ bisect_clean_state() { } bisect_replay () { - test -r "$1" || { - echo >&2 "cannot read $1 for replaying" - exit 1 - } + test -r "$1" || die "cannot read $1 for replaying" bisect_reset while read bisect command rev do @@ -386,23 +379,11 @@ bisect_replay () { case "$command" in start) cmd="bisect_start $rev" - eval "$cmd" - ;; - good) - bisect_write 'good' "$rev" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - ;; - bad) - 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" - ;; + eval "$cmd" ;; + good|bad|skip) + bisect_write "$command" "$rev" ;; *) - echo >&2 "?? what are you talking about?" - exit 1 ;; + die "?? what are you talking about?" ;; esac done <"$1" bisect_auto_next From 155fc795b9c613af79781ebec5fd8eda084e1636 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 24 Oct 2007 07:01:21 +0200 Subject: [PATCH 07/11] Bisect: refactor "bisect_{bad,good,skip}" into "bisect_state". Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-bisect.sh | 82 +++++++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 51 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 61a2956647..f8d0099059 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -135,47 +135,33 @@ bisect_write() { test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } -bisect_bad() { +bisect_state() { bisect_autostart - case "$#" in - 0) - rev=$(git rev-parse --verify HEAD) ;; - 1) - rev=$(git rev-parse --verify "$1^{commit}") ;; + state=$1 + case "$#,$state" in + 0,*) + die "Please call 'bisect_state' with at least one argument." ;; + 1,bad|1,good|1,skip) + rev=$(git rev-parse --verify HEAD) || + die "Bad rev input: HEAD" + bisect_write "$state" "$rev" ;; + 2,bad) + rev=$(git rev-parse --verify "$2^{commit}") || + die "Bad rev input: $2" + bisect_write "$state" "$rev" ;; + *,good|*,skip) + shift + revs=$(git rev-parse --revs-only --no-flags "$@") && + test '' != "$revs" || die "Bad rev input: $@" + for rev in $revs + do + rev=$(git rev-parse --verify "$rev^{commit}") || + die "Bad rev commit: $rev^{commit}" + bisect_write "$state" "$rev" + done ;; *) usage ;; - esac || exit - bisect_write 'bad' "$rev" - bisect_auto_next -} - -bisect_good() { - 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 'good' "$rev" - done - bisect_auto_next -} - -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" - done bisect_auto_next } @@ -405,24 +391,22 @@ bisect_run () { exit $res fi - # Use "bisect_good" or "bisect_bad" - # depending on run success or failure. + # Find current state depending on run success or failure. if [ $res -gt 0 ]; then - next_bisect='bisect_bad' + state='bad' else - next_bisect='bisect_good' + state='good' fi - # We have to use a subshell because bisect_good or - # bisect_bad functions can exit. - ( $next_bisect > "$GIT_DIR/BISECT_RUN" ) + # We have to use a subshell because "bisect_state" can exit. + ( bisect_state $state > "$GIT_DIR/BISECT_RUN" ) res=$? cat "$GIT_DIR/BISECT_RUN" if [ $res -ne 0 ]; then echo >&2 "bisect run failed:" - echo >&2 "$next_bisect exited with error code $res" + echo >&2 "'bisect_state $state' exited with error code $res" exit $res fi @@ -444,12 +428,8 @@ case "$#" in case "$cmd" in start) bisect_start "$@" ;; - bad) - bisect_bad "$@" ;; - good) - bisect_good "$@" ;; - skip) - bisect_skip "$@" ;; + bad|good|skip) + bisect_state "$cmd" "$@" ;; next) # Not sure we want "next" at the UI level anymore. bisect_next "$@" ;; From c39ce91827e6f8c9e2cbfb482f90fe6f58d4d573 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:49:23 +0200 Subject: [PATCH 08/11] Bisect: add "bisect skip" to the documentation. Also fix "bisect bad" and "bisect good" short usage description. Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 1072fb87d1..785f381423 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending on the subcommand: git bisect start [ [...]] [--] [...] - git bisect bad - git bisect good + git bisect bad [] + git bisect good [...] + git bisect skip [...] git bisect reset [] git bisect visualize git bisect replay @@ -134,6 +135,20 @@ $ git reset --hard HEAD~3 # try 3 revs before what Then compile and test the one you chose to try. After that, tell bisect what the result was as usual. +Bisect skip +~~~~~~~~~~~~ + +Instead of choosing by yourself a nearby commit, you may just want git +to do it for you using: + +------------ +$ git bisect skip # Current version cannot be tested +------------ + +But computing the commit to test may be slower afterwards and git may +eventually not be able to tell the first bad among a bad and one or +more "skip"ped commits. + Cutting down bisection by giving more parameters to bisect start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 37f9fd0dde454db08d342e5ce7625e012507bb9b Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 22 Oct 2007 07:49:39 +0200 Subject: [PATCH 09/11] Bisect: add a "bisect replay" test case. Signed-off-by: Christian Couder Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t6030-bisect-porcelain.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index db82259afe..16d0c4a90e 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -167,6 +167,13 @@ test_expect_success 'bisect skip: add line and then a new test' ' git bisect skip && git bisect good > my_bisect_log.txt && grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect log > log_to_replay.txt + git bisect reset +' + +test_expect_success 'bisect skip and bisect replay' ' + git bisect replay log_to_replay.txt > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && git bisect reset ' From 71b0251cdd2cc35a983e21d4e71285db56d2a519 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 26 Oct 2007 05:39:37 +0200 Subject: [PATCH 10/11] Bisect run: "skip" current commit if script exit code is 125. This is incompatible with previous versions because an exit code of 125 used to mark current commit as "bad". But hopefully this exit code is not much used by test scripts or other programs. (126 and 127 are used by POSIX compliant shells to mean "found but not executable" and "command not found", respectively.) Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Documentation/git-bisect.txt | 10 ++++++--- git-bisect.sh | 11 +++++++++- t/t6030-bisect-porcelain.sh | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 785f381423..4795349c10 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -182,14 +182,18 @@ $ git bisect run my_script ------------ Note that the "run" script (`my_script` in the above example) should -exit with code 0 in case the current source code is good and with a -code between 1 and 127 (included) in case the current source code is -bad. +exit with code 0 in case the current source code is good. Exit with a +code between 1 and 127 (inclusive), except 125, if the current +source code is bad. Any other exit code will abort the automatic bisect process. (A program that does "exit(-1)" leaves $? = 255, see exit(3) manual page, the value is chopped with "& 0377".) +The special exit code 125 should be used when the current source code +cannot be tested. If the "run" script exits with this code, the current +revision will be skipped, see `git bisect skip` above. + You may often find that during bisect you want to have near-constant tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or "revision that does not have this commit needs this patch applied to diff --git a/git-bisect.sh b/git-bisect.sh index f8d0099059..180c6c280c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -392,7 +392,10 @@ bisect_run () { fi # Find current state depending on run success or failure. - if [ $res -gt 0 ]; then + # A special exit code of 125 means cannot test. + if [ $res -eq 125 ]; then + state='skip' + elif [ $res -gt 0 ]; then state='bad' else state='good' @@ -404,6 +407,12 @@ bisect_run () { cat "$GIT_DIR/BISECT_RUN" + if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ + > /dev/null; then + echo >&2 "bisect run cannot continue any more" + exit $res + fi + if [ $res -ne 0 ]; then echo >&2 "bisect run failed:" echo >&2 "'bisect_state $state' exited with error code $res" diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 16d0c4a90e..53956c08e2 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -177,6 +177,46 @@ test_expect_success 'bisect skip and bisect replay' ' git bisect reset ' +HASH6= +test_expect_success 'bisect run & skip: cannot tell between 2' ' + add_line_into_file "6: Yet a line." hello && + HASH6=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "grep line hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH6 $HASH1 && + if git bisect run ./test_script.sh > 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 $HASH3 my_bisect_log.txt && + ! grep $HASH6 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + grep $HASH5 my_bisect_log.txt + fi +' + +HASH7= +test_expect_success 'bisect run & skip: find first bad' ' + git bisect reset && + add_line_into_file "7: Should be the last line." hello && + HASH7=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh && + echo "grep Yet hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH7 $HASH1 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH6 is first bad commit" my_bisect_log.txt +' + # # test_done From 6ca8b977e4f678050db8fcb0eec2091dd44a2bd0 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 29 Oct 2007 05:31:52 +0100 Subject: [PATCH 11/11] Bisect: add "skip" to the short usage string. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- git-bisect.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 180c6c280c..b74f44df60 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,12 +1,14 @@ #!/bin/sh -USAGE='[start|bad|good|next|reset|visualize|replay|log|run]' +USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect start [ [...]] [--] [...] reset bisect state and start bisection. git bisect bad [] mark a known-bad revision. git bisect good [...] mark ... known-good revisions. +git bisect skip [...] + mark ... untestable revisions. git bisect next find next bisection to test and check it out. git bisect reset [] @@ -17,8 +19,6 @@ git bisect replay replay bisection log. git bisect log show bisect log. -git bisect skip [...] - mark ... untestable revisions. git bisect run ... use ... to automatically bisect.'