From 3d4ecc0e23b2b2f555e7d33b5623fd4e67cc2ac7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:05:01 -0400 Subject: 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); -- cgit v1.2.3 From 8db9a4b85d6b0d7424c8a19b77a5baa8529ab64c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:06:51 -0400 Subject: 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 | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 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 (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } + if (!prefixcmp(name, "refname")) + refname = ref->refname; + else + continue; + + 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; } } -- cgit v1.2.3 From 8cae19d987b1bbd43258558f591e39d9d216dcb3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:09:39 -0400 Subject: 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)" ' -- cgit v1.2.3 From 7c2b3029df45a74d0ebd11afcc94259791cfb90d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:14:20 -0400 Subject: 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); -- cgit v1.2.3 From 2d8a7f0b30b4f9ef750ab763aabec117ffe4e749 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 7 Apr 2009 03:16:56 -0400 Subject: 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), -- cgit v1.2.3