Merge branch 'cc/bisect' (early part)

* 'cc/bisect' (early part):
  bisect: make "git bisect" use new "--next-all" bisect-helper function
  bisect: add "check_good_are_ancestors_of_bad" function
  bisect: implement the "check_merge_bases" function
  bisect: automatically sort sha1_array if needed when looking it up
  bisect: make skipped array functions more generic
  bisect: remove too much function nesting
  bisect: use new "struct argv_array" to prepare argv for "setup_revisions"
  bisect: store good revisions in a "sha1_array"
  bisect: implement "rev_argv_push" to fill an argv with revs
  bisect: use "sha1_array" to store skipped revisions
  am: simplify "sq" function by using "git rev-parse --sq-quote"
  bisect: use "git rev-parse --sq-quote" instead of a custom "sq" function
  rev-parse: add --sq-quote to shell quote arguments
  rev-list: remove stringed output flag from "show_bisect_vars"
  bisect--helper: remove "--next-vars" option as it is now useless
  bisect: use "git bisect--helper --next-exit" in "git-bisect.sh"
  bisect--helper: add "--next-exit" to output bisect results
  bisect: move common bisect functionality to "bisect_common"
  rev-list: refactor printing bisect vars
  rev-list: make "estimate_bisect_steps" non static
This commit is contained in:
Junio C Hamano 2009-05-23 01:41:27 -07:00
commit d34f715853
9 changed files with 499 additions and 276 deletions

View File

@ -30,6 +30,11 @@ OPTIONS
Only meaningful in `--parseopt` mode. Tells the option parser to echo Only meaningful in `--parseopt` mode. Tells the option parser to echo
out the first `--` met instead of skipping it. out the first `--` met instead of skipping it.
--sq-quote::
Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE
section below). In contrast to the `--sq` option below, this
mode does only quoting. Nothing else is done to command input.
--revs-only:: --revs-only::
Do not output flags and parameters not meant for Do not output flags and parameters not meant for
'git-rev-list' command. 'git-rev-list' command.
@ -64,7 +69,8 @@ OPTIONS
properly quoted for consumption by shell. Useful when properly quoted for consumption by shell. Useful when
you expect your parameter to contain whitespaces and you expect your parameter to contain whitespaces and
newlines (e.g. when using pickaxe `-S` with newlines (e.g. when using pickaxe `-S` with
'git-diff-\*'). 'git-diff-\*'). In contrast to the `--sq-quote` option,
the command input is still interpreted as usual.
--not:: --not::
When showing object names, prefix them with '{caret}' and When showing object names, prefix them with '{caret}' and
@ -406,6 +412,33 @@ C? option C with an optional argument"
eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?`
------------ ------------
SQ-QUOTE
--------
In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a
single line suitable for `sh(1)` `eval`. This line is made by
normalizing the arguments following `--sq-quote`. Nothing other than
quoting the arguments is done.
If you want command input to still be interpreted as usual by
'git-rev-parse' before the output is shell quoted, see the `--sq`
option.
Example
~~~~~~~
------------
$ cat >your-git-script.sh <<\EOF
#!/bin/sh
args=$(git rev-parse --sq-quote "$@") # quote user-supplied arguments
command="git frotz -n24 $args" # and use it inside a handcrafted
# command line
eval "$command"
EOF
$ sh your-git-script.sh "a b'c"
------------
EXAMPLES EXAMPLES
-------- --------

View File

@ -1249,7 +1249,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
$(QUIET_GEN)$(RM) $@ $@+ && \ $(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
$@.sh >$@+ && \ $@.sh >$@+ && \

456
bisect.c
View File

@ -6,15 +6,30 @@
#include "list-objects.h" #include "list-objects.h"
#include "quote.h" #include "quote.h"
#include "sha1-lookup.h" #include "sha1-lookup.h"
#include "run-command.h"
#include "bisect.h" #include "bisect.h"
static unsigned char (*skipped_sha1)[20]; struct sha1_array {
static int skipped_sha1_nr; unsigned char (*sha1)[20];
static int skipped_sha1_alloc; int sha1_nr;
int sha1_alloc;
int sorted;
};
static const char **rev_argv; static struct sha1_array good_revs;
static int rev_argv_nr; static struct sha1_array skipped_revs;
static int rev_argv_alloc;
static const unsigned char *current_bad_sha1;
struct argv_array {
const char **argv;
int argv_nr;
int argv_alloc;
};
static const char *argv_diff_tree[] = {"diff-tree", "--pretty", NULL, NULL};
static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
/* bits #0-15 in revision.h */ /* bits #0-15 in revision.h */
@ -398,23 +413,37 @@ struct commit_list *find_bisection(struct commit_list *list,
return best; return best;
} }
static void argv_array_push(struct argv_array *array, const char *string)
{
ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc);
array->argv[array->argv_nr++] = string;
}
static void argv_array_push_sha1(struct argv_array *array,
const unsigned char *sha1,
const char *format)
{
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, format, sha1_to_hex(sha1));
argv_array_push(array, strbuf_detach(&buf, NULL));
}
static void sha1_array_push(struct sha1_array *array,
const unsigned char *sha1)
{
ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc);
hashcpy(array->sha1[array->sha1_nr++], sha1);
}
static int register_ref(const char *refname, const unsigned char *sha1, static int register_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data) int flags, void *cb_data)
{ {
if (!strcmp(refname, "bad")) { if (!strcmp(refname, "bad")) {
ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); current_bad_sha1 = sha1;
rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1));
} else if (!prefixcmp(refname, "good-")) { } else if (!prefixcmp(refname, "good-")) {
const char *hex = sha1_to_hex(sha1); sha1_array_push(&good_revs, sha1);
char *good = xmalloc(strlen(hex) + 2);
*good = '^';
memcpy(good + 1, hex, strlen(hex) + 1);
ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
rev_argv[rev_argv_nr++] = good;
} else if (!prefixcmp(refname, "skip-")) { } else if (!prefixcmp(refname, "skip-")) {
ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1, sha1_array_push(&skipped_revs, sha1);
skipped_sha1_alloc);
hashcpy(skipped_sha1[skipped_sha1_nr++], sha1);
} }
return 0; return 0;
@ -425,7 +454,7 @@ static int read_bisect_refs(void)
return for_each_ref_in("refs/bisect/", register_ref, NULL); return for_each_ref_in("refs/bisect/", register_ref, NULL);
} }
void read_bisect_paths(void) void read_bisect_paths(struct argv_array *array)
{ {
struct strbuf str = STRBUF_INIT; struct strbuf str = STRBUF_INIT;
const char *filename = git_path("BISECT_NAMES"); const char *filename = git_path("BISECT_NAMES");
@ -440,8 +469,8 @@ void read_bisect_paths(void)
strbuf_trim(&str); strbuf_trim(&str);
quoted = strbuf_detach(&str, NULL); quoted = strbuf_detach(&str, NULL);
res = sq_dequote_to_argv(quoted, &rev_argv, res = sq_dequote_to_argv(quoted, &array->argv,
&rev_argv_nr, &rev_argv_alloc); &array->argv_nr, &array->argv_alloc);
if (res) if (res)
die("Badly quoted content in file '%s': %s", die("Badly quoted content in file '%s': %s",
filename, quoted); filename, quoted);
@ -451,26 +480,45 @@ void read_bisect_paths(void)
fclose(fp); fclose(fp);
} }
static int skipcmp(const void *a, const void *b) static int array_cmp(const void *a, const void *b)
{ {
return hashcmp(a, b); return hashcmp(a, b);
} }
static void prepare_skipped(void) static void sort_sha1_array(struct sha1_array *array)
{ {
qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp); qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp);
array->sorted = 1;
} }
static const unsigned char *skipped_sha1_access(size_t index, void *table) static const unsigned char *sha1_access(size_t index, void *table)
{ {
unsigned char (*skipped)[20] = table; unsigned char (*array)[20] = table;
return skipped[index]; return array[index];
} }
static int lookup_skipped(unsigned char *sha1) static int lookup_sha1_array(struct sha1_array *array,
const unsigned char *sha1)
{ {
return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr, if (!array->sorted)
skipped_sha1_access); sort_sha1_array(array);
return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access);
}
static char *join_sha1_array_hex(struct sha1_array *array, char delim)
{
struct strbuf joined_hexs = STRBUF_INIT;
int i;
for (i = 0; i < array->sha1_nr; i++) {
strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i]));
if (i + 1 < array->sha1_nr)
strbuf_addch(&joined_hexs, delim);
}
return strbuf_detach(&joined_hexs, NULL);
} }
struct commit_list *filter_skipped(struct commit_list *list, struct commit_list *filter_skipped(struct commit_list *list,
@ -481,15 +529,14 @@ struct commit_list *filter_skipped(struct commit_list *list,
*tried = NULL; *tried = NULL;
if (!skipped_sha1_nr) if (!skipped_revs.sha1_nr)
return list; return list;
prepare_skipped();
while (list) { while (list) {
struct commit_list *next = list->next; struct commit_list *next = list->next;
list->next = NULL; list->next = NULL;
if (0 <= lookup_skipped(list->item->object.sha1)) { if (0 <= lookup_sha1_array(&skipped_revs,
list->item->object.sha1)) {
/* Move current to tried list */ /* Move current to tried list */
*tried = list; *tried = list;
tried = &list->next; tried = &list->next;
@ -508,49 +555,328 @@ struct commit_list *filter_skipped(struct commit_list *list,
static void bisect_rev_setup(struct rev_info *revs, const char *prefix) static void bisect_rev_setup(struct rev_info *revs, const char *prefix)
{ {
struct argv_array rev_argv = { NULL, 0, 0 };
int i;
init_revisions(revs, prefix); init_revisions(revs, prefix);
revs->abbrev = 0; revs->abbrev = 0;
revs->commit_format = CMIT_FMT_UNSPECIFIED; revs->commit_format = CMIT_FMT_UNSPECIFIED;
/* argv[0] will be ignored by setup_revisions */ /* rev_argv.argv[0] will be ignored by setup_revisions */
ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup"); argv_array_push_sha1(&rev_argv, current_bad_sha1, "%s");
for (i = 0; i < good_revs.sha1_nr; i++)
argv_array_push_sha1(&rev_argv, good_revs.sha1[i], "^%s");
argv_array_push(&rev_argv, xstrdup("--"));
read_bisect_paths(&rev_argv);
argv_array_push(&rev_argv, NULL);
setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL);
revs->limited = 1;
}
static void bisect_common(struct rev_info *revs, int *reaches, int *all)
{
if (prepare_revision_walk(revs))
die("revision walk setup failed");
if (revs->tree_objects)
mark_edges_uninteresting(revs->commits, revs, NULL);
revs->commits = find_bisection(revs->commits, reaches, all,
!!skipped_revs.sha1_nr);
}
static void exit_if_skipped_commits(struct commit_list *tried,
const unsigned char *bad)
{
if (!tried)
return;
printf("There are only 'skip'ped commits left to test.\n"
"The first bad commit could be any of:\n");
print_commit_list(tried, "%s\n", "%s\n");
if (bad)
printf("%s\n", sha1_to_hex(bad));
printf("We cannot bisect more!\n");
exit(2);
}
static int is_expected_rev(const unsigned char *sha1)
{
const char *filename = git_path("BISECT_EXPECTED_REV");
struct stat st;
struct strbuf str = STRBUF_INIT;
FILE *fp;
int res = 0;
if (stat(filename, &st) || !S_ISREG(st.st_mode))
return 0;
fp = fopen(filename, "r");
if (!fp)
return 0;
if (strbuf_getline(&str, fp, '\n') != EOF)
res = !strcmp(str.buf, sha1_to_hex(sha1));
strbuf_release(&str);
fclose(fp);
return res;
}
static void mark_expected_rev(char *bisect_rev_hex)
{
int len = strlen(bisect_rev_hex);
const char *filename = git_path("BISECT_EXPECTED_REV");
int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0)
die("could not create file '%s': %s",
filename, strerror(errno));
bisect_rev_hex[len] = '\n';
write_or_die(fd, bisect_rev_hex, len + 1);
bisect_rev_hex[len] = '\0';
if (close(fd) < 0)
die("closing file %s: %s", filename, strerror(errno));
}
static int bisect_checkout(char *bisect_rev_hex)
{
int res;
mark_expected_rev(bisect_rev_hex);
argv_checkout[2] = bisect_rev_hex;
res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
if (res)
exit(res);
argv_show_branch[1] = bisect_rev_hex;
return run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
}
static struct commit *get_commit_reference(const unsigned char *sha1)
{
struct commit *r = lookup_commit_reference(sha1);
if (!r)
die("Not a valid commit name %s", sha1_to_hex(sha1));
return r;
}
static struct commit **get_bad_and_good_commits(int *rev_nr)
{
int len = 1 + good_revs.sha1_nr;
struct commit **rev = xmalloc(len * sizeof(*rev));
int i, n = 0;
rev[n++] = get_commit_reference(current_bad_sha1);
for (i = 0; i < good_revs.sha1_nr; i++)
rev[n++] = get_commit_reference(good_revs.sha1[i]);
*rev_nr = n;
return rev;
}
static void handle_bad_merge_base(void)
{
if (is_expected_rev(current_bad_sha1)) {
char *bad_hex = sha1_to_hex(current_bad_sha1);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
fprintf(stderr, "The merge base %s is bad.\n"
"This means the bug has been fixed "
"between %s and [%s].\n",
bad_hex, bad_hex, good_hex);
exit(3);
}
fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n"
"git bisect cannot work properly in this case.\n"
"Maybe you mistake good and bad revs?\n");
exit(1);
}
void handle_skipped_merge_base(const unsigned char *mb)
{
char *mb_hex = sha1_to_hex(mb);
char *bad_hex = sha1_to_hex(current_bad_sha1);
char *good_hex = join_sha1_array_hex(&good_revs, ' ');
fprintf(stderr, "Warning: the merge base between %s and [%s] "
"must be skipped.\n"
"So we cannot be sure the first bad commit is "
"between %s and %s.\n"
"We continue anyway.\n",
bad_hex, good_hex, mb_hex, bad_hex);
free(good_hex);
}
/*
* "check_merge_bases" checks that merge bases are not "bad".
*
* - If one is "bad", it means the user assumed something wrong
* and we must exit with a non 0 error code.
* - If one is "good", that's good, we have nothing to do.
* - 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.
*/
static void check_merge_bases(void)
{
struct commit_list *result;
int rev_nr;
struct commit **rev = get_bad_and_good_commits(&rev_nr);
result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
for (; result; result = result->next) {
const unsigned char *mb = result->item->object.sha1;
if (!hashcmp(mb, current_bad_sha1)) {
handle_bad_merge_base();
} else if (0 <= lookup_sha1_array(&good_revs, mb)) {
continue;
} else if (0 <= lookup_sha1_array(&skipped_revs, mb)) {
handle_skipped_merge_base(mb);
} else {
printf("Bisecting: a merge base must be tested\n");
exit(bisect_checkout(sha1_to_hex(mb)));
}
}
free(rev);
free_commit_list(result);
}
/*
* This function runs the command "git rev-list $_good ^$_bad"
* and returns 1 if it produces some output, 0 otherwise.
*/
static int check_ancestors(void)
{
struct argv_array rev_argv = { NULL, 0, 0 };
struct strbuf str = STRBUF_INIT;
int i, result = 0;
struct child_process rls;
FILE *rls_fout;
argv_array_push(&rev_argv, xstrdup("rev-list"));
argv_array_push_sha1(&rev_argv, current_bad_sha1, "^%s");
for (i = 0; i < good_revs.sha1_nr; i++)
argv_array_push_sha1(&rev_argv, good_revs.sha1[i], "%s");
argv_array_push(&rev_argv, NULL);
memset(&rls, 0, sizeof(rls));
rls.argv = rev_argv.argv;
rls.out = -1;
rls.git_cmd = 1;
if (start_command(&rls))
die("Could not launch 'git rev-list' command.");
rls_fout = fdopen(rls.out, "r");
while (strbuf_getline(&str, rls_fout, '\n') != EOF) {
strbuf_trim(&str);
if (*str.buf) {
result = 1;
break;
}
}
fclose(rls_fout);
finish_command(&rls);
return result;
}
/*
* "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, its source code will be
* checked out to be tested by the user and we will exit.
*/
static void check_good_are_ancestors_of_bad(const char *prefix)
{
const char *filename = git_path("BISECT_ANCESTORS_OK");
struct stat st;
int fd;
if (!current_bad_sha1)
die("a bad revision is needed");
/* Check if file BISECT_ANCESTORS_OK exists. */
if (!stat(filename, &st) && S_ISREG(st.st_mode))
return;
/* Bisecting with no good rev is ok. */
if (good_revs.sha1_nr == 0)
return;
if (check_ancestors())
check_merge_bases();
/* Create file BISECT_ANCESTORS_OK. */
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0)
warning("could not create file '%s': %s",
filename, strerror(errno));
else
close(fd);
}
/*
* We use the convention that exiting with an exit code 10 means that
* the bisection process finished successfully.
* In this case the calling shell script should exit 0.
*/
int bisect_next_all(const char *prefix)
{
struct rev_info revs;
struct commit_list *tried;
int reaches = 0, all = 0, nr;
const unsigned char *bisect_rev;
char bisect_rev_hex[41];
if (read_bisect_refs()) if (read_bisect_refs())
die("reading bisect refs failed"); die("reading bisect refs failed");
ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); check_good_are_ancestors_of_bad(prefix);
rev_argv[rev_argv_nr++] = xstrdup("--");
read_bisect_paths();
ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
rev_argv[rev_argv_nr++] = NULL;
setup_revisions(rev_argv_nr, rev_argv, revs, NULL);
revs->limited = 1;
}
int bisect_next_vars(const char *prefix)
{
struct rev_info revs;
struct rev_list_info info;
int reaches = 0, all = 0;
memset(&info, 0, sizeof(info));
info.revs = &revs;
info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED;
bisect_rev_setup(&revs, prefix); bisect_rev_setup(&revs, prefix);
if (prepare_revision_walk(&revs)) bisect_common(&revs, &reaches, &all);
die("revision walk setup failed");
if (revs.tree_objects)
mark_edges_uninteresting(revs.commits, &revs, NULL);
revs.commits = find_bisection(revs.commits, &reaches, &all, revs.commits = filter_skipped(revs.commits, &tried, 0);
!!skipped_sha1_nr);
return show_bisect_vars(&info, reaches, all); if (!revs.commits) {
/*
* We should exit here only if the "bad"
* commit is also a "skip" commit.
*/
exit_if_skipped_commits(tried, NULL);
printf("%s was both good and bad\n",
sha1_to_hex(current_bad_sha1));
exit(1);
}
bisect_rev = revs.commits->item->object.sha1;
memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41);
if (!hashcmp(bisect_rev, current_bad_sha1)) {
exit_if_skipped_commits(tried, current_bad_sha1);
printf("%s is first bad commit\n", bisect_rev_hex);
argv_diff_tree[2] = bisect_rev_hex;
run_command_v_opt(argv_diff_tree, RUN_GIT_CMD);
/* This means the bisection process succeeded. */
exit(10);
}
nr = all - reaches - 1;
printf("Bisecting: %d revisions left to test after this "
"(roughly %d steps)\n", nr, estimate_bisect_steps(all));
return bisect_checkout(bisect_rev_hex);
} }

View File

@ -9,10 +9,13 @@ extern struct commit_list *filter_skipped(struct commit_list *list,
struct commit_list **tried, struct commit_list **tried,
int show_all); int show_all);
extern void print_commit_list(struct commit_list *list,
const char *format_cur,
const char *format_last);
/* bisect_show_flags flags in struct rev_list_info */ /* bisect_show_flags flags in struct rev_list_info */
#define BISECT_SHOW_ALL (1<<0) #define BISECT_SHOW_ALL (1<<0)
#define BISECT_SHOW_TRIED (1<<1) #define BISECT_SHOW_TRIED (1<<1)
#define BISECT_SHOW_STRINGED (1<<2)
struct rev_list_info { struct rev_list_info {
struct rev_info *revs; struct rev_info *revs;
@ -24,6 +27,8 @@ struct rev_list_info {
extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
extern int bisect_next_vars(const char *prefix); extern int bisect_next_all(const char *prefix);
extern int estimate_bisect_steps(int all);
#endif #endif

View File

@ -4,24 +4,24 @@
#include "bisect.h" #include "bisect.h"
static const char * const git_bisect_helper_usage[] = { static const char * const git_bisect_helper_usage[] = {
"git bisect--helper --next-vars", "git bisect--helper --next-all",
NULL NULL
}; };
int cmd_bisect__helper(int argc, const char **argv, const char *prefix) int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{ {
int next_vars = 0; int next_all = 0;
struct option options[] = { struct option options[] = {
OPT_BOOLEAN(0, "next-vars", &next_vars, OPT_BOOLEAN(0, "next-all", &next_all,
"output next bisect step variables"), "perform 'git bisect next'"),
OPT_END() OPT_END()
}; };
argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0); argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0);
if (!next_vars) if (!next_all)
usage_with_options(git_bisect_helper_usage, options); usage_with_options(git_bisect_helper_usage, options);
/* next-vars */ /* next-all */
return bisect_next_vars(prefix); return bisect_next_all(prefix);
} }

View File

@ -211,7 +211,7 @@ static inline int exp2i(int n)
* *
* and P(2^n + x) < 0.5 means 2^n < 3x * and P(2^n + x) < 0.5 means 2^n < 3x
*/ */
static int estimate_bisect_steps(int all) int estimate_bisect_steps(int all)
{ {
int n, x, e; int n, x, e;
@ -225,20 +225,37 @@ static int estimate_bisect_steps(int all)
return (e < 3 * x) ? n : n - 1; return (e < 3 * x) ? n : n - 1;
} }
static void show_tried_revs(struct commit_list *tried, int stringed) void print_commit_list(struct commit_list *list,
const char *format_cur,
const char *format_last)
{
for ( ; list; list = list->next) {
const char *format = list->next ? format_cur : format_last;
printf(format, sha1_to_hex(list->item->object.sha1));
}
}
static void show_tried_revs(struct commit_list *tried)
{ {
printf("bisect_tried='"); printf("bisect_tried='");
for (;tried; tried = tried->next) { print_commit_list(tried, "%s|", "%s");
char *format = tried->next ? "%s|" : "%s"; printf("'\n");
printf(format, sha1_to_hex(tried->item->object.sha1)); }
}
printf(stringed ? "' &&\n" : "'\n"); static void print_var_str(const char *var, const char *val)
{
printf("%s='%s'\n", var, val);
}
static void print_var_int(const char *var, int val)
{
printf("%s=%d\n", var, val);
} }
int show_bisect_vars(struct rev_list_info *info, int reaches, int all) int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
{ {
int cnt, flags = info->bisect_show_flags; int cnt, flags = info->bisect_show_flags;
char hex[41] = "", *format; char hex[41] = "";
struct commit_list *tried; struct commit_list *tried;
struct rev_info *revs = info->revs; struct rev_info *revs = info->revs;
@ -269,28 +286,14 @@ int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
} }
if (flags & BISECT_SHOW_TRIED) if (flags & BISECT_SHOW_TRIED)
show_tried_revs(tried, flags & BISECT_SHOW_STRINGED); show_tried_revs(tried);
format = (flags & BISECT_SHOW_STRINGED) ?
"bisect_rev=%s &&\n" print_var_str("bisect_rev", hex);
"bisect_nr=%d &&\n" print_var_int("bisect_nr", cnt - 1);
"bisect_good=%d &&\n" print_var_int("bisect_good", all - reaches - 1);
"bisect_bad=%d &&\n" print_var_int("bisect_bad", reaches - 1);
"bisect_all=%d &&\n" print_var_int("bisect_all", all);
"bisect_steps=%d\n" print_var_int("bisect_steps", estimate_bisect_steps(all));
:
"bisect_rev=%s\n"
"bisect_nr=%d\n"
"bisect_good=%d\n"
"bisect_bad=%d\n"
"bisect_all=%d\n"
"bisect_steps=%d\n";
printf(format,
hex,
cnt - 1,
all - reaches - 1,
reaches - 1,
all,
estimate_bisect_steps(all));
return 0; return 0;
} }

View File

@ -402,6 +402,18 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
return 0; return 0;
} }
static int cmd_sq_quote(int argc, const char **argv)
{
struct strbuf buf = STRBUF_INIT;
if (argc)
sq_quote_argv(&buf, argv, 0);
printf("%s\n", buf.buf);
strbuf_release(&buf);
return 0;
}
static void die_no_single_rev(int quiet) static void die_no_single_rev(int quiet)
{ {
if (quiet) if (quiet)
@ -419,6 +431,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (argc > 1 && !strcmp("--parseopt", argv[1])) if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix); return cmd_parseopt(argc - 1, argv + 1, prefix);
if (argc > 1 && !strcmp("--sq-quote", argv[1]))
return cmd_sq_quote(argc - 2, argv + 2);
prefix = setup_git_directory(); prefix = setup_git_directory();
git_config(git_default_config, NULL); git_config(git_default_config, NULL);
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {

View File

@ -44,11 +44,7 @@ else
fi fi
sq () { sq () {
for sqarg git rev-parse --sq-quote "$@"
do
printf "%s" "$sqarg" |
sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/'
done
} }
stop_here () { stop_here () {

View File

@ -33,16 +33,6 @@ require_work_tree
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
sq() {
@@PERL@@ -e '
for (@ARGV) {
s/'\''/'\'\\\\\'\''/g;
print " '\''$_'\''";
}
print "\n";
' "$@"
}
bisect_autostart() { bisect_autostart() {
test -s "$GIT_DIR/BISECT_START" || { test -s "$GIT_DIR/BISECT_START" || {
echo >&2 'You need to start by "git bisect start"' echo >&2 'You need to start by "git bisect start"'
@ -107,7 +97,7 @@ bisect_start() {
for arg; do for arg; do
case "$arg" in --) has_double_dash=1; break ;; esac case "$arg" in --) has_double_dash=1; break ;; esac
done done
orig_args=$(sq "$@") orig_args=$(git rev-parse --sq-quote "$@")
bad_seen=0 bad_seen=0
eval='' eval=''
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@ -147,7 +137,7 @@ bisect_start() {
# Write new start state. # Write new start state.
# #
echo "$start_head" >"$GIT_DIR/BISECT_START" && echo "$start_head" >"$GIT_DIR/BISECT_START" &&
sq "$@" >"$GIT_DIR/BISECT_NAMES" && git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" &&
eval "$eval" && eval "$eval" &&
echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit
# #
@ -177,10 +167,6 @@ is_expected_rev() {
test "$1" = $(cat "$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() { check_expected_revs() {
for _rev in "$@"; do for _rev in "$@"; do
if ! is_expected_rev "$_rev"; then if ! is_expected_rev "$_rev"; then
@ -199,7 +185,7 @@ bisect_skip() {
*..*) *..*)
revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;; revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;;
*) *)
revs=$(sq "$arg") ;; revs=$(git rev-parse --sq-quote "$arg") ;;
esac esac
all="$all $revs" all="$all $revs"
done done
@ -279,162 +265,22 @@ bisect_auto_next() {
bisect_next_check && bisect_next || : bisect_next_check && bisect_next || :
} }
exit_if_skipped_commits () {
_tried=$1
_bad=$2
if test -n "$_tried" ; then
echo "There are only 'skip'ped commit left to test."
echo "The first bad commit could be any of:"
echo "$_tried" | tr '[|]' '[\012]'
test -n "$_bad" && echo "$_bad"
echo "We cannot bisect more!"
exit 2
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
# Get bad, good and skipped revs # Perform all bisection computation, display and checkout
bad=$(git rev-parse --verify refs/bisect/bad) && git bisect--helper --next-all
good=$(git for-each-ref --format='^%(objectname)' \ res=$?
"refs/bisect/good-*" | tr '\012' ' ') &&
skip=$(git for-each-ref --format='%(objectname)' \
"refs/bisect/skip-*" | tr '\012' ' ') || exit
# Maybe some merge bases must be tested first # Check if we should exit because bisection is finished
check_good_are_ancestors_of_bad "$bad" "$good" "$skip" test $res -eq 10 && exit 0
# Return now if a checkout has already been done
test "$?" -eq "1" && return
# Get bisection information # Check for an error in the bisection process
eval=$(eval "git bisect--helper --next-vars") && test $res -ne 0 && exit $res
eval "$eval" || exit
if [ -z "$bisect_rev" ]; then return 0
# We should exit here only if the "bad"
# commit is also a "skip" commit (see above).
exit_if_skipped_commits "$bisect_tried"
echo "$bad was both good and bad"
exit 1
fi
if [ "$bisect_rev" = "$bad" ]; then
exit_if_skipped_commits "$bisect_tried" "$bad"
echo "$bisect_rev is first bad commit"
git diff-tree --pretty $bisect_rev
exit 0
fi
bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
} }
bisect_visualize() { bisect_visualize() {