From 7a8e3895f68e9ae4e44e521c78fc98768c2a88ec Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 27 May 2009 07:09:40 +0200 Subject: [PATCH 1/5] bisect: drop unparse_commit() and use clear_commit_marks() The goal of this patch series is to check if good revisions are ancestor of the bad revision without forking a process to launch "git rev-list $good ^$bad". This new version of this patch series does not use an "unparse_commit" function anymore, we use "clear_commit_marks" instead. Signed-off-by: Junio C Hamano --- bisect.c | 2 +- commit.c | 20 -------------------- commit.h | 2 -- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/bisect.c b/bisect.c index c43c120bde..18f9fa4062 100644 --- a/bisect.c +++ b/bisect.c @@ -771,7 +771,7 @@ static int check_ancestors(const char *prefix) /* Clean up objects used, as they will be reused. */ for (i = 0; i < pending_copy.nr; i++) { struct object *o = pending_copy.objects[i].item; - unparse_commit((struct commit *)o); + clear_commit_marks((struct commit *)o, ALL_REV_FLAGS); } return res; diff --git a/commit.c b/commit.c index 8f6b703c55..aa3b35b6a8 100644 --- a/commit.c +++ b/commit.c @@ -316,26 +316,6 @@ int parse_commit(struct commit *item) return ret; } -static void unparse_commit_list(struct commit_list *list) -{ - for (; list; list = list->next) - unparse_commit(list->item); -} - -void unparse_commit(struct commit *item) -{ - item->object.flags = 0; - item->object.used = 0; - if (item->object.parsed) { - item->object.parsed = 0; - if (item->parents) { - unparse_commit_list(item->parents); - free_commit_list(item->parents); - item->parents = NULL; - } - } -} - struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p) { struct commit_list *new_list = xmalloc(sizeof(struct commit_list)); diff --git a/commit.h b/commit.h index f3eaf1d048..ba9f63813e 100644 --- a/commit.h +++ b/commit.h @@ -40,8 +40,6 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size); int parse_commit(struct commit *item); -void unparse_commit(struct commit *item); - struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p); unsigned commit_list_count(const struct commit_list *l); struct commit_list * insert_by_date(struct commit *item, struct commit_list **list); From e22278c0a0784d4285f0e3173794caad4e542658 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 28 May 2009 23:21:16 +0200 Subject: [PATCH 2/5] bisect: display first bad commit without forking a new process Previously "git diff-tree --pretty COMMIT" was run using "run_command_v_opt" to display information about the first bad commit. The goal of this patch is to avoid a "fork" and an "exec" call when displaying that information. To do that, we manually setup revision information as "git diff-tree --pretty" would do it, and then use the "log_tree_commit" function. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/bisect.c b/bisect.c index 18f9fa4062..2c14f4d616 100644 --- a/bisect.c +++ b/bisect.c @@ -7,6 +7,7 @@ #include "quote.h" #include "sha1-lookup.h" #include "run-command.h" +#include "log-tree.h" #include "bisect.h" struct sha1_array { @@ -27,7 +28,6 @@ struct argv_array { 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}; @@ -815,6 +815,31 @@ static void check_good_are_ancestors_of_bad(const char *prefix) close(fd); } +/* + * This does "git diff-tree --pretty COMMIT" without one fork+exec. + */ +static void show_diff_tree(const char *prefix, struct commit *commit) +{ + struct rev_info opt; + + /* diff-tree init */ + init_revisions(&opt, prefix); + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + opt.abbrev = 0; + opt.diff = 1; + + /* This is what "--pretty" does */ + opt.verbose_header = 1; + opt.use_terminator = 0; + opt.commit_format = CMIT_FMT_DEFAULT; + + /* diff-tree init */ + if (!opt.diffopt.output_format) + opt.diffopt.output_format = DIFF_FORMAT_RAW; + + log_tree_commit(&opt, commit); +} + /* * We use the convention that exiting with an exit code 10 means that * the bisection process finished successfully. @@ -860,8 +885,7 @@ int bisect_next_all(const char *prefix) 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); + show_diff_tree(prefix, revs.commits->item); /* This means the bisection process succeeded. */ exit(10); } From 9af3589e0e42eb289dfdb8bb4031e5bec4923308 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 6 Jun 2009 06:41:33 +0200 Subject: [PATCH 3/5] bisect: add parameters to "filter_skipped" because we will need to get more information from this function in some later patches. The new "int *count" parameter gives the number of commits left after the skipped commit have been filtered out. The new "int *skipped_first" parameter tells us if the first commit in the list has been skipped. Note that using this parameter also changes the behavior of the function if the first commit is indeed skipped. Because we assume that in this case we will want all the filtered commits, not just the first one, even if "show_all" is not set. So using a not NULL "skipped_first" parameter really means that we plan to choose to test another commit than the first non skipped one if the first commit in the list is skipped. That in turn means that, in case the first commit is skipped, we have to return a fully filtered list. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 40 ++++++++++++++++++++++++++++++++++++---- bisect.h | 4 +++- builtin-rev-list.c | 4 +++- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/bisect.c b/bisect.c index 2c14f4d616..115cf5fa41 100644 --- a/bisect.c +++ b/bisect.c @@ -521,14 +521,34 @@ static char *join_sha1_array_hex(struct sha1_array *array, char delim) return strbuf_detach(&joined_hexs, NULL); } +/* + * In this function, passing a not NULL skipped_first is very special. + * It means that we want to know if the first commit in the list is + * skipped because we will want to test a commit away from it if it is + * indeed skipped. + * So if the first commit is skipped, we cannot take the shortcut to + * just "return list" when we find the first non skipped commit, we + * have to return a fully filtered list. + * + * We use (*skipped_first == -1) to mean "it has been found that the + * first commit is not skipped". In this case *skipped_first is set back + * to 0 just before the function returns. + */ struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, - int show_all) + int show_all, + int *count, + int *skipped_first) { struct commit_list *filtered = NULL, **f = &filtered; *tried = NULL; + if (skipped_first) + *skipped_first = 0; + if (count) + *count = 0; + if (!skipped_revs.sha1_nr) return list; @@ -537,19 +557,31 @@ struct commit_list *filter_skipped(struct commit_list *list, list->next = NULL; if (0 <= lookup_sha1_array(&skipped_revs, list->item->object.sha1)) { + if (skipped_first && !*skipped_first) + *skipped_first = 1; /* Move current to tried list */ *tried = list; tried = &list->next; } else { - if (!show_all) - return list; + if (!show_all) { + if (!skipped_first || !*skipped_first) + return list; + } else if (skipped_first && !*skipped_first) { + /* This means we know it's not skipped */ + *skipped_first = -1; + } /* Move current to filtered list */ *f = list; f = &list->next; + if (count) + (*count)++; } list = next; } + if (skipped_first && *skipped_first == -1) + *skipped_first = 0; + return filtered; } @@ -865,7 +897,7 @@ int bisect_next_all(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_revs.sha1_nr); - revs.commits = filter_skipped(revs.commits, &tried, 0); + revs.commits = filter_skipped(revs.commits, &tried, 0, NULL, NULL); if (!revs.commits) { /* diff --git a/bisect.h b/bisect.h index fb744fdb79..82f8fc1910 100644 --- a/bisect.h +++ b/bisect.h @@ -7,7 +7,9 @@ extern struct commit_list *find_bisection(struct commit_list *list, extern struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, - int show_all); + int show_all, + int *count, + int *skipped_first); extern void print_commit_list(struct commit_list *list, const char *format_cur, diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 31ea5f4aac..4ba1c12e0b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -262,7 +262,9 @@ int show_bisect_vars(struct rev_list_info *info, int reaches, int all) if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) return 1; - revs->commits = filter_skipped(revs->commits, &tried, flags & BISECT_SHOW_ALL); + revs->commits = filter_skipped(revs->commits, &tried, + flags & BISECT_SHOW_ALL, + NULL, NULL); /* * revs->commits can reach "reaches" commits among From 62d0b0daf12239fdb898a0d197dfc49a5e2742b0 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 6 Jun 2009 06:41:34 +0200 Subject: [PATCH 4/5] bisect: when skipping, choose a commit away from a skipped commit To do that a new function "apply_skip_ratio" is added and another function "managed_skipped" is created to wrap both "filter_skipped" and the previous one. In "managed_skipped" we detect when we should choose a commit away from a skipped one and then we automatically choose a skip ratio to pass to "apply_skip_ratio". The ratio is choosen so that it alternates between 1/5, 2/5 and 3/5. In "apply_skip_ratio", we ignore a given ratio of all the commits that could be tested. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/bisect.c b/bisect.c index 115cf5fa41..6fdff05722 100644 --- a/bisect.c +++ b/bisect.c @@ -585,6 +585,54 @@ struct commit_list *filter_skipped(struct commit_list *list, return filtered; } +static struct commit_list *apply_skip_ratio(struct commit_list *list, + int count, + int skip_num, int skip_denom) +{ + int index, i; + struct commit_list *cur, *previous; + + cur = list; + previous = NULL; + index = count * skip_num / skip_denom; + + for (i = 0; cur; cur = cur->next, i++) { + if (i == index) { + if (hashcmp(cur->item->object.sha1, current_bad_sha1)) + return cur; + if (previous) + return previous; + return list; + } + previous = cur; + } + + return list; +} + +static struct commit_list *managed_skipped(struct commit_list *list, + struct commit_list **tried) +{ + int count, skipped_first; + int skip_num, skip_denom; + + *tried = NULL; + + if (!skipped_revs.sha1_nr) + return list; + + list = filter_skipped(list, tried, 0, &count, &skipped_first); + + if (!skipped_first) + return list; + + /* Use alternatively 1/5, 2/5 and 3/5 as skip ratio. */ + skip_num = count % 3 + 1; + skip_denom = 5; + + return apply_skip_ratio(list, count, skip_num, skip_denom); +} + static void bisect_rev_setup(struct rev_info *revs, const char *prefix, const char *bad_format, const char *good_format, int read_paths) @@ -897,7 +945,7 @@ int bisect_next_all(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_revs.sha1_nr); - revs.commits = filter_skipped(revs.commits, &tried, 0, NULL, NULL); + revs.commits = managed_skipped(revs.commits, &tried); if (!revs.commits) { /* From a66037c9755a2beb49bbd915e6f0dd9b1a732925 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 6 Jun 2009 06:41:35 +0200 Subject: [PATCH 5/5] t6030: test skipping away from an already skipped commit Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- t/t6030-bisect-porcelain.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 5254b23512..4556cdd8d2 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -555,6 +555,18 @@ test_expect_success 'restricting bisection on one dir and a file' ' grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt ' +test_expect_success 'skipping away from skipped commit' ' + git bisect start $PARA_HASH7 $HASH1 && + para4=$(git rev-parse --verify HEAD) && + test "$para4" = "$PARA_HASH4" && + git bisect skip && + hash7=$(git rev-parse --verify HEAD) && + test "$hash7" = "$HASH7" && + git bisect skip && + hash3=$(git rev-parse --verify HEAD) && + test "$hash3" = "$HASH3" +' + # # test_done