From ebbc088e13e1bf0dbf8eb08b00519602c176f864 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sun, 29 Mar 2009 11:44:44 +0200 Subject: [PATCH 01/50] quote: implement "sq_dequote_many" to unwrap many args in one string The sq_dequote() function does not allow parsing a string with more than one single-quoted parameter easily; use its code to implement a new API sq_dequote_step() to allow the caller iterate through such a string to parse them one-by-one. The original sq_dequote() becomes a thin wrapper around it. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- quote.c | 18 ++++++++++++++++-- quote.h | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/quote.c b/quote.c index 6a520855d6..ea49c7a99f 100644 --- a/quote.c +++ b/quote.c @@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) } } -char *sq_dequote(char *arg) +char *sq_dequote_step(char *arg, char **next) { char *dst = arg; char *src = arg; @@ -92,6 +92,8 @@ char *sq_dequote(char *arg) switch (*++src) { case '\0': *dst = 0; + if (next) + *next = NULL; return arg; case '\\': c = *++src; @@ -101,11 +103,23 @@ char *sq_dequote(char *arg) } /* Fallthrough */ default: - return NULL; + if (!next || !isspace(*src)) + return NULL; + do { + c = *++src; + } while (isspace(c)); + *dst = 0; + *next = src; + return arg; } } } +char *sq_dequote(char *arg) +{ + return sq_dequote_step(arg, NULL); +} + /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote diff --git a/quote.h b/quote.h index c5eea6f18e..2315105fa3 100644 --- a/quote.h +++ b/quote.h @@ -39,6 +39,14 @@ extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); */ extern char *sq_dequote(char *); +/* + * Same as the above, but can be used to unwrap many arguments in the + * same string separated by space. "next" is changed to point to the + * next argument that should be passed as first parameter. When there + * is no more argument to be dequoted, "next" is updated to point to NULL. + */ +extern char *sq_dequote_step(char *arg, char **next); + extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); From eaa759b9141f125d7e55a4b08b60497845d3c52e Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sun, 29 Mar 2009 11:44:52 +0200 Subject: [PATCH 02/50] quote: add "sq_dequote_to_argv" to put unwrapped args in an argv array This new function unwraps the space separated shell quoted elements in its first argument and places them in the argv array passed as its second argument. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- quote.c | 17 +++++++++++++++++ quote.h | 1 + 2 files changed, 18 insertions(+) diff --git a/quote.c b/quote.c index ea49c7a99f..7a49fcf696 100644 --- a/quote.c +++ b/quote.c @@ -120,6 +120,23 @@ char *sq_dequote(char *arg) return sq_dequote_step(arg, NULL); } +int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) +{ + char *next = arg; + + if (!*arg) + return 0; + do { + char *dequoted = sq_dequote_step(next, &next); + if (!dequoted) + return -1; + ALLOC_GROW(*argv, *nr + 1, *alloc); + (*argv)[(*nr)++] = dequoted; + } while (next); + + return 0; +} + /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote diff --git a/quote.h b/quote.h index 2315105fa3..66730f2bff 100644 --- a/quote.h +++ b/quote.h @@ -46,6 +46,7 @@ extern char *sq_dequote(char *); * is no more argument to be dequoted, "next" is updated to point to NULL. */ extern char *sq_dequote_step(char *arg, char **next); +extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); From 2a8177b63d39503b182248b04ffcc75e3495754c Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 30 Mar 2009 05:07:15 +0200 Subject: [PATCH 03/50] refs: add "for_each_ref_in" function to refactor "for_each_*_ref" functions The "for_each_{tag,branch,remote,replace,}_ref" functions are redefined in terms of "for_each_ref_in" so that we can lose the hardcoded length of prefix strings from the code. Signed-off-by: Christian Couder --- refs.c | 11 ++++++++--- refs.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/refs.c b/refs.c index aeef257ee3..2d198a1add 100644 --- a/refs.c +++ b/refs.c @@ -647,19 +647,24 @@ int for_each_ref(each_ref_fn fn, void *cb_data) return do_for_each_ref("refs/", fn, 0, 0, cb_data); } +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data); +} + int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data); + return for_each_ref_in("refs/tags/", fn, cb_data); } int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data); + return for_each_ref_in("refs/heads/", fn, cb_data); } int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data); + return for_each_ref_in("refs/remotes/", fn, cb_data); } int for_each_rawref(each_ref_fn fn, void *cb_data) diff --git a/refs.h b/refs.h index 29bdcecd4e..abb125754d 100644 --- a/refs.h +++ b/refs.h @@ -20,6 +20,7 @@ struct ref_lock { typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); +extern int for_each_ref_in(const char *, each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); From ff62d732d826efcf271cd6c50a41f613af97aff6 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:17 +0100 Subject: [PATCH 04/50] rev-list: make "bisect_list" variable local to "cmd_rev_list" The "bisect_list" variable was static for no reason as it is only used in the "cmd_rev_list" function. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- builtin-rev-list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 40d5fcb6b0..28fe2dc30b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -52,7 +52,6 @@ static const char rev_list_usage[] = static struct rev_info revs; -static int bisect_list; static int show_timestamp; static int hdr_termination; static const char *header_prefix; @@ -618,6 +617,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) struct commit_list *list; int i; int read_from_stdin = 0; + int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; int quiet = 0; From a2ad79ced25e1b76fabec079549f521e8071ddd1 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:24 +0100 Subject: [PATCH 05/50] rev-list: move bisect related code into its own file This patch creates new "bisect.c" and "bisect.h" files and move bisect related code into these files. While at it, we also remove some include directives that are not needed any more from the beginning of "builtin-rev-list.c". Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Makefile | 1 + bisect.c | 388 +++++++++++++++++++++++++++++++++++++++++++++ bisect.h | 8 + builtin-rev-list.c | 388 +-------------------------------------------- 4 files changed, 398 insertions(+), 387 deletions(-) create mode 100644 bisect.c create mode 100644 bisect.h diff --git a/Makefile b/Makefile index 320c89786c..9fa2928357 100644 --- a/Makefile +++ b/Makefile @@ -420,6 +420,7 @@ LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o LIB_OBJS += attr.o LIB_OBJS += base85.o +LIB_OBJS += bisect.o LIB_OBJS += blob.o LIB_OBJS += branch.o LIB_OBJS += bundle.o diff --git a/bisect.c b/bisect.c new file mode 100644 index 0000000000..27def7dacf --- /dev/null +++ b/bisect.c @@ -0,0 +1,388 @@ +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "bisect.h" + +/* bits #0-15 in revision.h */ + +#define COUNTED (1u<<16) + +/* + * This is a truly stupid algorithm, but it's only + * used for bisection, and we just don't care enough. + * + * We care just barely enough to avoid recursing for + * non-merge entries. + */ +static int count_distance(struct commit_list *entry) +{ + int nr = 0; + + while (entry) { + struct commit *commit = entry->item; + struct commit_list *p; + + if (commit->object.flags & (UNINTERESTING | COUNTED)) + break; + if (!(commit->object.flags & TREESAME)) + nr++; + commit->object.flags |= COUNTED; + p = commit->parents; + entry = p; + if (p) { + p = p->next; + while (p) { + nr += count_distance(p); + p = p->next; + } + } + } + + return nr; +} + +static void clear_distance(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + commit->object.flags &= ~COUNTED; + list = list->next; + } +} + +#define DEBUG_BISECT 0 + +static inline int weight(struct commit_list *elem) +{ + return *((int*)(elem->item->util)); +} + +static inline void weight_set(struct commit_list *elem, int weight) +{ + *((int*)(elem->item->util)) = weight; +} + +static int count_interesting_parents(struct commit *commit) +{ + struct commit_list *p; + int count; + + for (count = 0, p = commit->parents; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + count++; + } + return count; +} + +static inline int halfway(struct commit_list *p, int nr) +{ + /* + * Don't short-cut something we are not going to return! + */ + if (p->item->object.flags & TREESAME) + return 0; + if (DEBUG_BISECT) + return 0; + /* + * 2 and 3 are halfway of 5. + * 3 is halfway of 6 but 2 and 4 are not. + */ + switch (2 * weight(p) - nr) { + case -1: case 0: case 1: + return 1; + default: + return 0; + } +} + +#if !DEBUG_BISECT +#define show_list(a,b,c,d) do { ; } while (0) +#else +static void show_list(const char *debug, int counted, int nr, + struct commit_list *list) +{ + struct commit_list *p; + + fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr); + + for (p = list; p; p = p->next) { + struct commit_list *pp; + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + enum object_type type; + unsigned long size; + char *buf = read_sha1_file(commit->object.sha1, &type, &size); + char *ep, *sp; + + fprintf(stderr, "%c%c%c ", + (flags & TREESAME) ? ' ' : 'T', + (flags & UNINTERESTING) ? 'U' : ' ', + (flags & COUNTED) ? 'C' : ' '); + if (commit->util) + fprintf(stderr, "%3d", weight(p)); + else + fprintf(stderr, "---"); + fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1)); + for (pp = commit->parents; pp; pp = pp->next) + fprintf(stderr, " %.*s", 8, + sha1_to_hex(pp->item->object.sha1)); + + sp = strstr(buf, "\n\n"); + if (sp) { + sp += 2; + for (ep = sp; *ep && *ep != '\n'; ep++) + ; + fprintf(stderr, " %.*s", (int)(ep - sp), sp); + } + fprintf(stderr, "\n"); + } +} +#endif /* DEBUG_BISECT */ + +static struct commit_list *best_bisection(struct commit_list *list, int nr) +{ + struct commit_list *p, *best; + int best_distance = -1; + + best = list; + for (p = list; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (flags & TREESAME) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + if (distance > best_distance) { + best = p; + best_distance = distance; + } + } + + 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 (flags & TREESAME) + 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 + * reach any tree-changing commits (e.g. just above uninteresting one + * but traversal is with pathspec). + * + * weight = -1 means it has one parent and its distance is yet to + * be computed. + * + * weight = -2 means it has more than one parent and its distance is + * unknown. After running count_distance() first, they will get zero + * or positive distance. + */ +static struct commit_list *do_find_bisection(struct commit_list *list, + int nr, int *weights, + int find_all) +{ + int n, counted; + struct commit_list *p; + + counted = 0; + + for (n = 0, p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + + p->item->util = &weights[n++]; + switch (count_interesting_parents(commit)) { + case 0: + if (!(flags & TREESAME)) { + weight_set(p, 1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + /* + * otherwise, it is known not to reach any + * tree-changing commit and gets weight 0. + */ + break; + case 1: + weight_set(p, -1); + break; + default: + weight_set(p, -2); + break; + } + } + + show_list("bisection 2 initialize", counted, nr, list); + + /* + * If you have only one parent in the resulting set + * then you can reach one commit more than that parent + * can reach. So we do not have to run the expensive + * count_distance() for single strand of pearls. + * + * However, if you have more than one parents, you cannot + * just add their distance and one for yourself, since + * they usually reach the same ancestor and you would + * end up counting them twice that way. + * + * So we will first count distance of merges the usual + * way, and then fill the blanks using cheaper algorithm. + */ + for (p = list; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + if (weight(p) != -2) + continue; + weight_set(p, count_distance(p)); + clear_distance(list); + + /* Does it happen to be at exactly half-way? */ + if (!find_all && halfway(p, nr)) + return p; + counted++; + } + + show_list("bisection 2 count_distance", counted, nr, list); + + while (counted < nr) { + for (p = list; p; p = p->next) { + struct commit_list *q; + unsigned flags = p->item->object.flags; + + if (0 <= weight(p)) + continue; + for (q = p->item->parents; q; q = q->next) { + if (q->item->object.flags & UNINTERESTING) + continue; + if (0 <= weight(q)) + break; + } + if (!q) + continue; + + /* + * weight for p is unknown but q is known. + * add one for p itself if p is to be counted, + * otherwise inherit it from q directly. + */ + if (!(flags & TREESAME)) { + weight_set(p, weight(q)+1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + else + weight_set(p, weight(q)); + + /* Does it happen to be at exactly half-way? */ + if (!find_all && halfway(p, nr)) + return p; + } + } + + show_list("bisection 2 counted all", counted, nr, list); + + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); +} + +struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all, + int find_all) +{ + int nr, on_list; + struct commit_list *p, *best, *next, *last; + int *weights; + + show_list("bisection 2 entry", 0, 0, list); + + /* + * Count the number of total and tree-changing items on the + * list, while reversing the list. + */ + for (nr = on_list = 0, last = NULL, p = list; + p; + p = next) { + unsigned flags = p->item->object.flags; + + next = p->next; + if (flags & UNINTERESTING) + continue; + p->next = last; + last = p; + if (!(flags & TREESAME)) + nr++; + on_list++; + } + list = last; + show_list("bisection 2 sorted", 0, nr, list); + + *all = nr; + weights = xcalloc(on_list, sizeof(*weights)); + + /* Do the real work of finding bisection commit. */ + best = do_find_bisection(list, nr, weights, find_all); + if (best) { + if (!find_all) + best->next = NULL; + *reaches = weight(best); + } + free(weights); + return best; +} + diff --git a/bisect.h b/bisect.h new file mode 100644 index 0000000000..60b2fe1cdc --- /dev/null +++ b/bisect.h @@ -0,0 +1,8 @@ +#ifndef BISECT_H +#define BISECT_H + +extern struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all, + int find_all); + +#endif diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 28fe2dc30b..b1e8200d2b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -1,20 +1,12 @@ #include "cache.h" -#include "refs.h" -#include "tag.h" #include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tree-walk.h" #include "diff.h" #include "revision.h" #include "list-objects.h" #include "builtin.h" #include "log-tree.h" #include "graph.h" - -/* bits #0-15 in revision.h */ - -#define COUNTED (1u<<16) +#include "bisect.h" static const char rev_list_usage[] = "git rev-list [OPTION] ... [ -- paths... ]\n" @@ -195,384 +187,6 @@ static void show_edge(struct commit *commit) printf("-%s\n", sha1_to_hex(commit->object.sha1)); } -/* - * This is a truly stupid algorithm, but it's only - * used for bisection, and we just don't care enough. - * - * We care just barely enough to avoid recursing for - * non-merge entries. - */ -static int count_distance(struct commit_list *entry) -{ - int nr = 0; - - while (entry) { - struct commit *commit = entry->item; - struct commit_list *p; - - if (commit->object.flags & (UNINTERESTING | COUNTED)) - break; - if (!(commit->object.flags & TREESAME)) - nr++; - commit->object.flags |= COUNTED; - p = commit->parents; - entry = p; - if (p) { - p = p->next; - while (p) { - nr += count_distance(p); - p = p->next; - } - } - } - - return nr; -} - -static void clear_distance(struct commit_list *list) -{ - while (list) { - struct commit *commit = list->item; - commit->object.flags &= ~COUNTED; - list = list->next; - } -} - -#define DEBUG_BISECT 0 - -static inline int weight(struct commit_list *elem) -{ - return *((int*)(elem->item->util)); -} - -static inline void weight_set(struct commit_list *elem, int weight) -{ - *((int*)(elem->item->util)) = weight; -} - -static int count_interesting_parents(struct commit *commit) -{ - struct commit_list *p; - int count; - - for (count = 0, p = commit->parents; p; p = p->next) { - if (p->item->object.flags & UNINTERESTING) - continue; - count++; - } - return count; -} - -static inline int halfway(struct commit_list *p, int nr) -{ - /* - * Don't short-cut something we are not going to return! - */ - if (p->item->object.flags & TREESAME) - return 0; - if (DEBUG_BISECT) - return 0; - /* - * 2 and 3 are halfway of 5. - * 3 is halfway of 6 but 2 and 4 are not. - */ - switch (2 * weight(p) - nr) { - case -1: case 0: case 1: - return 1; - default: - return 0; - } -} - -#if !DEBUG_BISECT -#define show_list(a,b,c,d) do { ; } while (0) -#else -static void show_list(const char *debug, int counted, int nr, - struct commit_list *list) -{ - struct commit_list *p; - - fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr); - - for (p = list; p; p = p->next) { - struct commit_list *pp; - struct commit *commit = p->item; - unsigned flags = commit->object.flags; - enum object_type type; - unsigned long size; - char *buf = read_sha1_file(commit->object.sha1, &type, &size); - char *ep, *sp; - - fprintf(stderr, "%c%c%c ", - (flags & TREESAME) ? ' ' : 'T', - (flags & UNINTERESTING) ? 'U' : ' ', - (flags & COUNTED) ? 'C' : ' '); - if (commit->util) - fprintf(stderr, "%3d", weight(p)); - else - fprintf(stderr, "---"); - fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1)); - for (pp = commit->parents; pp; pp = pp->next) - fprintf(stderr, " %.*s", 8, - sha1_to_hex(pp->item->object.sha1)); - - sp = strstr(buf, "\n\n"); - if (sp) { - sp += 2; - for (ep = sp; *ep && *ep != '\n'; ep++) - ; - fprintf(stderr, " %.*s", (int)(ep - sp), sp); - } - fprintf(stderr, "\n"); - } -} -#endif /* DEBUG_BISECT */ - -static struct commit_list *best_bisection(struct commit_list *list, int nr) -{ - struct commit_list *p, *best; - int best_distance = -1; - - best = list; - for (p = list; p; p = p->next) { - int distance; - unsigned flags = p->item->object.flags; - - if (flags & TREESAME) - continue; - distance = weight(p); - if (nr - distance < distance) - distance = nr - distance; - if (distance > best_distance) { - best = p; - best_distance = distance; - } - } - - 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 (flags & TREESAME) - 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 - * reach any tree-changing commits (e.g. just above uninteresting one - * but traversal is with pathspec). - * - * weight = -1 means it has one parent and its distance is yet to - * be computed. - * - * weight = -2 means it has more than one parent and its distance is - * unknown. After running count_distance() first, they will get zero - * or positive distance. - */ -static struct commit_list *do_find_bisection(struct commit_list *list, - int nr, int *weights, - int find_all) -{ - int n, counted; - struct commit_list *p; - - counted = 0; - - for (n = 0, p = list; p; p = p->next) { - struct commit *commit = p->item; - unsigned flags = commit->object.flags; - - p->item->util = &weights[n++]; - switch (count_interesting_parents(commit)) { - case 0: - if (!(flags & TREESAME)) { - weight_set(p, 1); - counted++; - show_list("bisection 2 count one", - counted, nr, list); - } - /* - * otherwise, it is known not to reach any - * tree-changing commit and gets weight 0. - */ - break; - case 1: - weight_set(p, -1); - break; - default: - weight_set(p, -2); - break; - } - } - - show_list("bisection 2 initialize", counted, nr, list); - - /* - * If you have only one parent in the resulting set - * then you can reach one commit more than that parent - * can reach. So we do not have to run the expensive - * count_distance() for single strand of pearls. - * - * However, if you have more than one parents, you cannot - * just add their distance and one for yourself, since - * they usually reach the same ancestor and you would - * end up counting them twice that way. - * - * So we will first count distance of merges the usual - * way, and then fill the blanks using cheaper algorithm. - */ - for (p = list; p; p = p->next) { - if (p->item->object.flags & UNINTERESTING) - continue; - if (weight(p) != -2) - continue; - weight_set(p, count_distance(p)); - clear_distance(list); - - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) - return p; - counted++; - } - - show_list("bisection 2 count_distance", counted, nr, list); - - while (counted < nr) { - for (p = list; p; p = p->next) { - struct commit_list *q; - unsigned flags = p->item->object.flags; - - if (0 <= weight(p)) - continue; - for (q = p->item->parents; q; q = q->next) { - if (q->item->object.flags & UNINTERESTING) - continue; - if (0 <= weight(q)) - break; - } - if (!q) - continue; - - /* - * weight for p is unknown but q is known. - * add one for p itself if p is to be counted, - * otherwise inherit it from q directly. - */ - if (!(flags & TREESAME)) { - weight_set(p, weight(q)+1); - counted++; - show_list("bisection 2 count one", - counted, nr, list); - } - else - weight_set(p, weight(q)); - - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) - return p; - } - } - - show_list("bisection 2 counted all", counted, nr, list); - - 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 find_all) -{ - int nr, on_list; - struct commit_list *p, *best, *next, *last; - int *weights; - - show_list("bisection 2 entry", 0, 0, list); - - /* - * Count the number of total and tree-changing items on the - * list, while reversing the list. - */ - for (nr = on_list = 0, last = NULL, p = list; - p; - p = next) { - unsigned flags = p->item->object.flags; - - next = p->next; - if (flags & UNINTERESTING) - continue; - p->next = last; - last = p; - if (!(flags & TREESAME)) - nr++; - on_list++; - } - list = last; - show_list("bisection 2 sorted", 0, nr, list); - - *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); - - /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights, find_all); - if (best) { - if (!find_all) - best->next = NULL; - *reaches = weight(best); - } - free(weights); - return best; -} - static inline int log2i(int n) { int log2 = 0; From 9996983c9c35353f352ef7c1abd9b3d2fbb21114 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:30 +0100 Subject: [PATCH 06/50] rev-list: move code to show bisect vars into its own function This is a straightforward clean up to make "cmd_rev_list" function smaller. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- builtin-rev-list.c | 83 +++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index b1e8200d2b..74d22b4658 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -226,6 +226,49 @@ static int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } +static int show_bisect_vars(int reaches, int all, int bisect_find_all) +{ + int cnt; + char hex[41]; + + if (!revs.commits) + return 1; + + /* + * revs.commits can reach "reaches" commits among + * "all" commits. If it is good, then there are + * (all-reaches) commits left to be bisected. + * On the other hand, if it is bad, then the set + * to bisect is "reaches". + * A bisect set of size N has (N-1) commits further + * to test, as we already know one bad one. + */ + 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" + "bisect_steps=%d\n", + hex, + cnt - 1, + all - reaches - 1, + reaches - 1, + all, + estimate_bisect_steps(all)); + + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct commit_list *list; @@ -313,44 +356,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) 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; - /* - * revs.commits can reach "reaches" commits among - * "all" commits. If it is good, then there are - * (all-reaches) commits left to be bisected. - * On the other hand, if it is bad, then the set - * to bisect is "reaches". - * A bisect set of size N has (N-1) commits further - * to test, as we already know one bad one. - */ - 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" - "bisect_steps=%d\n", - hex, - cnt - 1, - all - reaches - 1, - reaches - 1, - all, - estimate_bisect_steps(all)); - return 0; - } + if (bisect_show_vars) + return show_bisect_vars(reaches, all, bisect_find_all); } traverse_commit_list(&revs, From 6a17fad73369173ca71d3adf0d4335a0c8137cb9 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:35 +0100 Subject: [PATCH 07/50] rev-list: make "show_bisect_vars" non static and declare it in "bisect.h" as we will use this function later. While at it, rename its last argument "show_all" instead of "bisect_find_all". Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.h | 2 ++ builtin-rev-list.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bisect.h b/bisect.h index 60b2fe1cdc..860a15c9b8 100644 --- a/bisect.h +++ b/bisect.h @@ -5,4 +5,6 @@ extern struct commit_list *find_bisection(struct commit_list *list, int *reaches, int *all, int find_all); +extern int show_bisect_vars(int reaches, int all, int show_all); + #endif diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 74d22b4658..c700c34be5 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -226,7 +226,7 @@ static int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -static int show_bisect_vars(int reaches, int all, int bisect_find_all) +int show_bisect_vars(int reaches, int all, int show_all) { int cnt; char hex[41]; @@ -246,9 +246,10 @@ static int show_bisect_vars(int reaches, int all, int bisect_find_all) cnt = all - reaches; if (cnt < reaches) cnt = reaches; + strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); - if (bisect_find_all) { + if (show_all) { traverse_commit_list(&revs, show_commit, show_object); printf("------\n"); } From 7428d754e2dec9e82253d1e02b4df20fab3f3384 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:41 +0100 Subject: [PATCH 08/50] rev-list: pass "revs" to "show_bisect_vars" instead of using static "revs" data Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.h | 3 ++- builtin-rev-list.c | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bisect.h b/bisect.h index 860a15c9b8..31c99fe5f4 100644 --- a/bisect.h +++ b/bisect.h @@ -5,6 +5,7 @@ extern struct commit_list *find_bisection(struct commit_list *list, int *reaches, int *all, int find_all); -extern int show_bisect_vars(int reaches, int all, int show_all); +extern int show_bisect_vars(struct rev_info *revs, int reaches, int all, + int show_all); #endif diff --git a/builtin-rev-list.c b/builtin-rev-list.c index c700c34be5..cdb0f9d913 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -226,16 +226,16 @@ static int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -int show_bisect_vars(int reaches, int all, int show_all) +int show_bisect_vars(struct rev_info *revs, int reaches, int all, int show_all) { int cnt; char hex[41]; - if (!revs.commits) + if (!revs->commits) return 1; /* - * revs.commits can reach "reaches" commits among + * revs->commits can reach "reaches" commits among * "all" commits. If it is good, then there are * (all-reaches) commits left to be bisected. * On the other hand, if it is bad, then the set @@ -247,10 +247,10 @@ int show_bisect_vars(int reaches, int all, int show_all) if (cnt < reaches) cnt = reaches; - strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); + strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); if (show_all) { - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(revs, show_commit, show_object); printf("------\n"); } @@ -358,7 +358,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, bisect_find_all); if (bisect_show_vars) - return show_bisect_vars(reaches, all, bisect_find_all); + return show_bisect_vars(&revs, reaches, all, + bisect_find_all); } traverse_commit_list(&revs, From 96beef8c2efaab06f703991ed7802b8cef4c00e3 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 4 Apr 2009 22:59:26 +0200 Subject: [PATCH 09/50] sha1-lookup: add new "sha1_pos" function to efficiently lookup sha1 This function has been copied from the "patch_pos" function in "patch-ids.c" but an additional parameter has been added. The new parameter is a function pointer, that is used to access the sha1 of an element in the table. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- sha1-lookup.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ sha1-lookup.h | 7 ++++ 2 files changed, 108 insertions(+) diff --git a/sha1-lookup.c b/sha1-lookup.c index da357479cf..055dd87dc1 100644 --- a/sha1-lookup.c +++ b/sha1-lookup.c @@ -1,6 +1,107 @@ #include "cache.h" #include "sha1-lookup.h" +static uint32_t take2(const unsigned char *sha1) +{ + return ((sha1[0] << 8) | sha1[1]); +} + +/* + * Conventional binary search loop looks like this: + * + * do { + * int mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); + * + * The invariants are: + * + * - When entering the loop, lo points at a slot that is never + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). + * + * - We find a point 'mi' between lo and hi (mi could be the same + * as lo, but never can be the same as hi), and check if it hits + * the target. There are three cases: + * + * - if it is a hit, we are happy. + * + * - if it is strictly higher than the target, we update hi with + * it. + * + * - if it is strictly lower than the target, we update lo to be + * one slot after it, because we allow lo to be at the target. + * + * When choosing 'mi', we do not have to take the "middle" but + * anywhere in between lo and hi, as long as lo <= mi < hi is + * satisfied. When we somehow know that the distance between the + * target and lo is much shorter than the target and hi, we could + * pick mi that is much closer to lo than the midway. + */ +/* + * The table should contain "nr" elements. + * The sha1 of element i (between 0 and nr - 1) should be returned + * by "fn(i, table)". + */ +int sha1_pos(const unsigned char *sha1, void *table, size_t nr, + sha1_access_fn fn) +{ + size_t hi = nr; + size_t lo = 0; + size_t mi = 0; + + if (!nr) + return -1; + + if (nr != 1) { + size_t lov, hiv, miv, ofs; + + for (ofs = 0; ofs < 18; ofs += 2) { + lov = take2(fn(0, table) + ofs); + hiv = take2(fn(nr - 1, table) + ofs); + miv = take2(sha1 + ofs); + if (miv < lov) + return -1; + if (hiv < miv) + return -1 - nr; + if (lov != hiv) { + /* + * At this point miv could be equal + * to hiv (but sha1 could still be higher); + * the invariant of (mi < hi) should be + * kept. + */ + mi = (nr - 1) * (miv - lov) / (hiv - lov); + if (lo <= mi && mi < hi) + break; + die("oops"); + } + } + if (18 <= ofs) + die("cannot happen -- lo and hi are identical"); + } + + do { + int cmp; + cmp = hashcmp(fn(mi, table), sha1); + if (!cmp) + return mi; + if (cmp > 0) + hi = mi; + else + lo = mi + 1; + mi = (hi + lo) / 2; + } while (lo < hi); + return -lo-1; +} + /* * Conventional binary search loop looks like this: * diff --git a/sha1-lookup.h b/sha1-lookup.h index 3249a81b3d..20af285681 100644 --- a/sha1-lookup.h +++ b/sha1-lookup.h @@ -1,6 +1,13 @@ #ifndef SHA1_LOOKUP_H #define SHA1_LOOKUP_H +typedef const unsigned char *sha1_access_fn(size_t index, void *table); + +extern int sha1_pos(const unsigned char *sha1, + void *table, + size_t nr, + sha1_access_fn fn); + extern int sha1_entry_pos(const void *table, size_t elem_size, size_t key_offset, From 5289bae17f24805cc8507129e21d794b0b56264c Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 4 Apr 2009 22:59:31 +0200 Subject: [PATCH 10/50] patch-ids: use the new generic "sha1_pos" function to lookup sha1 instead of the specific one from which the new one has been copied. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- patch-ids.c | 93 +++-------------------------------------------------- 1 file changed, 5 insertions(+), 88 deletions(-) diff --git a/patch-ids.c b/patch-ids.c index 3be5d3165e..5717257051 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -1,6 +1,7 @@ #include "cache.h" #include "diff.h" #include "commit.h" +#include "sha1-lookup.h" #include "patch-ids.h" static int commit_patch_id(struct commit *commit, struct diff_options *options, @@ -15,99 +16,15 @@ static int commit_patch_id(struct commit *commit, struct diff_options *options, return diff_flush_patch_id(options, sha1); } -static uint32_t take2(const unsigned char *id) +static const unsigned char *patch_id_access(size_t index, void *table) { - return ((id[0] << 8) | id[1]); + struct patch_id **id_table = table; + return id_table[index]->patch_id; } -/* - * Conventional binary search loop looks like this: - * - * do { - * int mi = (lo + hi) / 2; - * int cmp = "entry pointed at by mi" minus "target"; - * if (!cmp) - * return (mi is the wanted one) - * if (cmp > 0) - * hi = mi; "mi is larger than target" - * else - * lo = mi+1; "mi is smaller than target" - * } while (lo < hi); - * - * The invariants are: - * - * - When entering the loop, lo points at a slot that is never - * above the target (it could be at the target), hi points at a - * slot that is guaranteed to be above the target (it can never - * be at the target). - * - * - We find a point 'mi' between lo and hi (mi could be the same - * as lo, but never can be the same as hi), and check if it hits - * the target. There are three cases: - * - * - if it is a hit, we are happy. - * - * - if it is strictly higher than the target, we update hi with - * it. - * - * - if it is strictly lower than the target, we update lo to be - * one slot after it, because we allow lo to be at the target. - * - * When choosing 'mi', we do not have to take the "middle" but - * anywhere in between lo and hi, as long as lo <= mi < hi is - * satisfied. When we somehow know that the distance between the - * target and lo is much shorter than the target and hi, we could - * pick mi that is much closer to lo than the midway. - */ static int patch_pos(struct patch_id **table, int nr, const unsigned char *id) { - int hi = nr; - int lo = 0; - int mi = 0; - - if (!nr) - return -1; - - if (nr != 1) { - unsigned lov, hiv, miv, ofs; - - for (ofs = 0; ofs < 18; ofs += 2) { - lov = take2(table[0]->patch_id + ofs); - hiv = take2(table[nr-1]->patch_id + ofs); - miv = take2(id + ofs); - if (miv < lov) - return -1; - if (hiv < miv) - return -1 - nr; - if (lov != hiv) { - /* - * At this point miv could be equal - * to hiv (but id could still be higher); - * the invariant of (mi < hi) should be - * kept. - */ - mi = (nr-1) * (miv - lov) / (hiv - lov); - if (lo <= mi && mi < hi) - break; - die("oops"); - } - } - if (18 <= ofs) - die("cannot happen -- lo and hi are identical"); - } - - do { - int cmp; - cmp = hashcmp(table[mi]->patch_id, id); - if (!cmp) - return mi; - if (cmp > 0) - hi = mi; - else - lo = mi + 1; - mi = (hi + lo) / 2; - } while (lo < hi); - return -lo-1; + return sha1_pos(id, table, nr, patch_id_access); } #define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */ From 951886481668b97485640a1b24fc73fccff0d629 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:49 +0100 Subject: [PATCH 11/50] rev-list: call new "filter_skip" function This patch implements a new "filter_skip" function in C in "bisect.c" that will later replace the existing implementation in shell in "git-bisect.sh". An array is used to store the skipped commits. But the array is not yet fed anything. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++ bisect.h | 6 ++++- builtin-rev-list.c | 30 ++++++++++++++++++---- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/bisect.c b/bisect.c index 27def7dacf..9178a7a8e2 100644 --- a/bisect.c +++ b/bisect.c @@ -4,6 +4,9 @@ #include "revision.h" #include "bisect.h" +static unsigned char (*skipped_sha1)[20]; +static int skipped_sha1_nr; + /* bits #0-15 in revision.h */ #define COUNTED (1u<<16) @@ -386,3 +389,63 @@ struct commit_list *find_bisection(struct commit_list *list, return best; } +static int skipcmp(const void *a, const void *b) +{ + return hashcmp(a, b); +} + +static void prepare_skipped(void) +{ + qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp); +} + +static int lookup_skipped(unsigned char *sha1) +{ + int lo, hi; + lo = 0; + hi = skipped_sha1_nr; + while (lo < hi) { + int mi = (lo + hi) / 2; + int cmp = hashcmp(sha1, skipped_sha1[mi]); + if (!cmp) + return mi; + if (cmp < 0) + hi = mi; + else + lo = mi + 1; + } + return -lo - 1; +} + +struct commit_list *filter_skipped(struct commit_list *list, + struct commit_list **tried, + int show_all) +{ + struct commit_list *filtered = NULL, **f = &filtered; + + *tried = NULL; + + if (!skipped_sha1_nr) + return list; + + prepare_skipped(); + + while (list) { + struct commit_list *next = list->next; + list->next = NULL; + if (0 <= lookup_skipped(list->item->object.sha1)) { + /* Move current to tried list */ + *tried = list; + tried = &list->next; + } else { + if (!show_all) + return list; + /* Move current to filtered list */ + *f = list; + f = &list->next; + } + list = next; + } + + return filtered; +} diff --git a/bisect.h b/bisect.h index 31c99fe5f4..2489630da0 100644 --- a/bisect.h +++ b/bisect.h @@ -5,7 +5,11 @@ extern struct commit_list *find_bisection(struct commit_list *list, int *reaches, int *all, int find_all); +extern struct commit_list *filter_skipped(struct commit_list *list, + struct commit_list **tried, + int show_all); + extern int show_bisect_vars(struct rev_info *revs, int reaches, int all, - int show_all); + int show_all, int show_tried); #endif diff --git a/builtin-rev-list.c b/builtin-rev-list.c index cdb0f9d913..925d64356c 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -226,14 +226,28 @@ static int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -int show_bisect_vars(struct rev_info *revs, int reaches, int all, int show_all) +static void show_tried_revs(struct commit_list *tried) +{ + printf("bisect_tried='"); + for (;tried; tried = tried->next) { + char *format = tried->next ? "%s|" : "%s"; + printf(format, sha1_to_hex(tried->item->object.sha1)); + } + printf("'\n"); +} + +int show_bisect_vars(struct rev_info *revs, int reaches, int all, + int show_all, int show_tried) { int cnt; - char hex[41]; + char hex[41] = ""; + struct commit_list *tried; - if (!revs->commits) + if (!revs->commits && !show_tried) return 1; + revs->commits = filter_skipped(revs->commits, &tried, show_all); + /* * revs->commits can reach "reaches" commits among * "all" commits. If it is good, then there are @@ -247,13 +261,16 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int show_all) if (cnt < reaches) cnt = reaches; - strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); + if (revs->commits) + strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); if (show_all) { traverse_commit_list(revs, show_commit, show_object); printf("------\n"); } + if (show_tried) + show_tried_revs(tried); printf("bisect_rev=%s\n" "bisect_nr=%d\n" "bisect_good=%d\n" @@ -278,6 +295,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; + int bisect_show_all = 0; int quiet = 0; git_config(git_default_config, NULL); @@ -305,6 +323,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--bisect-all")) { bisect_list = 1; bisect_find_all = 1; + bisect_show_all = 1; revs.show_decorations = 1; continue; } @@ -357,9 +376,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, bisect_find_all); + if (bisect_show_vars) return show_bisect_vars(&revs, reaches, all, - bisect_find_all); + bisect_show_all, 0); } traverse_commit_list(&revs, From 4eb5b64631d281f3789b052efac53f4c1ec2c1b6 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sat, 4 Apr 2009 22:59:36 +0200 Subject: [PATCH 12/50] bisect: use the new generic "sha1_pos" function to lookup sha1 instead of the specific one that was simpler but less efficient. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/bisect.c b/bisect.c index 9178a7a8e2..47120c1cd8 100644 --- a/bisect.c +++ b/bisect.c @@ -2,6 +2,7 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "sha1-lookup.h" #include "bisect.h" static unsigned char (*skipped_sha1)[20]; @@ -399,22 +400,16 @@ static void prepare_skipped(void) qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp); } +static const unsigned char *skipped_sha1_access(size_t index, void *table) +{ + unsigned char (*skipped)[20] = table; + return skipped[index]; +} + static int lookup_skipped(unsigned char *sha1) { - int lo, hi; - lo = 0; - hi = skipped_sha1_nr; - while (lo < hi) { - int mi = (lo + hi) / 2; - int cmp = hashcmp(sha1, skipped_sha1[mi]); - if (!cmp) - return mi; - if (cmp < 0) - hi = mi; - else - lo = mi + 1; - } - return -lo - 1; + return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr, + skipped_sha1_access); } struct commit_list *filter_skipped(struct commit_list *list, From 1bf072e3661eeef8d9721079a332e804b5678c7e Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:54 +0100 Subject: [PATCH 13/50] bisect--helper: implement "git bisect--helper" This patch implements a new "git bisect--helper" builtin plumbing command that will be used to migrate "git-bisect.sh" to C. We start by implementing only the "--next-vars" option that will read bisect refs from "refs/bisect/", and then compute the next bisect step, and output shell variables ready to be eval'ed by the shell. At this step, "git bisect--helper" ignores the paths that may have been put in "$GIT_DIR/BISECT_NAMES". This will be fixed in a later patch. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Makefile | 1 + bisect.c | 68 ++++++++++++++++++++++++++++++++++++++++ bisect.h | 7 +++++ builtin-bisect--helper.c | 27 ++++++++++++++++ builtin.h | 1 + git.c | 1 + 6 files changed, 105 insertions(+) create mode 100644 builtin-bisect--helper.c diff --git a/Makefile b/Makefile index 42cabe8162..a2bfad43bc 100644 --- a/Makefile +++ b/Makefile @@ -533,6 +533,7 @@ BUILTIN_OBJS += builtin-add.o BUILTIN_OBJS += builtin-annotate.o BUILTIN_OBJS += builtin-apply.o BUILTIN_OBJS += builtin-archive.o +BUILTIN_OBJS += builtin-bisect--helper.o BUILTIN_OBJS += builtin-blame.o BUILTIN_OBJS += builtin-branch.o BUILTIN_OBJS += builtin-bundle.o diff --git a/bisect.c b/bisect.c index 47120c1cd8..94ec011786 100644 --- a/bisect.c +++ b/bisect.c @@ -2,11 +2,18 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "refs.h" +#include "list-objects.h" #include "sha1-lookup.h" #include "bisect.h" static unsigned char (*skipped_sha1)[20]; static int skipped_sha1_nr; +static int skipped_sha1_alloc; + +static const char **rev_argv; +static int rev_argv_nr; +static int rev_argv_alloc; /* bits #0-15 in revision.h */ @@ -390,6 +397,33 @@ struct commit_list *find_bisection(struct commit_list *list, return best; } +static int register_ref(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + if (!strcmp(refname, "bad")) { + ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); + rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1)); + } else if (!prefixcmp(refname, "good-")) { + const char *hex = sha1_to_hex(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-")) { + ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1, + skipped_sha1_alloc); + hashcpy(skipped_sha1[skipped_sha1_nr++], sha1); + } + + return 0; +} + +static int read_bisect_refs(void) +{ + return for_each_ref_in("refs/bisect/", register_ref, NULL); +} + static int skipcmp(const void *a, const void *b) { return hashcmp(a, b); @@ -444,3 +478,37 @@ struct commit_list *filter_skipped(struct commit_list *list, return filtered; } + +int bisect_next_vars(const char *prefix) +{ + struct rev_info revs; + int reaches = 0, all = 0; + + init_revisions(&revs, prefix); + revs.abbrev = 0; + revs.commit_format = CMIT_FMT_UNSPECIFIED; + + /* argv[0] will be ignored by setup_revisions */ + ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); + rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup"); + + if (read_bisect_refs()) + die("reading bisect refs failed"); + + ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); + rev_argv[rev_argv_nr++] = xstrdup("--"); + + setup_revisions(rev_argv_nr, rev_argv, &revs, NULL); + + revs.limited = 1; + + 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_sha1_nr); + + return show_bisect_vars(&revs, reaches, all, 0, 1); +} diff --git a/bisect.h b/bisect.h index 2489630da0..05eea175f7 100644 --- a/bisect.h +++ b/bisect.h @@ -9,7 +9,14 @@ extern struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, int show_all); +/* + * The "show_all" parameter should be 0 if this function is called + * from outside "builtin-rev-list.c" as otherwise it would use + * static "revs" from this file. + */ extern int show_bisect_vars(struct rev_info *revs, int reaches, int all, int show_all, int show_tried); +extern int bisect_next_vars(const char *prefix); + #endif diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c new file mode 100644 index 0000000000..8fe778766a --- /dev/null +++ b/builtin-bisect--helper.c @@ -0,0 +1,27 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "bisect.h" + +static const char * const git_bisect_helper_usage[] = { + "git bisect--helper --next-vars", + NULL +}; + +int cmd_bisect__helper(int argc, const char **argv, const char *prefix) +{ + int next_vars = 0; + struct option options[] = { + OPT_BOOLEAN(0, "next-vars", &next_vars, + "output next bisect step variables"), + OPT_END() + }; + + argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0); + + if (!next_vars) + usage_with_options(git_bisect_helper_usage, options); + + /* next-vars */ + return bisect_next_vars(prefix); +} diff --git a/builtin.h b/builtin.h index 1495cf6a20..425ff8e89b 100644 --- a/builtin.h +++ b/builtin.h @@ -25,6 +25,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); +extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix); extern int cmd_blame(int argc, const char **argv, const char *prefix); extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_bundle(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index c2b181ed78..a553926b68 100644 --- a/git.c +++ b/git.c @@ -271,6 +271,7 @@ static void handle_internal_command(int argc, const char **argv) { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE }, { "blame", cmd_blame, RUN_SETUP }, { "branch", cmd_branch, RUN_SETUP }, { "bundle", cmd_bundle }, From 3b437b0dabfdff12d5dd78b9bb55a0be4e2da51c Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:55:59 +0100 Subject: [PATCH 14/50] bisect: implement "read_bisect_paths" to read paths in "$GIT_DIR/BISECT_NAMES" This is needed because "git bisect--helper" must read bisect paths in "$GIT_DIR/BISECT_NAMES", so that a bisection can be performed only on commits that touches paths in this file. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/bisect.c b/bisect.c index 94ec011786..3279fb12b2 100644 --- a/bisect.c +++ b/bisect.c @@ -4,6 +4,7 @@ #include "revision.h" #include "refs.h" #include "list-objects.h" +#include "quote.h" #include "sha1-lookup.h" #include "bisect.h" @@ -424,6 +425,32 @@ static int read_bisect_refs(void) return for_each_ref_in("refs/bisect/", register_ref, NULL); } +void read_bisect_paths(void) +{ + struct strbuf str = STRBUF_INIT; + const char *filename = git_path("BISECT_NAMES"); + FILE *fp = fopen(filename, "r"); + + if (!fp) + die("Could not open file '%s': %s", filename, strerror(errno)); + + while (strbuf_getline(&str, fp, '\n') != EOF) { + char *quoted; + int res; + + strbuf_trim(&str); + quoted = strbuf_detach(&str, NULL); + res = sq_dequote_to_argv(quoted, &rev_argv, + &rev_argv_nr, &rev_argv_alloc); + if (res) + die("Badly quoted content in file '%s': %s", + filename, quoted); + } + + strbuf_release(&str); + fclose(fp); +} + static int skipcmp(const void *a, const void *b) { return hashcmp(a, b); @@ -479,14 +506,11 @@ struct commit_list *filter_skipped(struct commit_list *list, return filtered; } -int bisect_next_vars(const char *prefix) +static void bisect_rev_setup(struct rev_info *revs, const char *prefix) { - struct rev_info revs; - int reaches = 0, all = 0; - - init_revisions(&revs, prefix); - revs.abbrev = 0; - revs.commit_format = CMIT_FMT_UNSPECIFIED; + init_revisions(revs, prefix); + revs->abbrev = 0; + revs->commit_format = CMIT_FMT_UNSPECIFIED; /* argv[0] will be ignored by setup_revisions */ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); @@ -498,9 +522,22 @@ int bisect_next_vars(const char *prefix) ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); rev_argv[rev_argv_nr++] = xstrdup("--"); - setup_revisions(rev_argv_nr, rev_argv, &revs, NULL); + read_bisect_paths(); - revs.limited = 1; + 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; + int reaches = 0, all = 0; + + bisect_rev_setup(&revs, prefix); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); From 23b5f18b50c15155f79618522b5721b880eceb65 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Thu, 26 Mar 2009 05:56:02 +0100 Subject: [PATCH 15/50] bisect: use "bisect--helper" and remove "filter_skipped" function Use the new "git bisect--helper" builtin. It should be faster and safer instead of the old "filter_skipped" shell function. And it is a first step to move more shell code to C. As the output is a little bit different we have to change the code that interpret the results. But these changes improve code clarity. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- git-bisect.sh | 89 +++++++-------------------------------------------- 1 file changed, 12 insertions(+), 77 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index e313bdea70..0f7590dfc2 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -279,76 +279,13 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } -filter_skipped() { +eval_and_string_together() { _eval="$1" - _skip="$2" - if [ -z "$_skip" ]; then - eval "$_eval" | { - while read line - do - echo "$line &&" - done - echo ':' - } - return - fi - - # Let's parse the output of: - # "git rev-list --bisect-vars --bisect-all ..." eval "$_eval" | { - VARS= FOUND= TRIED= - while read hash line + while read line do - case "$VARS,$FOUND,$TRIED,$hash" in - 1,*,*,*) - # "bisect_foo=bar" read from rev-list output. - echo "$hash &&" - ;; - ,*,*,---*) - # Separator - ;; - ,,,bisect_rev*) - # We had nothing to search. - echo "bisect_rev= &&" - VARS=1 - ;; - ,,*,bisect_rev*) - # We did not find a good bisect rev. - # This should happen only if the "bad" - # commit is also a "skip" commit. - 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 - ;; - ,1,*,bisect_rev*) - # We have already found a rev to be tested. - VARS=1 - ;; - ,1,*,*) - ;; - *) - # Unexpected input - echo "die 'filter_skipped error'" - die "filter_skipped error " \ - "VARS: '$VARS' " \ - "FOUND: '$FOUND' " \ - "TRIED: '$TRIED' " \ - "hash: '$hash' " \ - "line: '$line'" - ;; - esac + echo "$line &&" done echo ':' } @@ -356,10 +293,12 @@ filter_skipped() { exit_if_skipped_commits () { _tried=$1 - if expr "$_tried" : ".*[|].*" > /dev/null ; then + _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 @@ -490,28 +429,24 @@ bisect_next() { 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="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && - eval=$(filter_skipped "$eval" "$skip") && + eval="git bisect--helper --next-vars" && + eval=$(eval_and_string_together "$eval") && eval "$eval" || exit if [ -z "$bisect_rev" ]; then + # 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" + exit_if_skipped_commits "$bisect_tried" "$bad" 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" - bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)" } From b74d7efb108c9d3fd2d057b0c452583552a0577a Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sun, 29 Mar 2009 11:45:01 +0200 Subject: [PATCH 16/50] t6030: test bisecting with paths This patch adds some tests to check that "git bisect" works fine when passing paths to "git bisect start" to reduce the number of bisection steps. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- t/t6030-bisect-porcelain.sh | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 052a6c90f5..54b7ea6505 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -506,6 +506,66 @@ test_expect_success 'optimized merge base checks' ' unset GIT_TRACE ' +# This creates another side branch called "parallel" with some files +# in some directories, to test bisecting with paths. +# +# We should have the following: +# +# P1-P2-P3-P4-P5-P6-P7 +# / / / +# H1-H2-H3-H4-H5-H6-H7 +# \ \ \ +# S5-A \ +# \ \ +# S6-S7----B +# +test_expect_success '"parallel" side branch creation' ' + git bisect reset && + git checkout -b parallel $HASH1 && + mkdir dir1 dir2 && + add_line_into_file "1(para): line 1 on parallel branch" dir1/file1 && + PARA_HASH1=$(git rev-parse --verify HEAD) && + add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 && + PARA_HASH2=$(git rev-parse --verify HEAD) && + add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 && + PARA_HASH3=$(git rev-parse --verify HEAD) + git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" && + PARA_HASH4=$(git rev-parse --verify HEAD) + add_line_into_file "5(para): add line on parallel branch" dir1/file1 && + PARA_HASH5=$(git rev-parse --verify HEAD) + add_line_into_file "6(para): add line on parallel branch" dir2/file2 && + PARA_HASH6=$(git rev-parse --verify HEAD) + git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" && + PARA_HASH7=$(git rev-parse --verify HEAD) +' + +test_expect_success 'restricting bisection on one dir' ' + git bisect reset && + git bisect start HEAD $HASH1 -- dir1 && + para1=$(git rev-parse --verify HEAD) && + test "$para1" = "$PARA_HASH1" && + git bisect bad > my_bisect_log.txt && + grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt +' + +test_expect_success 'restricting bisection on one dir and a file' ' + git bisect reset && + git bisect start HEAD $HASH1 -- dir1 hello && + para4=$(git rev-parse --verify HEAD) && + test "$para4" = "$PARA_HASH4" && + git bisect bad && + hash3=$(git rev-parse --verify HEAD) && + test "$hash3" = "$HASH3" && + git bisect good && + hash4=$(git rev-parse --verify HEAD) && + test "$hash4" = "$HASH4" && + git bisect good && + para1=$(git rev-parse --verify HEAD) && + test "$para1" = "$PARA_HASH1" && + git bisect good > my_bisect_log.txt && + grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt +' + # # test_done From 37c4c38d7356bf256d0297fdbac78ef8b6807fac Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Sun, 29 Mar 2009 11:55:43 +0200 Subject: [PATCH 17/50] rev-list: pass "int flags" as last argument of "show_bisect_vars" Instead of "int show_all, int show_tried" we now only pass "int flags", because we will add one more flag in a later patch. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 2 +- bisect.h | 8 ++++++-- builtin-rev-list.c | 13 ++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bisect.c b/bisect.c index 3279fb12b2..285bf146c1 100644 --- a/bisect.c +++ b/bisect.c @@ -547,5 +547,5 @@ int bisect_next_vars(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_sha1_nr); - return show_bisect_vars(&revs, reaches, all, 0, 1); + return show_bisect_vars(&revs, reaches, all, BISECT_SHOW_TRIED); } diff --git a/bisect.h b/bisect.h index 05eea175f7..b9aa88482e 100644 --- a/bisect.h +++ b/bisect.h @@ -9,13 +9,17 @@ extern struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, int show_all); +/* show_bisect_vars flags */ +#define BISECT_SHOW_ALL (1<<0) +#define BISECT_SHOW_TRIED (1<<1) + /* - * The "show_all" parameter should be 0 if this function is called + * The flag BISECT_SHOW_ALL should not be set if this function is called * from outside "builtin-rev-list.c" as otherwise it would use * static "revs" from this file. */ extern int show_bisect_vars(struct rev_info *revs, int reaches, int all, - int show_all, int show_tried); + int flags); extern int bisect_next_vars(const char *prefix); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 925d64356c..69dca631d9 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -236,17 +236,16 @@ static void show_tried_revs(struct commit_list *tried) printf("'\n"); } -int show_bisect_vars(struct rev_info *revs, int reaches, int all, - int show_all, int show_tried) +int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) { int cnt; char hex[41] = ""; struct commit_list *tried; - if (!revs->commits && !show_tried) + if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) return 1; - revs->commits = filter_skipped(revs->commits, &tried, show_all); + revs->commits = filter_skipped(revs->commits, &tried, flags & BISECT_SHOW_ALL); /* * revs->commits can reach "reaches" commits among @@ -264,12 +263,12 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, if (revs->commits) strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); - if (show_all) { + if (flags & BISECT_SHOW_ALL) { traverse_commit_list(revs, show_commit, show_object); printf("------\n"); } - if (show_tried) + if (flags & BISECT_SHOW_TRIED) show_tried_revs(tried); printf("bisect_rev=%s\n" "bisect_nr=%d\n" @@ -379,7 +378,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_show_vars) return show_bisect_vars(&revs, reaches, all, - bisect_show_all, 0); + bisect_show_all ? BISECT_SHOW_ALL : 0); } traverse_commit_list(&revs, From e89aa6d2f546b2d4f2d88c15ce7e343751d6922f Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 30 Mar 2009 06:59:59 +0200 Subject: [PATCH 18/50] bisect--helper: string output variables together with "&&" When doing: eval "git bisect--helper --next-vars" | { while read line do echo "$line &&" done echo ':' } the result code comes from the last "echo ':'", not from running "git bisect--helper --next-vars". This patch gets rid of the need to string together the line from the output of "git bisect--helper" with "&&" in the calling script by making "git bisect--helper --next-vars" return output variables already in that format. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 3 ++- bisect.h | 1 + builtin-rev-list.c | 29 +++++++++++++++++++---------- git-bisect.sh | 15 +-------------- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/bisect.c b/bisect.c index 285bf146c1..69f8860ca1 100644 --- a/bisect.c +++ b/bisect.c @@ -547,5 +547,6 @@ int bisect_next_vars(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_sha1_nr); - return show_bisect_vars(&revs, reaches, all, BISECT_SHOW_TRIED); + return show_bisect_vars(&revs, reaches, all, + BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED); } diff --git a/bisect.h b/bisect.h index b9aa88482e..f5d106735c 100644 --- a/bisect.h +++ b/bisect.h @@ -12,6 +12,7 @@ extern struct commit_list *filter_skipped(struct commit_list *list, /* show_bisect_vars flags */ #define BISECT_SHOW_ALL (1<<0) #define BISECT_SHOW_TRIED (1<<1) +#define BISECT_SHOW_STRINGED (1<<2) /* * The flag BISECT_SHOW_ALL should not be set if this function is called diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 69dca631d9..eb341477ca 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -226,20 +226,20 @@ static int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -static void show_tried_revs(struct commit_list *tried) +static void show_tried_revs(struct commit_list *tried, int stringed) { printf("bisect_tried='"); for (;tried; tried = tried->next) { char *format = tried->next ? "%s|" : "%s"; printf(format, sha1_to_hex(tried->item->object.sha1)); } - printf("'\n"); + printf(stringed ? "' &&\n" : "'\n"); } int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) { int cnt; - char hex[41] = ""; + char hex[41] = "", *format; struct commit_list *tried; if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) @@ -269,13 +269,22 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) } if (flags & BISECT_SHOW_TRIED) - show_tried_revs(tried); - printf("bisect_rev=%s\n" - "bisect_nr=%d\n" - "bisect_good=%d\n" - "bisect_bad=%d\n" - "bisect_all=%d\n" - "bisect_steps=%d\n", + show_tried_revs(tried, flags & BISECT_SHOW_STRINGED); + format = (flags & BISECT_SHOW_STRINGED) ? + "bisect_rev=%s &&\n" + "bisect_nr=%d &&\n" + "bisect_good=%d &&\n" + "bisect_bad=%d &&\n" + "bisect_all=%d &&\n" + "bisect_steps=%d\n" + : + "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, diff --git a/git-bisect.sh b/git-bisect.sh index 0f7590dfc2..5074dda451 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -279,18 +279,6 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } -eval_and_string_together() { - _eval="$1" - - eval "$_eval" | { - while read line - do - echo "$line &&" - done - echo ':' - } -} - exit_if_skipped_commits () { _tried=$1 _bad=$2 @@ -429,8 +417,7 @@ bisect_next() { test "$?" -eq "1" && return # Get bisection information - eval="git bisect--helper --next-vars" && - eval=$(eval_and_string_together "$eval") && + eval=$(eval "git bisect--helper --next-vars") && eval "$eval" || exit if [ -z "$bisect_rev" ]; then From b92c5f228a9c07fe339c8fd5406069602b6452f6 Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Fri, 3 Apr 2009 11:02:37 +0200 Subject: [PATCH 19/50] builtin-remote.c: Split out prune_remote as a separate function. prune_remote will be used in update(), so this function was split out to avoid code duplication. Signed-off-by: Finn Arne Gangstad Signed-off-by: Junio C Hamano --- builtin-remote.c | 62 ++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 9ef846f6a4..c53966fd8d 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -26,6 +26,7 @@ static const char * const builtin_remote_usage[] = { static int verbose; static int show_all(void); +static int prune_remote(const char *remote, int dry_run); static inline int postfixcmp(const char *string, const char *postfix) { @@ -1128,46 +1129,49 @@ static int prune(int argc, const char **argv) OPT__DRY_RUN(&dry_run), OPT_END() }; - struct ref_states states; - const char *dangling_msg; argc = parse_options(argc, argv, options, builtin_remote_usage, 0); if (argc < 1) usage_with_options(builtin_remote_usage, options); - dangling_msg = (dry_run - ? " %s will become dangling!\n" - : " %s has become dangling!\n"); + for (; argc; argc--, argv++) + result |= prune_remote(*argv, dry_run); + + return result; +} + +static int prune_remote(const char *remote, int dry_run) +{ + int result = 0, i; + struct ref_states states; + const char *dangling_msg = dry_run + ? " %s will become dangling!\n" + : " %s has become dangling!\n"; memset(&states, 0, sizeof(states)); - for (; argc; argc--, argv++) { - int i; + get_remote_ref_states(remote, &states, GET_REF_STATES); - get_remote_ref_states(*argv, &states, GET_REF_STATES); - - if (states.stale.nr) { - printf("Pruning %s\n", *argv); - printf("URL: %s\n", - states.remote->url_nr - ? states.remote->url[0] - : "(no URL)"); - } - - for (i = 0; i < states.stale.nr; i++) { - const char *refname = states.stale.items[i].util; - - if (!dry_run) - result |= delete_ref(refname, NULL, 0); - - printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", - abbrev_ref(refname, "refs/remotes/")); - warn_dangling_symref(dangling_msg, refname); - } - - free_remote_ref_states(&states); + if (states.stale.nr) { + printf("Pruning %s\n", remote); + printf("URL: %s\n", + states.remote->url_nr + ? states.remote->url[0] + : "(no URL)"); } + for (i = 0; i < states.stale.nr; i++) { + const char *refname = states.stale.items[i].util; + + if (!dry_run) + result |= delete_ref(refname, NULL, 0); + + printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", + abbrev_ref(refname, "refs/remotes/")); + warn_dangling_symref(dangling_msg, refname); + } + + free_remote_ref_states(&states); return result; } From efa54803cb1dc15923799f94abf82cb0433c2b9b Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Fri, 3 Apr 2009 11:03:44 +0200 Subject: [PATCH 20/50] git remote update: New option --prune With the --prune (or -p) option, git remote update will also prune all the remotes that it fetches. Previously, you had to do a manual git remote prune for each of the remotes you wanted to prune, and this could be tedious with many remotes. A single command will now update a set of remotes, and remove all stale branches: git remote update -p [group] Signed-off-by: Finn Arne Gangstad Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 4 +++- builtin-remote.c | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index c9c0e6f932..0b6e67dbca 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -16,7 +16,7 @@ SYNOPSIS 'git remote set-head' [-a | -d | ] 'git remote show' [-n] 'git remote prune' [-n | --dry-run] -'git remote update' [group] +'git remote update' [-p | --prune] [group] DESCRIPTION ----------- @@ -125,6 +125,8 @@ the configuration parameter remotes.default will get used; if remotes.default is not defined, all remotes which do not have the configuration parameter remote..skipDefaultUpdate set to true will be updated. (See linkgit:git-config[1]). ++ +With `--prune` option, prune all the remotes that are updated. DISCUSSION diff --git a/builtin-remote.c b/builtin-remote.c index c53966fd8d..3146eb467d 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -15,7 +15,7 @@ static const char * const builtin_remote_usage[] = { "git remote set-head [-a | -d | ]", "git remote show [-n] ", "git remote prune [-n | --dry-run] ", - "git remote [-v | --verbose] update [group]", + "git remote [-v | --verbose] update [-p | --prune] [group]", NULL }; @@ -1208,10 +1208,18 @@ static int get_remote_group(const char *key, const char *value, void *cb) static int update(int argc, const char **argv) { - int i, result = 0; + int i, result = 0, prune = 0; struct string_list list = { NULL, 0, 0, 0 }; static const char *default_argv[] = { NULL, "default", NULL }; + struct option options[] = { + OPT_GROUP("update specific options"), + OPT_BOOLEAN('p', "prune", &prune, + "prune remotes after fecthing"), + OPT_END() + }; + argc = parse_options(argc, argv, options, builtin_remote_usage, + PARSE_OPT_KEEP_ARGV0); if (argc < 2) { argc = 2; argv = default_argv; @@ -1226,8 +1234,12 @@ static int update(int argc, const char **argv) if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) result = for_each_remote(get_one_remote_for_update, &list); - for (i = 0; i < list.nr; i++) - result |= fetch_remote(list.items[i].string); + for (i = 0; i < list.nr; i++) { + int err = fetch_remote(list.items[i].string); + result |= err; + if (!err && prune) + result |= prune_remote(list.items[i].string, 0); + } /* all names were strdup()ed or strndup()ed */ list.strdup_strings = 1; From 1a7b1f6b9c9a129236c66c392877e8697825470f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 6 Apr 2009 00:48:49 -0700 Subject: [PATCH 21/50] sha1-lookup: fix up the assertion message Signed-off-by: Junio C Hamano --- sha1-lookup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1-lookup.c b/sha1-lookup.c index 055dd87dc1..c4dc55d1f5 100644 --- a/sha1-lookup.c +++ b/sha1-lookup.c @@ -81,7 +81,7 @@ int sha1_pos(const unsigned char *sha1, void *table, size_t nr, mi = (nr - 1) * (miv - lov) / (hiv - lov); if (lo <= mi && mi < hi) break; - die("oops"); + die("BUG: assertion failed in binary search"); } } if (18 <= ofs) From bed5d42163ec2e2ddde3b1d78d303a4fb39bc0d0 Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Mon, 6 Apr 2009 15:41:00 +0200 Subject: [PATCH 22/50] git remote update: Report error for non-existing groups Previosly, git remote update would just silently fail and do nothing. Now it will report an error saying that the group does not exist. Signed-off-by: Finn Arne Gangstad Signed-off-by: Junio C Hamano --- builtin-remote.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 3146eb467d..51df99ba93 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1188,16 +1188,18 @@ struct remote_group { struct string_list *list; } remote_group; -static int get_remote_group(const char *key, const char *value, void *cb) +static int get_remote_group(const char *key, const char *value, void *num_hits) { if (!prefixcmp(key, "remotes.") && !strcmp(key + 8, remote_group.name)) { /* split list by white space */ int space = strcspn(value, " \t\n"); while (*value) { - if (space > 1) + if (space > 1) { string_list_append(xstrndup(value, space), remote_group.list); + ++*((int *)num_hits); + } value += space + (value[space] != '\0'); space = strcspn(value, " \t\n"); } @@ -1227,8 +1229,11 @@ static int update(int argc, const char **argv) remote_group.list = &list; for (i = 1; i < argc; i++) { + int groups_found = 0; remote_group.name = argv[i]; - result = git_config(get_remote_group, NULL); + result = git_config(get_remote_group, &groups_found); + if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) + die("No such remote group: '%s'", argv[i]); } if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) From 9a23ba3375e2afa8045a433a3debce99c373beb2 Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Mon, 6 Apr 2009 15:41:01 +0200 Subject: [PATCH 23/50] remote: New function remote_is_configured() Previously, there was no easy way to check for the existence of a configured remote. remote_get for example would always create the remote "on demand". This new function returns 1 if the remote is configured, 0 otherwise. Signed-off-by: Finn Arne Gangstad Signed-off-by: Junio C Hamano --- remote.c | 11 +++++++++++ remote.h | 1 + 2 files changed, 12 insertions(+) diff --git a/remote.c b/remote.c index 2b037f11b2..b36fd70978 100644 --- a/remote.c +++ b/remote.c @@ -667,6 +667,17 @@ struct remote *remote_get(const char *name) return ret; } +int remote_is_configured(const char *name) +{ + int i; + read_config(); + + for (i = 0; i < remotes_nr; i++) + if (!strcmp(name, remotes[i]->name)) + return 1; + return 0; +} + int for_each_remote(each_remote_fn fn, void *priv) { int i, result = 0; diff --git a/remote.h b/remote.h index de3d21b662..99706a89bc 100644 --- a/remote.h +++ b/remote.h @@ -45,6 +45,7 @@ struct remote { }; struct remote *remote_get(const char *name); +int remote_is_configured(const char *name); typedef int each_remote_fn(struct remote *remote, void *priv); int for_each_remote(each_remote_fn fn, void *priv); From b344e1614b15dfde0ab4dfc175bed1aac39bc264 Mon Sep 17 00:00:00 2001 From: Finn Arne Gangstad Date: Mon, 6 Apr 2009 15:41:02 +0200 Subject: [PATCH 24/50] git remote update: Fallback to remote if group does not exist Previously, git remote update would fail unless there was a remote group configured with the same name as the remote. git remote update will now fall back to using the remote if no matching group can be found. This enables "git remote update -p ..." to fetch and prune one or more remotes, for example. Signed-off-by: Finn Arne Gangstad Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 2 +- builtin-remote.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 0b6e67dbca..9e2b4eaa38 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -16,7 +16,7 @@ SYNOPSIS 'git remote set-head' [-a | -d | ] 'git remote show' [-n] 'git remote prune' [-n | --dry-run] -'git remote update' [-p | --prune] [group] +'git remote update' [-p | --prune] [group | remote]... DESCRIPTION ----------- diff --git a/builtin-remote.c b/builtin-remote.c index 51df99ba93..ca7c639ad3 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -1232,8 +1232,14 @@ static int update(int argc, const char **argv) int groups_found = 0; remote_group.name = argv[i]; result = git_config(get_remote_group, &groups_found); - if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) - die("No such remote group: '%s'", argv[i]); + if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) { + struct remote *remote; + if (!remote_is_configured(argv[i])) + die("No such remote or remote group: %s", + argv[i]); + remote = remote_get(argv[i]); + string_list_append(remote->name, remote_group.list); + } } if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) From e892dc713e6b0881320d4c65bff7a49d44752bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Tue, 7 Apr 2009 01:24:30 +0200 Subject: [PATCH 25/50] Documentation: Introduce "upstream branch" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 +- Documentation/glossary-content.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index ad22cb875e..77d3a8e31c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1208,7 +1208,7 @@ push.default:: * `matching` push all matching branches. All branches having the same name in both ends are considered to be matching. This is the default. -* `tracking` push the current branch to the branch it is tracking. +* `tracking` push the current branch to its upstream branch. * `current` push the current branch to a branch of the same name. rebase.stat:: diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 4fc1cf1184..572374f7a6 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -449,6 +449,12 @@ This commit is referred to as a "merge commit", or sometimes just a An <> which is not <> from a <>, <>, or any other reference. +[[def_upstream_branch]]upstream branch:: + The default <> that is merged into the branch in + question (or the branch in question is rebased onto). It is configured + via branch..remote and branch..merge. If the upstream branch + of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'". + [[def_working_tree]]working tree:: The tree of actual checked out files. The working tree is normally equal to the <> plus any local changes From 3d4ecc0e23b2b2f555e7d33b5623fd4e67cc2ac7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:05:01 -0400 Subject: [PATCH 26/50] for-each-ref: refactor get_short_ref function This function took a "refinfo" object which is unnecessarily restrictive; it only ever looked at the refname field. This patch refactors it to take just the ref name as a string. While we're touching the relevant lines, let's give it consistent memory semantics. Previously, some code paths would return an allocated string and some would return the original string; now it will always return a malloc'd string. This doesn't actually fix a bug or a leak, because for-each-ref doesn't clean up its memory, but it makes the function a lot less surprising for reuse (which will happen in a later patch). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 5cbb4b081d..4aaf75c779 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -569,7 +569,7 @@ static void gen_scanf_fmt(char *scanf_fmt, const char *rule) /* * Shorten the refname to an non-ambiguous form */ -static char *get_short_ref(struct refinfo *ref) +static char *get_short_ref(const char *ref) { int i; static char **scanf_fmts; @@ -598,17 +598,17 @@ static char *get_short_ref(struct refinfo *ref) /* bail out if there are no rules */ if (!nr_rules) - return ref->refname; + return xstrdup(ref); - /* buffer for scanf result, at most ref->refname must fit */ - short_name = xstrdup(ref->refname); + /* buffer for scanf result, at most ref must fit */ + short_name = xstrdup(ref); /* skip first rule, it will always match */ for (i = nr_rules - 1; i > 0 ; --i) { int j; int short_name_len; - if (1 != sscanf(ref->refname, scanf_fmts[i], short_name)) + if (1 != sscanf(ref, scanf_fmts[i], short_name)) continue; short_name_len = strlen(short_name); @@ -642,7 +642,7 @@ static char *get_short_ref(struct refinfo *ref) } free(short_name); - return ref->refname; + return xstrdup(ref); } @@ -684,7 +684,7 @@ static void populate_value(struct refinfo *ref) if (formatp) { formatp++; if (!strcmp(formatp, "short")) - refname = get_short_ref(ref); + refname = get_short_ref(ref->refname); else die("unknown refname format %s", formatp); From 11c211fa06fc396e8ee8132ef83e2f2763ff6976 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 6 Apr 2009 21:28:36 +0200 Subject: [PATCH 27/50] list-objects: add "void *data" parameter to show functions The goal of this patch is to get rid of the "static struct rev_info revs" static variable in "builtin-rev-list.c". To do that, we need to pass the revs to the "show_commit" function in "builtin-rev-list.c" and this in turn means that the "traverse_commit_list" function in "list-objects.c" must be passed functions pointers to functions with 2 parameters instead of one. So we have to change all the callers and all the functions passed to "traverse_commit_list". Anyway this makes the code more clean and more generic, so it should be a good thing in the long run. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 6 ++-- builtin-rev-list.c | 68 ++++++++++++++++++++++-------------------- list-objects.c | 9 +++--- list-objects.h | 6 ++-- upload-pack.c | 6 ++-- 5 files changed, 49 insertions(+), 46 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 9fc3b35547..82536359d6 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1901,13 +1901,13 @@ static void read_object_list_from_stdin(void) #define OBJECT_ADDED (1u<<20) -static void show_commit(struct commit *commit) +static void show_commit(struct commit *commit, void *data) { add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); commit->object.flags |= OBJECT_ADDED; } -static void show_object(struct object_array_entry *p) +static void show_object(struct object_array_entry *p, void *data) { add_preferred_base_object(p->name); add_object_entry(p->item->sha1, p->item->type, p->name, 0); @@ -2071,7 +2071,7 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(&revs, show_commit, show_object, NULL); if (keep_unreachable) add_objects_in_unpacked_packs(&revs); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index eb341477ca..cd6f6b8fbb 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -42,72 +42,72 @@ static const char rev_list_usage[] = " --bisect-all" ; -static struct rev_info revs; - static int show_timestamp; static int hdr_termination; static const char *header_prefix; -static void finish_commit(struct commit *commit); -static void show_commit(struct commit *commit) +static void finish_commit(struct commit *commit, void *data); +static void show_commit(struct commit *commit, void *data) { - graph_show_commit(revs.graph); + struct rev_info *revs = data; + + graph_show_commit(revs->graph); if (show_timestamp) printf("%lu ", commit->date); if (header_prefix) fputs(header_prefix, stdout); - if (!revs.graph) { + if (!revs->graph) { if (commit->object.flags & BOUNDARY) putchar('-'); else if (commit->object.flags & UNINTERESTING) putchar('^'); - else if (revs.left_right) { + else if (revs->left_right) { if (commit->object.flags & SYMMETRIC_LEFT) putchar('<'); else putchar('>'); } } - if (revs.abbrev_commit && revs.abbrev) - fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), + if (revs->abbrev_commit && revs->abbrev) + fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev), stdout); else fputs(sha1_to_hex(commit->object.sha1), stdout); - if (revs.print_parents) { + if (revs->print_parents) { struct commit_list *parents = commit->parents; while (parents) { printf(" %s", sha1_to_hex(parents->item->object.sha1)); parents = parents->next; } } - if (revs.children.name) { + if (revs->children.name) { struct commit_list *children; - children = lookup_decoration(&revs.children, &commit->object); + children = lookup_decoration(&revs->children, &commit->object); while (children) { printf(" %s", sha1_to_hex(children->item->object.sha1)); children = children->next; } } - show_decorations(&revs, commit); - if (revs.commit_format == CMIT_FMT_ONELINE) + show_decorations(revs, commit); + if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); else putchar('\n'); - if (revs.verbose_header && commit->buffer) { + if (revs->verbose_header && commit->buffer) { struct strbuf buf = STRBUF_INIT; - pretty_print_commit(revs.commit_format, commit, - &buf, revs.abbrev, NULL, NULL, - revs.date_mode, 0); - if (revs.graph) { + pretty_print_commit(revs->commit_format, commit, + &buf, revs->abbrev, NULL, NULL, + revs->date_mode, 0); + if (revs->graph) { if (buf.len) { - if (revs.commit_format != CMIT_FMT_ONELINE) - graph_show_oneline(revs.graph); + if (revs->commit_format != CMIT_FMT_ONELINE) + graph_show_oneline(revs->graph); - graph_show_commit_msg(revs.graph, &buf); + graph_show_commit_msg(revs->graph, &buf); /* * Add a newline after the commit message. @@ -125,7 +125,7 @@ static void show_commit(struct commit *commit) * format doesn't explicitly end in a newline.) */ if (buf.len && buf.buf[buf.len - 1] == '\n') - graph_show_padding(revs.graph); + graph_show_padding(revs->graph); putchar('\n'); } else { /* @@ -133,7 +133,7 @@ static void show_commit(struct commit *commit) * the rest of the graph output for this * commit. */ - if (graph_show_remainder(revs.graph)) + if (graph_show_remainder(revs->graph)) putchar('\n'); } } else { @@ -142,14 +142,14 @@ static void show_commit(struct commit *commit) } strbuf_release(&buf); } else { - if (graph_show_remainder(revs.graph)) + if (graph_show_remainder(revs->graph)) putchar('\n'); } maybe_flush_or_die(stdout, "stdout"); - finish_commit(commit); + finish_commit(commit, data); } -static void finish_commit(struct commit *commit) +static void finish_commit(struct commit *commit, void *data) { if (commit->parents) { free_commit_list(commit->parents); @@ -159,20 +159,20 @@ static void finish_commit(struct commit *commit) commit->buffer = NULL; } -static void finish_object(struct object_array_entry *p) +static void finish_object(struct object_array_entry *p, void *data) { if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1)) die("missing blob object '%s'", sha1_to_hex(p->item->sha1)); } -static void show_object(struct object_array_entry *p) +static void show_object(struct object_array_entry *p, void *data) { /* An object with name "foo\n0000000..." can be used to * confuse downstream "git pack-objects" very badly. */ const char *ep = strchr(p->name, '\n'); - finish_object(p); + finish_object(p, data); if (ep) { printf("%s %.*s\n", sha1_to_hex(p->item->sha1), (int) (ep - p->name), @@ -264,7 +264,7 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); if (flags & BISECT_SHOW_ALL) { - traverse_commit_list(revs, show_commit, show_object); + traverse_commit_list(revs, show_commit, show_object, revs); printf("------\n"); } @@ -297,6 +297,7 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) int cmd_rev_list(int argc, const char **argv, const char *prefix) { + struct rev_info revs; struct commit_list *list; int i; int read_from_stdin = 0; @@ -391,8 +392,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } traverse_commit_list(&revs, - quiet ? finish_commit : show_commit, - quiet ? finish_object : show_object); + quiet ? finish_commit : show_commit, + quiet ? finish_object : show_object, + &revs); return 0; } diff --git a/list-objects.c b/list-objects.c index c8b8375e49..208a4cb002 100644 --- a/list-objects.c +++ b/list-objects.c @@ -137,8 +137,9 @@ void mark_edges_uninteresting(struct commit_list *list, } void traverse_commit_list(struct rev_info *revs, - void (*show_commit)(struct commit *), - void (*show_object)(struct object_array_entry *)) + show_commit_fn show_commit, + show_object_fn show_object, + void *data) { int i; struct commit *commit; @@ -146,7 +147,7 @@ void traverse_commit_list(struct rev_info *revs, while ((commit = get_revision(revs)) != NULL) { process_tree(revs, commit->tree, &objects, NULL, ""); - show_commit(commit); + show_commit(commit, data); } for (i = 0; i < revs->pending.nr; i++) { struct object_array_entry *pending = revs->pending.objects + i; @@ -173,7 +174,7 @@ void traverse_commit_list(struct rev_info *revs, sha1_to_hex(obj->sha1), name); } for (i = 0; i < objects.nr; i++) - show_object(&objects.objects[i]); + show_object(&objects.objects[i], data); free(objects.objects); if (revs->pending.nr) { free(revs->pending.objects); diff --git a/list-objects.h b/list-objects.h index 0f41391ecc..47fae2e468 100644 --- a/list-objects.h +++ b/list-objects.h @@ -1,11 +1,11 @@ #ifndef LIST_OBJECTS_H #define LIST_OBJECTS_H -typedef void (*show_commit_fn)(struct commit *); -typedef void (*show_object_fn)(struct object_array_entry *); +typedef void (*show_commit_fn)(struct commit *, void *); +typedef void (*show_object_fn)(struct object_array_entry *, void *); typedef void (*show_edge_fn)(struct commit *); -void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn); +void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *); void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); diff --git a/upload-pack.c b/upload-pack.c index a49d872447..495c99f80a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -66,7 +66,7 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) } static FILE *pack_pipe = NULL; -static void show_commit(struct commit *commit) +static void show_commit(struct commit *commit, void *data) { if (commit->object.flags & BOUNDARY) fputc('-', pack_pipe); @@ -78,7 +78,7 @@ static void show_commit(struct commit *commit) commit->buffer = NULL; } -static void show_object(struct object_array_entry *p) +static void show_object(struct object_array_entry *p, void *data) { /* An object with name "foo\n0000000..." can be used to * confuse downstream git-pack-objects very badly. @@ -134,7 +134,7 @@ static int do_rev_list(int fd, void *create_full_pack) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(&revs, show_commit, show_object, NULL); fflush(pack_pipe); fclose(pack_pipe); return 0; From d797257eb280b67dd1f7153a66b03453c0fb927a Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 6 Apr 2009 22:28:00 +0200 Subject: [PATCH 28/50] rev-list: remove last static vars used in "show_commit" This patch removes the last static variables that were used in the "show_commit" function. To do that, we create a new "rev_list_info" struct that we will pass in the "void *data" argument to "show_commit". This means that we have to change the first argument to "show_bisect_vars" too. While at it, we also remove a "struct commit_list *list" variable in "cmd_rev_list" that is not really needed. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 6 +++++- bisect.h | 14 ++++++++------ builtin-rev-list.c | 42 +++++++++++++++++++++--------------------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/bisect.c b/bisect.c index 69f8860ca1..4d2a150df2 100644 --- a/bisect.c +++ b/bisect.c @@ -535,8 +535,12 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix) 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; + bisect_rev_setup(&revs, prefix); if (prepare_revision_walk(&revs)) @@ -547,6 +551,6 @@ int bisect_next_vars(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_sha1_nr); - return show_bisect_vars(&revs, reaches, all, + return show_bisect_vars(&info, reaches, all, BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED); } diff --git a/bisect.h b/bisect.h index f5d106735c..b1c334d349 100644 --- a/bisect.h +++ b/bisect.h @@ -14,12 +14,14 @@ extern struct commit_list *filter_skipped(struct commit_list *list, #define BISECT_SHOW_TRIED (1<<1) #define BISECT_SHOW_STRINGED (1<<2) -/* - * The flag BISECT_SHOW_ALL should not be set if this function is called - * from outside "builtin-rev-list.c" as otherwise it would use - * static "revs" from this file. - */ -extern int show_bisect_vars(struct rev_info *revs, int reaches, int all, +struct rev_list_info { + struct rev_info *revs; + int show_timestamp; + int hdr_termination; + const char *header_prefix; +}; + +extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all, int flags); extern int bisect_next_vars(const char *prefix); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index cd6f6b8fbb..244b73eaeb 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -42,21 +42,18 @@ static const char rev_list_usage[] = " --bisect-all" ; -static int show_timestamp; -static int hdr_termination; -static const char *header_prefix; - static void finish_commit(struct commit *commit, void *data); static void show_commit(struct commit *commit, void *data) { - struct rev_info *revs = data; + struct rev_list_info *info = data; + struct rev_info *revs = info->revs; graph_show_commit(revs->graph); - if (show_timestamp) + if (info->show_timestamp) printf("%lu ", commit->date); - if (header_prefix) - fputs(header_prefix, stdout); + if (info->header_prefix) + fputs(info->header_prefix, stdout); if (!revs->graph) { if (commit->object.flags & BOUNDARY) @@ -138,7 +135,7 @@ static void show_commit(struct commit *commit, void *data) } } else { if (buf.len) - printf("%s%c", buf.buf, hdr_termination); + printf("%s%c", buf.buf, info->hdr_termination); } strbuf_release(&buf); } else { @@ -236,11 +233,13 @@ static void show_tried_revs(struct commit_list *tried, int stringed) printf(stringed ? "' &&\n" : "'\n"); } -int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) +int show_bisect_vars(struct rev_list_info *info, int reaches, int all, + int flags) { int cnt; char hex[41] = "", *format; struct commit_list *tried; + struct rev_info *revs = info->revs; if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) return 1; @@ -264,7 +263,7 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); if (flags & BISECT_SHOW_ALL) { - traverse_commit_list(revs, show_commit, show_object, revs); + traverse_commit_list(revs, show_commit, show_object, info); printf("------\n"); } @@ -298,7 +297,7 @@ int show_bisect_vars(struct rev_info *revs, int reaches, int all, int flags) int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct commit_list *list; + struct rev_list_info info; int i; int read_from_stdin = 0; int bisect_list = 0; @@ -313,6 +312,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commit_format = CMIT_FMT_UNSPECIFIED; argc = setup_revisions(argc, argv, &revs, NULL); + memset(&info, 0, sizeof(info)); + info.revs = &revs; + quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; @@ -322,7 +324,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--timestamp")) { - show_timestamp = 1; + info.show_timestamp = 1; continue; } if (!strcmp(arg, "--bisect")) { @@ -352,19 +354,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { /* The command line has a --pretty */ - hdr_termination = '\n'; + info.hdr_termination = '\n'; if (revs.commit_format == CMIT_FMT_ONELINE) - header_prefix = ""; + info.header_prefix = ""; else - header_prefix = "commit "; + info.header_prefix = "commit "; } else if (revs.verbose_header) /* Only --header was specified */ revs.commit_format = CMIT_FMT_RAW; - list = revs.commits; - - if ((!list && + if ((!revs.commits && (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending.nr)) || revs.diff) @@ -387,14 +387,14 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_find_all); if (bisect_show_vars) - return show_bisect_vars(&revs, reaches, all, + return show_bisect_vars(&info, reaches, all, bisect_show_all ? BISECT_SHOW_ALL : 0); } traverse_commit_list(&revs, quiet ? finish_commit : show_commit, quiet ? finish_object : show_object, - &revs); + &info); return 0; } From 13858e5770dd218e5318819d3273c916b46cf8e5 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 7 Apr 2009 05:08:42 +0200 Subject: [PATCH 29/50] rev-list: add "int bisect_show_flags" in "struct rev_list_info" This is a cleanup patch to make it easier to use the "show_bisect_vars" function and take advantage of the rev_list_info struct. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- bisect.c | 4 ++-- bisect.h | 6 +++--- builtin-rev-list.c | 11 ++++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bisect.c b/bisect.c index 4d2a150df2..58f7e6f773 100644 --- a/bisect.c +++ b/bisect.c @@ -540,6 +540,7 @@ int bisect_next_vars(const char *prefix) memset(&info, 0, sizeof(info)); info.revs = &revs; + info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED; bisect_rev_setup(&revs, prefix); @@ -551,6 +552,5 @@ int bisect_next_vars(const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, !!skipped_sha1_nr); - return show_bisect_vars(&info, reaches, all, - BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED); + return show_bisect_vars(&info, reaches, all); } diff --git a/bisect.h b/bisect.h index b1c334d349..fdba913877 100644 --- a/bisect.h +++ b/bisect.h @@ -9,20 +9,20 @@ extern struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, int show_all); -/* show_bisect_vars flags */ +/* bisect_show_flags flags in struct rev_list_info */ #define BISECT_SHOW_ALL (1<<0) #define BISECT_SHOW_TRIED (1<<1) #define BISECT_SHOW_STRINGED (1<<2) struct rev_list_info { struct rev_info *revs; + int bisect_show_flags; int show_timestamp; int hdr_termination; const char *header_prefix; }; -extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all, - int flags); +extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); extern int bisect_next_vars(const char *prefix); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 244b73eaeb..193993cf44 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -233,10 +233,9 @@ static void show_tried_revs(struct commit_list *tried, int stringed) printf(stringed ? "' &&\n" : "'\n"); } -int show_bisect_vars(struct rev_list_info *info, int reaches, int all, - int flags) +int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { - int cnt; + int cnt, flags = info->bisect_show_flags; char hex[41] = "", *format; struct commit_list *tried; struct rev_info *revs = info->revs; @@ -303,7 +302,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; - int bisect_show_all = 0; int quiet = 0; git_config(git_default_config, NULL); @@ -334,7 +332,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--bisect-all")) { bisect_list = 1; bisect_find_all = 1; - bisect_show_all = 1; + info.bisect_show_flags = BISECT_SHOW_ALL; revs.show_decorations = 1; continue; } @@ -387,8 +385,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_find_all); if (bisect_show_vars) - return show_bisect_vars(&info, reaches, all, - bisect_show_all ? BISECT_SHOW_ALL : 0); + return show_bisect_vars(&info, reaches, all); } traverse_commit_list(&revs, From 8db9a4b85d6b0d7424c8a19b77a5baa8529ab64c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:06:51 -0400 Subject: [PATCH 30/50] for-each-ref: refactor refname handling This code handles some special magic like *-deref and the :short formatting specifier. The next patch will add another field which outputs a ref and wants to use the same code. This patch splits the "which ref are we outputting" from the actual formatting. There should be no behavioral change. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 45 +++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 4aaf75c779..653dca9a57 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -672,32 +672,37 @@ static void populate_value(struct refinfo *ref) const char *name = used_atom[i]; struct atom_value *v = &ref->value[i]; int deref = 0; + const char *refname; + const char *formatp; + if (*name == '*') { deref = 1; name++; } - if (!prefixcmp(name, "refname")) { - const char *formatp = strchr(name, ':'); - const char *refname = ref->refname; - /* look for "short" refname format */ - if (formatp) { - formatp++; - if (!strcmp(formatp, "short")) - refname = get_short_ref(ref->refname); - else - die("unknown refname format %s", - formatp); - } + if (!prefixcmp(name, "refname")) + refname = ref->refname; + else + continue; - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } + formatp = strchr(name, ':'); + /* look for "short" refname format */ + if (formatp) { + formatp++; + if (!strcmp(formatp, "short")) + refname = get_short_ref(refname); + else + die("unknown %.*s format %s", + (int)(formatp - name), name, formatp); + } + + if (!deref) + v->s = refname; + else { + int len = strlen(refname); + char *s = xmalloc(len + 4); + sprintf(s, "%s^{}", refname); + v->s = s; } } From 8cae19d987b1bbd43258558f591e39d9d216dcb3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:09:39 -0400 Subject: [PATCH 31/50] for-each-ref: add "upstream" format field The logic for determining the upstream ref of a branch is somewhat complex to perform in a shell script. This patch provides a plumbing mechanism for scripts to access the C logic used internally by git-status, git-branch, etc. For example: $ git for-each-ref \ --format='%(refname:short) %(upstream:short)' \ refs/heads/ master origin/master Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 5 +++++ builtin-for-each-ref.c | 14 ++++++++++++++ t/t6300-for-each-ref.sh | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 5061d3e4e7..b362e9ed12 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -85,6 +85,11 @@ objectsize:: objectname:: The object name (aka SHA-1). +upstream:: + The name of a local ref which can be considered ``upstream'' + from the displayed ref. Respects `:short` in the same way as + `refname` above. + In addition to the above, for commit and tag objects, the header field names (`tree`, `parent`, `object`, `type`, and `tag`) can be used to specify the value in the header field. diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 653dca9a57..8796352eb6 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -8,6 +8,7 @@ #include "blob.h" #include "quote.h" #include "parse-options.h" +#include "remote.h" /* Quoting styles */ #define QUOTE_NONE 0 @@ -66,6 +67,7 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "upstream" }, }; /* @@ -682,6 +684,18 @@ static void populate_value(struct refinfo *ref) if (!prefixcmp(name, "refname")) refname = ref->refname; + else if(!prefixcmp(name, "upstream")) { + struct branch *branch; + /* only local branches may have an upstream */ + if (prefixcmp(ref->refname, "refs/heads/")) + continue; + branch = branch_get(ref->refname + 11); + + if (!branch || !branch->merge || !branch->merge[0] || + !branch->merge[0]->dst) + continue; + refname = branch->merge[0]->dst; + } else continue; diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 8bfae44a83..daf02d5c10 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -26,6 +26,13 @@ test_expect_success 'Create sample commit with known timestamp' ' git tag -a -m "Tagging at $datestamp" testtag ' +test_expect_success 'Create upstream config' ' + git update-ref refs/remotes/origin/master master && + git remote add origin nowhere && + git config branch.master.remote origin && + git config branch.master.merge refs/heads/master +' + test_atom() { case "$1" in head) ref=refs/heads/master ;; @@ -39,6 +46,7 @@ test_atom() { } test_atom head refname refs/heads/master +test_atom head upstream refs/remotes/origin/master test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581 @@ -68,6 +76,7 @@ test_atom head contents 'Initial ' test_atom tag refname refs/tags/testtag +test_atom tag upstream '' test_atom tag objecttype tag test_atom tag objectsize 154 test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322 @@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' ' cat >expected <<\EOF refs/heads/master +refs/remotes/origin/master refs/tags/testtag EOF @@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' ' cat >expected <<\EOF refs/tags/testtag +refs/remotes/origin/master refs/heads/master EOF @@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' ' cat >expected <<\EOF 'refs/heads/master' +'refs/remotes/origin/master' 'refs/tags/testtag' EOF @@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' ' cat >expected <<\EOF "refs/heads/master" +"refs/remotes/origin/master" "refs/tags/testtag" EOF @@ -273,6 +286,15 @@ test_expect_success 'Check short refname format' ' test_cmp expected actual ' +cat >expected <actual && + test_cmp expected actual +' + test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' From 7c2b3029df45a74d0ebd11afcc94259791cfb90d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:14:20 -0400 Subject: [PATCH 32/50] make get_short_ref a public function Often we want to shorten a full ref name to something "prettier" to show a user. For example, "refs/heads/master" is often shown simply as "master", or "refs/remotes/origin/master" is shown as "origin/master". Many places in the code use a very simple formula: skip common prefixes like refs/heads, refs/remotes, etc. This is codified in the prettify_ref function. for-each-ref has a more correct (but more expensive) approach: consider the ref lookup rules, and try shortening as much as possible while remaining unambiguous. This patch makes the latter strategy globally available as shorten_unambiguous_ref. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 105 +---------------------------------------- refs.c | 99 ++++++++++++++++++++++++++++++++++++++ refs.h | 1 + 3 files changed, 101 insertions(+), 104 deletions(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 8796352eb6..c8114c8afd 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -545,109 +545,6 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v } } -/* - * generate a format suitable for scanf from a ref_rev_parse_rules - * rule, that is replace the "%.*s" spec with a "%s" spec - */ -static void gen_scanf_fmt(char *scanf_fmt, const char *rule) -{ - char *spec; - - spec = strstr(rule, "%.*s"); - if (!spec || strstr(spec + 4, "%.*s")) - die("invalid rule in ref_rev_parse_rules: %s", rule); - - /* copy all until spec */ - strncpy(scanf_fmt, rule, spec - rule); - scanf_fmt[spec - rule] = '\0'; - /* copy new spec */ - strcat(scanf_fmt, "%s"); - /* copy remaining rule */ - strcat(scanf_fmt, spec + 4); - - return; -} - -/* - * Shorten the refname to an non-ambiguous form - */ -static char *get_short_ref(const char *ref) -{ - int i; - static char **scanf_fmts; - static int nr_rules; - char *short_name; - - /* pre generate scanf formats from ref_rev_parse_rules[] */ - if (!nr_rules) { - size_t total_len = 0; - - /* the rule list is NULL terminated, count them first */ - for (; ref_rev_parse_rules[nr_rules]; nr_rules++) - /* no +1 because strlen("%s") < strlen("%.*s") */ - total_len += strlen(ref_rev_parse_rules[nr_rules]); - - scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len); - - total_len = 0; - for (i = 0; i < nr_rules; i++) { - scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] - + total_len; - gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]); - total_len += strlen(ref_rev_parse_rules[i]); - } - } - - /* bail out if there are no rules */ - if (!nr_rules) - return xstrdup(ref); - - /* buffer for scanf result, at most ref must fit */ - short_name = xstrdup(ref); - - /* skip first rule, it will always match */ - for (i = nr_rules - 1; i > 0 ; --i) { - int j; - int short_name_len; - - if (1 != sscanf(ref, scanf_fmts[i], short_name)) - continue; - - short_name_len = strlen(short_name); - - /* - * check if the short name resolves to a valid ref, - * but use only rules prior to the matched one - */ - for (j = 0; j < i; j++) { - const char *rule = ref_rev_parse_rules[j]; - unsigned char short_objectname[20]; - char refname[PATH_MAX]; - - /* - * the short name is ambiguous, if it resolves - * (with this previous rule) to a valid ref - * read_ref() returns 0 on success - */ - mksnpath(refname, sizeof(refname), - rule, short_name_len, short_name); - if (!read_ref(refname, short_objectname)) - break; - } - - /* - * short name is non-ambiguous if all previous rules - * haven't resolved to a valid ref - */ - if (j == i) - return short_name; - } - - free(short_name); - return xstrdup(ref); -} - - /* * Parse the object referred by ref, and grab needed value. */ @@ -704,7 +601,7 @@ static void populate_value(struct refinfo *ref) if (formatp) { formatp++; if (!strcmp(formatp, "short")) - refname = get_short_ref(refname); + refname = shorten_unambiguous_ref(refname); else die("unknown %.*s format %s", (int)(formatp - name), name, formatp); diff --git a/refs.c b/refs.c index 59c373fc6d..1e5e7b4ad9 100644 --- a/refs.c +++ b/refs.c @@ -1652,3 +1652,102 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name) return (struct ref *)list; return NULL; } + +/* + * generate a format suitable for scanf from a ref_rev_parse_rules + * rule, that is replace the "%.*s" spec with a "%s" spec + */ +static void gen_scanf_fmt(char *scanf_fmt, const char *rule) +{ + char *spec; + + spec = strstr(rule, "%.*s"); + if (!spec || strstr(spec + 4, "%.*s")) + die("invalid rule in ref_rev_parse_rules: %s", rule); + + /* copy all until spec */ + strncpy(scanf_fmt, rule, spec - rule); + scanf_fmt[spec - rule] = '\0'; + /* copy new spec */ + strcat(scanf_fmt, "%s"); + /* copy remaining rule */ + strcat(scanf_fmt, spec + 4); + + return; +} + +char *shorten_unambiguous_ref(const char *ref) +{ + int i; + static char **scanf_fmts; + static int nr_rules; + char *short_name; + + /* pre generate scanf formats from ref_rev_parse_rules[] */ + if (!nr_rules) { + size_t total_len = 0; + + /* the rule list is NULL terminated, count them first */ + for (; ref_rev_parse_rules[nr_rules]; nr_rules++) + /* no +1 because strlen("%s") < strlen("%.*s") */ + total_len += strlen(ref_rev_parse_rules[nr_rules]); + + scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len); + + total_len = 0; + for (i = 0; i < nr_rules; i++) { + scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + + total_len; + gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]); + total_len += strlen(ref_rev_parse_rules[i]); + } + } + + /* bail out if there are no rules */ + if (!nr_rules) + return xstrdup(ref); + + /* buffer for scanf result, at most ref must fit */ + short_name = xstrdup(ref); + + /* skip first rule, it will always match */ + for (i = nr_rules - 1; i > 0 ; --i) { + int j; + int short_name_len; + + if (1 != sscanf(ref, scanf_fmts[i], short_name)) + continue; + + short_name_len = strlen(short_name); + + /* + * check if the short name resolves to a valid ref, + * but use only rules prior to the matched one + */ + for (j = 0; j < i; j++) { + const char *rule = ref_rev_parse_rules[j]; + unsigned char short_objectname[20]; + char refname[PATH_MAX]; + + /* + * the short name is ambiguous, if it resolves + * (with this previous rule) to a valid ref + * read_ref() returns 0 on success + */ + mksnpath(refname, sizeof(refname), + rule, short_name_len, short_name); + if (!read_ref(refname, short_objectname)) + break; + } + + /* + * short name is non-ambiguous if all previous rules + * haven't resolved to a valid ref + */ + if (j == i) + return short_name; + } + + free(short_name); + return xstrdup(ref); +} diff --git a/refs.h b/refs.h index 68c2d16d53..2d0f961c7c 100644 --- a/refs.h +++ b/refs.h @@ -80,6 +80,7 @@ extern int for_each_reflog(each_ref_fn, void *); extern int check_ref_format(const char *target); extern const char *prettify_ref(const struct ref *ref); +extern char *shorten_unambiguous_ref(const char *ref); /** rename ref, return 0 on success **/ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); From 2d8a7f0b30b4f9ef750ab763aabec117ffe4e749 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:16:56 -0400 Subject: [PATCH 33/50] branch: show upstream branch when double verbose This information is easily accessible when we are calculating the relationship. The only reason not to print it all the time is that it consumes a fair bit of screen space, and may not be of interest to the user. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 4 +++- builtin-branch.c | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 31ba7f2ade..ba3dea6840 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -100,7 +100,9 @@ OPTIONS -v:: --verbose:: - Show sha1 and commit subject line for each head. + Show sha1 and commit subject line for each head, along with + relationship to upstream branch (if any). If given twice, print + the name of the upstream branch, as well. --abbrev=:: Alter the sha1's minimum display length in the output listing. diff --git a/builtin-branch.c b/builtin-branch.c index ca81d725cb..3275821696 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -301,19 +301,30 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } -static void fill_tracking_info(struct strbuf *stat, const char *branch_name) +static void fill_tracking_info(struct strbuf *stat, const char *branch_name, + int show_upstream_ref) { int ours, theirs; struct branch *branch = branch_get(branch_name); - if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) + if (!stat_tracking_info(branch, &ours, &theirs)) { + if (branch && branch->merge && branch->merge[0]->dst && + show_upstream_ref) + strbuf_addf(stat, "[%s] ", + shorten_unambiguous_ref(branch->merge[0]->dst)); return; + } + + strbuf_addch(stat, '['); + if (show_upstream_ref) + strbuf_addf(stat, "%s: ", + shorten_unambiguous_ref(branch->merge[0]->dst)); if (!ours) - strbuf_addf(stat, "[behind %d] ", theirs); + strbuf_addf(stat, "behind %d] ", theirs); else if (!theirs) - strbuf_addf(stat, "[ahead %d] ", ours); + strbuf_addf(stat, "ahead %d] ", ours); else - strbuf_addf(stat, "[ahead %d, behind %d] ", ours, theirs); + strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); } static int matches_merge_filter(struct commit *commit) @@ -379,7 +390,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name); + fill_tracking_info(&stat, item->name, verbose > 1); strbuf_addf(&out, " %s %s%s", find_unique_abbrev(item->commit->object.sha1, abbrev), From 499c29394ce1ead3ebd29b0d3c8014cdb7a32e63 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 3 Apr 2009 15:32:20 -0400 Subject: [PATCH 34/50] Makefile: allow building without perl For systems with a missing or broken perl, it is nicer to explicitly say "we don't want perl" because: 1. The Makefile knows not to bother with Perl-ish things like Git.pm. 2. We can print a more user-friendly error message than "foo is not a git command" or whatever the broken perl might barf 3. Test scripts that require perl can mark themselves and such and be skipped This patch implements parts (1) and (2). The perl/ subdirectory is skipped entirely, gitweb is not built, and any git commands which rely on perl will print a human-readable message and exit with an error code. This patch is based on one from Robin H. Johnson. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 27 +++++++++++++++++++++++++-- unimplemented.sh | 4 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 unimplemented.sh diff --git a/Makefile b/Makefile index bcf7cbb3fb..34b05397de 100644 --- a/Makefile +++ b/Makefile @@ -145,6 +145,8 @@ all:: # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # +# Define NO_PERL if you do not want Perl scripts or libraries at all. +# # Define NO_TCLTK if you do not want Tcl/Tk GUI. # # The TCL_PATH variable governs the location of the Tcl interpreter @@ -353,7 +355,10 @@ BUILT_INS += git-whatchanged$X ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir -OTHER_PROGRAMS = git$X gitweb/gitweb.cgi +OTHER_PROGRAMS = git$X +ifndef NO_PERL +OTHER_PROGRAMS += gitweb/gitweb.cgi +endif # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH @@ -1104,6 +1109,10 @@ ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif +ifeq ($(PERL_PATH),) +NO_PERL=NoThanks +endif + QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = @@ -1178,7 +1187,9 @@ ifndef NO_TCLTK $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) gitexecdir='$(gitexec_instdir_SQ)' all $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all endif +ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all +endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) please_set_SHELL_PATH_to_a_more_modern_shell: @@ -1226,6 +1237,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh chmod +x $@+ && \ mv $@+ $@ +ifndef NO_PERL $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL @@ -1285,6 +1297,15 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css $@.sh > $@+ && \ chmod +x $@+ && \ mv $@+ $@ +else # NO_PERL +$(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \ + unimplemented.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +endif # NO_PERL configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ @@ -1603,9 +1624,11 @@ clean: $(RM) -r $(GIT_TARNAME) .doc-tmp-dir $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(RM) $(htmldocs).tar.gz $(manpages).tar.gz - $(RM) gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean +ifndef NO_PERL + $(RM) gitweb/gitweb.cgi $(MAKE) -C perl clean +endif $(MAKE) -C templates/ clean $(MAKE) -C t/ clean ifndef NO_TCLTK diff --git a/unimplemented.sh b/unimplemented.sh new file mode 100644 index 0000000000..5252de4b25 --- /dev/null +++ b/unimplemented.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo >&2 "fatal: git was built without support for `basename $0` (@@REASON@@)." +exit 128 From 1b19ccd236e3369ac77d74ded207406ffbf9feca Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 3 Apr 2009 15:33:59 -0400 Subject: [PATCH 35/50] tests: skip perl tests if NO_PERL is defined These scripts all test git programs that are written in perl, and thus obviously won't work if NO_PERL is defined. We pass NO_PERL to the scripts from the building Makefile via the GIT-BUILD-OPTIONS file. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Makefile | 1 + t/lib-git-svn.sh | 4 ++++ t/t3701-add-interactive.sh | 5 +++++ t/t7501-commit.sh | 4 ++-- t/t9001-send-email.sh | 5 +++++ t/t9200-git-cvsexportcommit.sh | 5 +++++ t/t9400-git-cvsserver-server.sh | 4 ++++ t/t9401-git-cvsserver-crlf.sh | 5 +++++ t/t9500-gitweb-standalone-no-errors.sh | 5 +++++ t/t9600-cvsimport.sh | 5 +++++ t/t9700-perl-git.sh | 5 +++++ t/test-lib.sh | 2 ++ 12 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 34b05397de..a865a46053 100644 --- a/Makefile +++ b/Makefile @@ -1422,6 +1422,7 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ + @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index cdd7ccdd2a..773d47cf3c 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -8,6 +8,10 @@ then say 'skipping git svn tests, NO_SVN_TESTS defined' test_done fi +if ! test_have_prereq PERL; then + say 'skipping git svn tests, perl not available' + test_done +fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/git-svn diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fe017839c4..dfc65601aa 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -3,6 +3,11 @@ test_description='add -i basic tests' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git add -i tests, perl not available' + test_done +fi + test_expect_success 'setup (initial)' ' echo content >file && git add file && diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index b4e2b4db84..e2ef53228e 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -38,7 +38,7 @@ test_expect_success \ "echo King of the bongo >file && test_must_fail git commit -m foo -a file" -test_expect_success \ +test_expect_success PERL \ "using paths with --interactive" \ "echo bong-o-bong >file && ! (echo 7 | git commit -m foo --interactive file)" @@ -119,7 +119,7 @@ test_expect_success \ "echo 'gak' >file && \ git commit -m 'author' --author 'Rubber Duck ' -a" -test_expect_success \ +test_expect_success PERL \ "interactive add" \ "echo 7 | git commit --interactive | grep 'What now'" diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 3c90c4fc1c..d9420e0a3b 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -3,6 +3,11 @@ test_description='git send-email' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git send-email tests, perl not available' + test_done +fi + PROG='git send-email' test_expect_success \ 'prepare reference tree' \ diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 36656923ac..56b7c06921 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -6,6 +6,11 @@ test_description='Test export of commits to CVS' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsexportcommit tests, perl not available' + test_done +fi + cvs >/dev/null 2>&1 if test $? -ne 1 then diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 39185db6c9..64f947d75b 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -10,6 +10,10 @@ cvs CLI client via git-cvsserver server' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsserver tests, perl not available' + test_done +fi cvs >/dev/null 2>&1 if test $? -ne 1 then diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 12e0e50822..aca40c1b1f 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -52,6 +52,11 @@ then say 'skipping git-cvsserver tests, cvs not found' test_done fi +if ! test_have_prereq PERL +then + say 'skipping git-cvsserver tests, perl not available' + test_done +fi perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { say 'skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 0bd332c493..f4210fbb04 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -65,6 +65,11 @@ gitweb_run () { . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping gitweb tests, perl not available' + test_done +fi + perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { say 'skipping gitweb tests, perl version is too old' test_done diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 33eb51938d..4322a0c1ed 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -3,6 +3,11 @@ test_description='git cvsimport basic tests' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsimport tests, perl not available' + test_done +fi + CVSROOT=$(pwd)/cvsroot export CVSROOT unset CVS_SERVER diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index 4a501c6847..b4ca244626 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -6,6 +6,11 @@ test_description='perl interface (Git.pm)' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping perl interface tests, perl not available' + test_done +fi + perl -MTest::More -e 0 2>/dev/null || { say "Perl Test::More unavailable, skipping test" test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index b050196cb7..4bd986f430 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -698,6 +698,8 @@ case $(uname -s) in ;; esac +test -z "$NO_PERL" && test_set_prereq PERL + # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y From 27845e9548b7b5b316d89f64546466f2004ee414 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 6 Apr 2009 16:18:23 -0400 Subject: [PATCH 36/50] add tests for remote groups This tries to systematically cover existing behavior, and also mark some expect_failure cases for desired behavior. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t5506-remote-groups.sh | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 t/t5506-remote-groups.sh diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh new file mode 100755 index 0000000000..2a1806b0b4 --- /dev/null +++ b/t/t5506-remote-groups.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git remote group handling' +. ./test-lib.sh + +mark() { + echo "$1" >mark +} + +update_repo() { + (cd $1 && + echo content >>file && + git add file && + git commit -F ../mark) +} + +update_repos() { + update_repo one $1 && + update_repo two $1 +} + +repo_fetched() { + if test "`git log -1 --pretty=format:%s $1 --`" = "`cat mark`"; then + echo >&2 "repo was fetched: $1" + return 0 + fi + echo >&2 "repo was not fetched: $1" + return 1 +} + +test_expect_success 'setup' ' + mkdir one && (cd one && git init) && + mkdir two && (cd two && git init) && + git remote add -m master one one && + git remote add -m master two two +' + +test_expect_success 'no group updates all' ' + mark update-all && + update_repos && + git remote update && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'nonexistant group produces error' ' + mark nonexistant && + update_repos && + test_must_fail git remote update nonexistant && + ! repo_fetched one && + ! repo_fetched two +' + +test_expect_success 'updating group updates all members' ' + mark group-all && + update_repos && + git config --add remotes.all one && + git config --add remotes.all two && + git remote update all && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'updating group does not update non-members' ' + mark group-some && + update_repos && + git config --add remotes.some one && + git remote update some && + repo_fetched one && + ! repo_fetched two +' + +test_expect_success 'updating remote name updates that remote' ' + mark remote-name && + update_repos && + git remote update one && + repo_fetched one && + ! repo_fetched two +' + +test_done From c2abd83fea0c332e907f5bc23bea4eeca091a86b Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Mon, 6 Apr 2009 16:37:59 -0400 Subject: [PATCH 37/50] git-svn: add fetch --parent option Signed-off-by: Jason Merrill Acked-By: Eric Wong --- Documentation/git-svn.txt | 3 +++ git-svn.perl | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b7b1af813d..85b2c8da5d 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -97,6 +97,9 @@ COMMANDS makes 'git-log' (even without --date=local) show the same times that `svn log` would in the local timezone. +--parent;; + Fetch only from the SVN parent of the current HEAD. + This doesn't interfere with interoperating with the Subversion repository you cloned from, but if you wish for your local Git repository to be able to interoperate with someone else's local Git diff --git a/git-svn.perl b/git-svn.perl index d9197989d2..cb718b8519 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -63,7 +63,7 @@ $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, - $_version, $_fetch_all, $_no_rebase, + $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_url, $_verbose, $_git_format, $_commit_url, $_tag); @@ -112,6 +112,7 @@ my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, 'fetch-all|all' => \$_fetch_all, + 'parent|p' => \$_fetch_parent, %fc_opts } ], clone => [ \&cmd_clone, "Initialize and fetch revisions", { 'revision|r=s' => \$_revision, @@ -381,12 +382,21 @@ sub cmd_fetch { } my ($remote) = @_; if (@_ > 1) { - die "Usage: $0 fetch [--all] [svn-remote]\n"; + die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n"; } - $remote ||= $Git::SVN::default_repo_id; - if ($_fetch_all) { + if ($_fetch_parent) { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + unless ($gs) { + die "Unable to determine upstream SVN information from ", + "working tree history\n"; + } + # just fetch, don't checkout. + $_no_checkout = 'true'; + $_fetch_all ? $gs->fetch_all : $gs->fetch; + } elsif ($_fetch_all) { cmd_multi_fetch(); } else { + $remote ||= $Git::SVN::default_repo_id; Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes()); } } From 6ea420328885603087b3f1df42683c911d1b3f29 Mon Sep 17 00:00:00 2001 From: Boris Byk Date: Sat, 11 Apr 2009 00:32:41 +0400 Subject: [PATCH 38/50] git-svn: speed up blame command 'git svn blame' now uses the 'git cat-file --batch' command to speed up resolving SVN revision number out of commit SHA by removing fork+exec overhead. [ew: enforced 80-column line wrap] Signed-off-by: Boris Byk Acked-by: Eric Wong --- git-svn.perl | 54 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index cb718b8519..50a398b3e7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -47,7 +47,8 @@ BEGIN { # import functions from Git into our packages, en masse no strict 'refs'; foreach (qw/command command_oneline command_noisy command_output_pipe - command_input_pipe command_close_pipe/) { + command_input_pipe command_close_pipe + command_bidi_pipe command_close_bidi_pipe/) { for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher Git::SVN::Migration Git::SVN::Log Git::SVN), __PACKAGE__) { @@ -1264,6 +1265,40 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } +sub cmt_sha2rev_batch { + my %s2r; + my ($pid, $in, $out, $ctx) = command_bidi_pipe(qw/cat-file --batch/); + my $list = shift; + + foreach my $sha (@{$list}) { + my $first = 1; + my $size = 0; + print $out $sha, "\n"; + + while (my $line = <$in>) { + if ($first && $line =~ /^[[:xdigit:]]{40}\smissing$/) { + last; + } elsif ($first && + $line =~ /^[[:xdigit:]]{40}\scommit\s(\d+)$/) { + $first = 0; + $size = $1; + next; + } elsif ($line =~ /^(git-svn-id: )/) { + my (undef, $rev, undef) = + extract_metadata($line); + $s2r{$sha} = $rev; + } + + $size -= length($line); + last if ($size == 0); + } + } + + command_close_bidi_pipe($pid, $in, $out, $ctx); + + return \%s2r; +} + sub working_head_info { my ($head, $refs) = @_; my @args = ('log', '--no-color', '--first-parent', '--pretty=medium'); @@ -5001,11 +5036,22 @@ sub cmd_blame { '--', $path); my ($sha1); my %authors; + my @buffer; + my %dsha; #distinct sha keys + while (my $line = <$fh>) { + push @buffer, $line; if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) { - $sha1 = $1; - (undef, $rev, undef) = ::cmt_metadata($1); - $rev = '0' if (!$rev); + $dsha{$1} = 1; + } + } + + my $s2r = ::cmt_sha2rev_batch([keys %dsha]); + + foreach my $line (@buffer) { + if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) { + $rev = $s2r->{$1}; + $rev = '0' if (!$rev) } elsif ($line =~ /^author (.*)/) { $authors{$rev} = $1; From 0d8bee71af1cda3d13d896c210773216dcf87b7c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 Apr 2009 10:46:17 -0700 Subject: [PATCH 39/50] git-svn: Add per-svn-remote ignore-paths config The --ignore-paths option to fetch is very useful for working on a subset of a SVN repository. For proper operation, every command that causes a fetch (explicit or implied) must include a matching --ignore-paths option. This patch adds a persistent svn-remote.$repo_id.ignore-paths config by promoting Fetcher::is_path_ignored to a member function and initializing $self->{ignore_regex} in Fetcher::new. Command line --ignore-paths is still recognized and acts in addition to the config value. Signed-off-by: Ben Jackson Acked-by: Eric Wong --- Documentation/git-svn.txt | 22 ++++++++----- git-svn.perl | 26 +++++++++------- t/t9134-git-svn-ignore-paths.sh | 55 +++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 85b2c8da5d..aad5e65c70 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -107,17 +107,25 @@ repository, either don't use this option or you should both use it in the same local timezone. --ignore-paths=;; - This allows one to specify Perl regular expression that will + This allows one to specify a Perl regular expression that will cause skipping of all matching paths from checkout from SVN. - Examples: + The '--ignore-paths' option should match for every 'fetch' + (including automatic fetches due to 'clone', 'dcommit', + 'rebase', etc) on a given repository. - --ignore-paths="^doc" - skip "doc*" directory for every fetch. +config key: svn-remote..ignore-paths - --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches" - and "tags" of first level directories. + If the ignore-paths config key is set and the command + line option is also given, both regular expressions + will be used. - Regular expression is not persistent, you should specify - it every time when fetching. +Examples: + + --ignore-paths="^doc" - skip "doc*" directory for every + fetch. + + --ignore-paths="^[^/]+/(?:branches|tags)" - skip + "branches" and "tags" of first level directories. 'clone':: Runs 'init' and 'fetch'. It will automatically create a diff --git a/git-svn.perl b/git-svn.perl index 50a398b3e7..279847921b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3331,6 +3331,8 @@ sub new { $self->{empty_symlinks} = _mark_empty_symlinks($git_svn, $switch_path); } + $self->{ignore_regex} = eval { command_oneline('config', '--get', + "svn-remote.$git_svn->{repo_id}.ignore-paths") }; $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -3395,8 +3397,10 @@ sub in_dot_git { # return value: 0 -- don't ignore, 1 -- ignore sub is_path_ignored { - my ($path) = @_; + my ($self, $path) = @_; return 1 if in_dot_git($path); + return 1 if defined($self->{ignore_regex}) && + $path =~ m!$self->{ignore_regex}!; return 0 unless defined($_ignore_regex); return 1 if $path =~ m!$_ignore_regex!o; return 0; @@ -3427,7 +3431,7 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); my $gpath = $self->git_path($path); return undef if ($gpath eq ''); @@ -3460,7 +3464,7 @@ sub open_file { my ($self, $path, $pb, $rev) = @_; my ($mode, $blob); - goto out if is_path_ignored($path); + goto out if $self->is_path_ignored($path); my $gpath = $self->git_path($path); ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath") @@ -3480,7 +3484,7 @@ sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; my $mode; - if (!is_path_ignored($path)) { + if (!$self->is_path_ignored($path)) { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $mode = '100644'; @@ -3491,7 +3495,7 @@ sub add_file { sub add_directory { my ($self, $path, $cp_path, $cp_rev) = @_; - goto out if is_path_ignored($path); + goto out if $self->is_path_ignored($path); my $gpath = $self->git_path($path); if ($gpath eq '') { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -3515,7 +3519,7 @@ out: sub change_dir_prop { my ($self, $db, $prop, $value) = @_; - return undef if is_path_ignored($db->{path}); + return undef if $self->is_path_ignored($db->{path}); $self->{dir_prop}->{$db->{path}} ||= {}; $self->{dir_prop}->{$db->{path}}->{$prop} = $value; undef; @@ -3523,7 +3527,7 @@ sub change_dir_prop { sub absent_directory { my ($self, $path, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); $self->{absent_dir}->{$pb->{path}} ||= []; push @{$self->{absent_dir}->{$pb->{path}}}, $path; undef; @@ -3531,7 +3535,7 @@ sub absent_directory { sub absent_file { my ($self, $path, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); $self->{absent_file}->{$pb->{path}} ||= []; push @{$self->{absent_file}->{$pb->{path}}}, $path; undef; @@ -3539,7 +3543,7 @@ sub absent_file { sub change_file_prop { my ($self, $fb, $prop, $value) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->is_path_ignored($fb->{path}); if ($prop eq 'svn:executable') { if ($fb->{mode_b} != 120000) { $fb->{mode_b} = defined $value ? 100755 : 100644; @@ -3555,7 +3559,7 @@ sub change_file_prop { sub apply_textdelta { my ($self, $fb, $exp) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->is_path_ignored($fb->{path}); my $fh = $::_repository->temp_acquire('svn_delta'); # $fh gets auto-closed() by SVN::TxDelta::apply(), # (but $base does not,) so dup() it for reading in close_file @@ -3602,7 +3606,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->is_path_ignored($fb->{path}); my $hash; my $path = $self->git_path($fb->{path}); diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh index c4b5b8bcf7..b9a15978eb 100755 --- a/t/t9134-git-svn-ignore-paths.sh +++ b/t/t9134-git-svn-ignore-paths.sh @@ -31,6 +31,22 @@ test_expect_success 'clone an SVN repository with ignored www directory' ' test_cmp expect expect2 ' +test_expect_success 'init+fetch an SVN repository with ignored www directory' ' + git svn init "$svnrepo" c && + ( cd c && git svn fetch --ignore-paths="^www" ) && + rm expect2 && + echo test_qqq > expect && + for i in c/*/*.txt; do cat $i >> expect2; done && + test_cmp expect expect2 +' + +test_expect_success 'set persistent ignore-paths config' ' + ( + cd g && + git config svn-remote.svn.ignore-paths "^www" + ) +' + test_expect_success 'SVN-side change outside of www' ' ( cd s && @@ -41,9 +57,20 @@ test_expect_success 'SVN-side change outside of www' ' ) ' -test_expect_success 'update git svn-cloned repo' ' +test_expect_success 'update git svn-cloned repo (config ignore)' ' ( cd g && + git svn rebase && + printf "test_qqq\nb\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'update git svn-cloned repo (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\n" > expect && for i in */*.txt; do cat $i >> expect2; done && @@ -62,9 +89,20 @@ test_expect_success 'SVN-side change inside of ignored www' ' ) ' -test_expect_success 'update git svn-cloned repo' ' +test_expect_success 'update git svn-cloned repo (config ignore)' ' ( cd g && + git svn rebase && + printf "test_qqq\nb\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'update git svn-cloned repo (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\n" > expect && for i in */*.txt; do cat $i >> expect2; done && @@ -84,9 +122,20 @@ test_expect_success 'SVN-side change in and out of ignored www' ' ) ' -test_expect_success 'update git svn-cloned repo again' ' +test_expect_success 'update git svn-cloned repo again (config ignore)' ' ( cd g && + git svn rebase && + printf "test_qqq\nb\nygg\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'update git svn-cloned repo again (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\nygg\n" > expect && for i in */*.txt; do cat $i >> expect2; done && From 88ec205477e18e612ab854f20ef87aa244b8debe Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 Apr 2009 10:46:18 -0700 Subject: [PATCH 40/50] git-svn: Save init/clone --ignore-paths in config The --ignored-paths argument is now stored as "svn-remote.$REMOTE_NAME.ignore-paths" in the config file. [ew: edited subject and message] Signed-off-by: Ben Jackson Acked-by: Eric Wong --- Documentation/git-svn.txt | 4 ++++ git-svn.perl | 3 +++ t/t9134-git-svn-ignore-paths.sh | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index aad5e65c70..9229d45ad9 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -85,6 +85,10 @@ COMMANDS specified, the prefix must include a trailing slash. Setting a prefix is useful if you wish to track multiple projects that share a common repository. +--ignore-paths=;; + When passed to 'init' or 'clone' this regular expression will + be preserved as a config key. See 'fetch' for a description + of '--ignore-paths'. 'fetch':: Fetch unfetched revisions from the Subversion remote we are diff --git a/git-svn.perl b/git-svn.perl index 279847921b..bc3ba064e4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -336,6 +336,9 @@ sub do_git_init_db { command_noisy('config', "$pfx.$i", $icv{$i}); $set = $i; } + my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex; + command_noisy('config', "$pfx.ignore-paths", $$ignore_regex) + if defined $$ignore_regex; } sub init_subdir { diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh index b9a15978eb..71fdc4a69d 100755 --- a/t/t9134-git-svn-ignore-paths.sh +++ b/t/t9134-git-svn-ignore-paths.sh @@ -40,10 +40,10 @@ test_expect_success 'init+fetch an SVN repository with ignored www directory' ' test_cmp expect expect2 ' -test_expect_success 'set persistent ignore-paths config' ' +test_expect_success 'verify ignore-paths config saved by clone' ' ( cd g && - git config svn-remote.svn.ignore-paths "^www" + git config --get svn-remote.svn.ignore-paths | fgrep "www" ) ' From b6c29915d2efd0d2cb56eca88bd8e6b4999546dc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 9 Apr 2009 10:45:39 -0500 Subject: [PATCH 41/50] Update delta compression message to be less misleading In the case of a small repository, pack-objects is smart enough to not start more threads than necessary. However, the output to the user always reports the value of the pack.threads configuration and not the real number of threads to be used. Signed-off-by: Dan McGee Acked-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 9fc3b35547..99181fd7ee 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1612,7 +1612,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, return; } if (progress > pack_to_stdout) - fprintf(stderr, "Delta compression using %d threads.\n", + fprintf(stderr, "Delta compression using up to %d threads.\n", delta_search_threads); /* Partition the work amongst work threads. */ From d3c9634eacdcaa71cbd69a160e6f4e80ddb7ab63 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 9 Apr 2009 13:29:57 +0200 Subject: [PATCH 42/50] git-svn: always initialize with core.autocrlf=false It has been reported time and time again in relation to msysGit that git-svn does not work well when core.autocrlf has any value other than 'false'. So let's make it so by default. Signed-off-by: Johannes Schindelin Acked-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/git-svn.perl b/git-svn.perl index bc3ba064e4..c5965c9aaf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -328,6 +328,7 @@ sub do_git_init_db { command_noisy(@init_db); $_repository = Git->repository(Repository => ".git"); } + command_noisy('config', 'core.autocrlf', 'false'); my $set; my $pfx = "svn-remote.$Git::SVN::default_repo_id"; foreach my $i (keys %icv) { From 519d05be9015871e422cd16ebced620cb01f8b3c Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 10 Apr 2009 00:25:37 +0200 Subject: [PATCH 43/50] Replace ",<,>,& with their respective XML entities in DAV requests If the repo url or the user email contain XML special characters, the remote DAV server is likely to reject the LOCK requests because the XML is then malformed. Signed-off-by: Mike Hommey Signed-off-by: Junio C Hamano --- http-push.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/http-push.c b/http-push.c index feeb340daf..5138224cc3 100644 --- a/http-push.c +++ b/http-push.c @@ -186,6 +186,32 @@ enum dav_header_flag { DAV_HEADER_TIMEOUT = (1u << 2) }; +static char *xml_entities(char *s) +{ + struct strbuf buf = STRBUF_INIT; + while (*s) { + size_t len = strcspn(s, "\"<>&"); + strbuf_add(&buf, s, len); + s += len; + switch (*s) { + case '"': + strbuf_addstr(&buf, """); + break; + case '<': + strbuf_addstr(&buf, "<"); + break; + case '>': + strbuf_addstr(&buf, ">"); + break; + case '&': + strbuf_addstr(&buf, "&"); + break; + } + s++; + } + return strbuf_detach(&buf, NULL); +} + static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) { struct strbuf buf = STRBUF_INIT; @@ -1225,6 +1251,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) struct remote_lock *lock = NULL; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; + char *escaped; url = xmalloc(strlen(repo->url) + strlen(path) + 1); sprintf(url, "%s%s", repo->url, path); @@ -1259,7 +1286,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout) ep = strchr(ep + 1, '/'); } - strbuf_addf(&out_buffer.buf, LOCK_REQUEST, git_default_email); + escaped = xml_entities(git_default_email); + strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped); + free(escaped); sprintf(timeout_header, "Timeout: Second-%ld", timeout); dav_headers = curl_slist_append(dav_headers, timeout_header); @@ -1584,8 +1613,11 @@ static int locking_available(void) struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; int lock_flags = 0; + char *escaped; - strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, repo->url); + escaped = xml_entities(repo->url); + strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, escaped); + free(escaped); dav_headers = curl_slist_append(dav_headers, "Depth: 0"); dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); From cced5fbc241f1274ba532040b985f38c15bbf555 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 9 Apr 2009 11:46:15 -0700 Subject: [PATCH 44/50] Allow users to un-configure rename detection I told people on the kernel mailing list to please use "-M" when sending me rename patches, so that I can see what they do while reading email rather than having to apply the patch and then look at the end result. I also told them that if they want to make it the default, they can just add [diff] renames to their ~/.gitconfig file. And while I was thinking about that, I wanted to also check whether you can then mark individual projects to _not_ have that default in the per-repository .git/config file. And you can't. Currently you cannot have a global "enable renames by default" and then a local ".. but not for _this_ project". Why? Because if somebody writes [diff] renames = no we simply ignore it, rather than resetting "diff_detect_rename_default" back to zero. Fixed thusly. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- diff.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/diff.c b/diff.c index e0fa78c84d..3ac71686eb 100644 --- a/diff.c +++ b/diff.c @@ -62,6 +62,15 @@ static int parse_diff_color_slot(const char *var, int ofs) die("bad config variable '%s'", var); } +static int git_config_rename(const char *var, const char *value) +{ + if (!value) + return DIFF_DETECT_RENAME; + if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy")) + return DIFF_DETECT_COPY; + return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0; +} + /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -75,13 +84,7 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "diff.renames")) { - if (!value) - diff_detect_rename_default = DIFF_DETECT_RENAME; - else if (!strcasecmp(value, "copies") || - !strcasecmp(value, "copy")) - diff_detect_rename_default = DIFF_DETECT_COPY; - else if (git_config_bool(var,value)) - diff_detect_rename_default = DIFF_DETECT_RENAME; + diff_detect_rename_default = git_config_rename(var, value); return 0; } if (!strcmp(var, "diff.autorefreshindex")) { From ee7ec2f9ded03700f2b95cc1d4b3d60ed374132a Mon Sep 17 00:00:00 2001 From: Ben Walton Date: Sun, 22 Mar 2009 09:20:44 -0400 Subject: [PATCH 45/50] documentation: Makefile accounts for SHELL_PATH setting Ensure that the Makefile that generates and installs the Documentation is aware of any SHELL_PATH setting. Use this value if found or the current setting for SHELL if not. This is an accommodation for systems where sh is not POSIX enough. Signed-off-by: Ben Walton Signed-off-by: Junio C Hamano --- Documentation/Makefile | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Documentation/Makefile b/Documentation/Makefile index dba97dc21d..e18242a6d4 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -103,6 +103,10 @@ ifdef DOCBOOK_SUPPRESS_SP XMLTO_EXTRA += -m manpage-suppress-sp.xsl endif +SHELL_PATH ?= $(SHELL) +# Shell quote; +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + # # Please note that there is a minor bug in asciidoc. # The version after 6.0.3 _will_ include the patch found here: @@ -178,7 +182,7 @@ install-pdf: pdf $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) install-html: html - sh ./install-webdoc.sh $(DESTDIR)$(htmldir) + '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir) ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE @@ -240,7 +244,7 @@ user-manual.xml: user-manual.txt user-manual.conf technical/api-index.txt: technical/api-index-skel.txt \ technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) - $(QUIET_GEN)cd technical && sh ./api-index.sh + $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ @@ -285,7 +289,7 @@ $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml howto-index.txt: howto-index.sh $(wildcard howto/*.txt) $(QUIET_GEN)$(RM) $@+ $@ && \ - sh ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \ + '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \ mv $@+ $@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt @@ -299,14 +303,14 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt mv $@+ $@ install-webdoc : html - sh ./install-webdoc.sh $(WEBDOC_DEST) + '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST) quick-install: quick-install-man quick-install-man: - sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir) quick-install-html: - sh ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) .PHONY: .FORCE-GIT-VERSION-FILE From 213152688c85a0e4d591abc1c10b7c279ffefb99 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 10 Apr 2009 15:20:18 -0700 Subject: [PATCH 46/50] process_{tree,blob}: Remove useless xstrdup calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Wed, 8 Apr 2009, Björn Steinbrink wrote: > > The name of the processed object was duplicated for passing it to > add_object(), but that already calls path_name, which allocates a new > string anyway. So the memory allocated by the xstrdup calls just went > nowhere, leaking memory. Ack, ack. There's another easy 5% or so for the built-in object walker: once we've created the hash from the name, the name isn't interesting any more, and so something trivial like this can help a bit. Does it matter? Probably not on its own. But a few more memory saving tricks and it might all make a difference. Linus Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 84a13c7ccd..a6adc8c271 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1861,6 +1861,8 @@ static void show_object(struct object_array_entry *p) add_preferred_base_object(p->name); add_object_entry(p->item->sha1, p->item->type, p->name, 0); p->item->flags |= OBJECT_ADDED; + free((char *)p->name); + p->name = NULL; } static void show_edge(struct commit *commit) From c6d8f7635f619f6576dccf17c1f1264d2cc37a2a Mon Sep 17 00:00:00 2001 From: "Daniel Cheng (aka SDiZ)" Date: Fri, 10 Apr 2009 14:26:49 +0800 Subject: [PATCH 47/50] State the effect of filter-branch on graft explicitly Signed-off-by: Daniel Cheng (aka SDiZ) Signed-off-by: Junio C Hamano --- Documentation/git-filter-branch.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index b0e710d5f9..7747c4877d 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -31,6 +31,9 @@ changes, which would normally have no effect. Nevertheless, this may be useful in the future for compensating for some git bugs or such, therefore such a usage is permitted. +*NOTE*: This command honors `.git/info/grafts`. If you have any grafts +defined, running this command will make them permanent. + *WARNING*! The rewritten history will have different object names for all the objects and will not converge with the original branch. You will not be able to easily push and distribute the rewritten branch on top of the From 3bd1bb327eb4d3e386800897734f9f42b48280ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 12 Apr 2009 15:43:24 -0700 Subject: [PATCH 48/50] GIT 1.6.2.3 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.2.3.txt | 6 ------ GIT-VERSION-GEN | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes-1.6.2.3.txt index 6560593fd5..4d3c1ac91c 100644 --- a/Documentation/RelNotes-1.6.2.3.txt +++ b/Documentation/RelNotes-1.6.2.3.txt @@ -20,9 +20,3 @@ Fixes since v1.6.2.2 to prevent them from being repacked. Many small documentation updates are included as well. - ---- -exec >/var/tmp/1 -echo O=$(git describe maint) -O=v1.6.2.2-41-gbff82d0 -git shortlog --no-merges $O..maint diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index ac2f24795c..04713fc7c9 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.2.2 +DEF_VER=v1.6.2.3 LF=' ' From 54a47493a34fbf25370c526a03c38894d709f3e4 Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Fri, 10 Apr 2009 09:34:40 +0900 Subject: [PATCH 49/50] Documentation/git.txt: GIT 1.6.2.2 has been out for a while These links inside "stalenotes" section need to be updated on the master branch every time a new stable or maintenance release is made. Signed-off-by: Nanako Shiraishi Signed-off-by: Junio C Hamano --- Documentation/git.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index 2ce5e6b451..eca29f00ea 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.2.1/git.html[documentation for release 1.6.2.1] +* link:v1.6.2.2/git.html[documentation for release 1.6.2.2] * release notes for + link:RelNotes-1.6.2.2.txt[1.6.2.2], link:RelNotes-1.6.2.1.txt[1.6.2.1], link:RelNotes-1.6.2.txt[1.6.2]. From c965c029330b1f81cc107c5d829e7fd79c61d8ea Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 12 Apr 2009 17:05:55 -0700 Subject: [PATCH 50/50] GIT 1.6.3-rc0 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.3.txt | 14 ++++++++++++-- Documentation/git.txt | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt index 9aa143b199..839498c38a 100644 --- a/Documentation/RelNotes-1.6.3.txt +++ b/Documentation/RelNotes-1.6.3.txt @@ -35,6 +35,8 @@ Updates since v1.6.2 (subsystems) +* various git-svn updates. + (performance) * many uses of lstat(2) in the codepath for "git checkout" have been @@ -80,9 +82,12 @@ Updates since v1.6.2 * You can give --date= option to git-blame. -* git-branch -r shows HEAD symref that points at a remote branch in +* "git-branch -r" shows HEAD symref that points at a remote branch in interest of each tracked remote repository. +* "git-branch -v -v" is a new way to get list of names for branches and the + "upstream" branch for them. + * git-config learned -e option to open an editor to edit the config file directly. @@ -90,6 +95,8 @@ Updates since v1.6.2 * git-fast-export choked when seeing a tag that does not point at commit. +* git-for-each-ref learned a new "upstream" token. + * git-format-patch can be told to use attachment with a new configuration, format.attach. @@ -118,6 +125,9 @@ Updates since v1.6.2 * Output from git-remote command has been vastly improved. +* "git remote update --prune $remote" updates from the named remote and + then prunes stale tracking branches. + * git-send-email learned --confirm option to review the Cc: list before sending the messages out. @@ -166,6 +176,6 @@ v1.6.2.X series. --- exec >/var/tmp/1 -O=v1.6.2.2-484-g796b137 +O=v1.6.2.3-497-g54a4749 echo O=$(git describe master) git shortlog --no-merges $O..master ^maint diff --git a/Documentation/git.txt b/Documentation/git.txt index eca29f00ea..470fdc5ecd 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.2.2/git.html[documentation for release 1.6.2.2] +* link:v1.6.2.3/git.html[documentation for release 1.6.2.3] * release notes for + link:RelNotes-1.6.2.3.txt[1.6.2.3], link:RelNotes-1.6.2.2.txt[1.6.2.2], link:RelNotes-1.6.2.1.txt[1.6.2.1], link:RelNotes-1.6.2.txt[1.6.2].