This is part of unification of the commands 'git tag -l, git branch -l and git for-each-ref'. This ports over branch.c to use ref-filter's printing options. Initially posted here: $(gmane/279226). It was decided that this series would follow up after refactoring ref-filter parsing mechanism, which is now merged into master (9606218b32344c5c756f7c29349d3845ef60b80c). v1 can be found here: $(gmane/288342) v2 can be found here: $(gmane/288863) v3 can be found here: $(gmane/290299) v4 can be found here: $(gmane/291106) Changes in this version (v5b): 1. Added the first patch of the series which was missing in v5. Changes in v5: 1. Rebased on top of jk/branch-shortening-funny-symrefs ($gmane/291295). 2. Introduced refname_atom_parser_internal() to act as a common ground for ref-printing atoms. Written by Jeff ($gmane/291497). 3. Ensure that ':dir', 'base', 'strip' is available to all ref-printing atoms. Written by Jeff. 4. Fix a memory leak which was reported by Stefan ($gmane/291703). Thanks to Junio, Jeff and Stefan for suggestions on the previous version. Karthik Nayak (17): ref-filter: implement %(if), %(then), and %(else) atoms ref-filter: include reference to 'used_atom' within 'atom_value' ref-filter: implement %(if:equals=<string>) and %(if:notequals=<string>) ref-filter: modify "%(objectname:short)" to take length ref-filter: move get_head_description() from branch.c ref-filter: introduce format_ref_array_item() ref-filter: make %(upstream:track) prints "[gone]" for invalid upstreams ref-filter: add support for %(upstream:track,nobracket) ref-filter: make "%(symref)" atom work with the ':short' modifier ref-filter: introduce refname_atom_parser_internal() ref-filter: introduce symref_atom_parser() and refname_atom_parser() ref-filter: make remote_ref_atom_parser() use refname_atom_parser_internal() ref-filter: add `:dir` and `:base` options for ref printing atoms ref-filter: allow porcelain to translate messages in the output branch, tag: use porcelain output branch: use ref-filter printing APIs branch: implement '--format' option Documentation/git-branch.txt | 7 +- Documentation/git-for-each-ref.txt | 87 +++++-- builtin/branch.c | 275 +++++++--------------- builtin/tag.c | 2 + ref-filter.c | 456 +++++++++++++++++++++++++++++++------ ref-filter.h | 7 + t/t3203-branch-output.sh | 16 +- t/t6040-tracking-info.sh | 2 +- t/t6300-for-each-ref.sh | 73 +++++- t/t6302-for-each-ref-filter.sh | 94 ++++++++ 10 files changed, 726 insertions(+), 293 deletions(-) Interdiff: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 0954269..4a5e9ea 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -116,21 +116,23 @@ objectname:: 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. Additionally respects `:track` to show - "[ahead N, behind M]" and `:trackshort` to show the terse - version: ">" (ahead), "<" (behind), "<>" (ahead and behind), - or "=" (in sync). `:track` also prints "[gone]" whenever - unknown upstream ref is encountered. Append `:track,nobracket` - to show tracking information without brackets (i.e "ahead N, - behind M"). Has no effect if the ref does not have tracking - information associated with it. + from the displayed ref. Respects `:short`, `:strip`, `:base` + and `:dir` in the same way as `refname` above. Additionally + respects `:track` to show "[ahead N, behind M]" and + `:trackshort` to show the terse version: ">" (ahead), "<" + (behind), "<>" (ahead and behind), or "=" (in sync). `:track` + also prints "[gone]" whenever unknown upstream ref is + encountered. Append `:track,nobracket` to show tracking + information without brackets (i.e "ahead N, behind M"). Has + no effect if the ref does not have tracking information + associated with it. push:: - The name of a local ref which represents the `@{push}` location - for the displayed ref. Respects `:short`, `:track`, and - `:trackshort` options as `upstream` does. Produces an empty - string if no `@{push}` ref is configured. + The name of a local ref which represents the `@{push}` + location for the displayed ref. Respects `:short`, `:strip`, + `:track`, `:trackshort`, `:base` and `:dir` options as + `upstream` does. Produces an empty string if no `@{push}` ref + is configured. HEAD:: '*' if HEAD matches current ref (the checked out branch), ' ' @@ -165,6 +167,12 @@ if:: the value between the %(if:...) and %(then) atoms with the given string. +symref:: + The ref which the given symbolic ref refers to. If not a + symbolic ref, nothing is printed. Respects the `:short`, + `:strip`, `:base` and `:dir` options 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/branch.c b/builtin/branch.c index c9a2e5b..6847ac3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -288,9 +288,11 @@ static int calc_maxwidth(struct ref_array *refs, int remote_bonus) skip_prefix(it->refname, "refs/heads/", &desc); skip_prefix(it->refname, "refs/remotes/", &desc); - if (it->kind == FILTER_REFS_DETACHED_HEAD) - w = strlen(get_head_description()); - else + if (it->kind == FILTER_REFS_DETACHED_HEAD) { + char *head_desc = get_head_description(); + w = strlen(head_desc); + free(head_desc); + } else w = utf8_strwidth(desc); if (it->kind == FILTER_REFS_REMOTES) diff --git a/ref-filter.c b/ref-filter.c index 3435df1..74c4869 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -50,6 +50,11 @@ struct if_then_else { condition_satisfied : 1; }; +struct refname_atom { + enum { R_BASE, R_DIR, R_NORMAL, R_SHORT, R_STRIP } option; + unsigned int strip; +}; + /* * An atom is a valid field atom listed below, possibly prefixed with * a "*" to denote deref_tag(). @@ -67,7 +72,8 @@ static struct used_atom { char color[COLOR_MAXLEN]; struct align align; struct { - enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT } option; + enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option; + struct refname_atom refname; unsigned int nobracket: 1; } remote_ref; struct { @@ -82,11 +88,7 @@ static struct used_atom { enum { O_FULL, O_LENGTH, O_SHORT } option; unsigned int length; } objectname; - enum { S_FULL, S_SHORT } symref; - struct { - enum { R_BASE, R_DIR, R_NORMAL, R_SHORT, R_STRIP } option; - unsigned int strip; - } refname; + struct refname_atom refname; } u; } *used_atom; static int used_atom_cnt, need_tagged, need_symref; @@ -100,13 +102,34 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value) die(_("unrecognized color: %%(color:%s)"), color_value); } +static void refname_atom_parser_internal(struct refname_atom *atom, + const char *arg, const char *name) +{ + if (!arg) + atom->option = R_NORMAL; + else if (!strcmp(arg, "short")) + atom->option = R_SHORT; + else if (skip_prefix(arg, "strip=", &arg)) { + atom->option = R_STRIP; + if (strtoul_ui(arg, 10, &atom->strip) || atom->strip <= 0) + die(_("positive value expected refname:strip=%s"), arg); + } else if (!strcmp(arg, "dir")) + atom->option = R_DIR; + else if (!strcmp(arg, "base")) + atom->option = R_BASE; + else + die(_("unrecognized %%(%s) argument: %s"), name, arg); +} + static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) { struct string_list params = STRING_LIST_INIT_DUP; int i; if (!arg) { - atom->u.remote_ref.option = RR_NORMAL; + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); return; } @@ -116,16 +139,17 @@ static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) for (i = 0; i < params.nr; i++) { const char *s = params.items[i].string; - if (!strcmp(s, "short")) - atom->u.remote_ref.option = RR_SHORTEN; - else if (!strcmp(s, "track")) + if (!strcmp(s, "track")) atom->u.remote_ref.option = RR_TRACK; else if (!strcmp(s, "trackshort")) atom->u.remote_ref.option = RR_TRACKSHORT; else if (!strcmp(s, "nobracket")) atom->u.remote_ref.nobracket = 1; - else - die(_("unrecognized format: %%(%s)"), atom->name); + else { + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); + } } string_list_clear(¶ms, 0); @@ -170,7 +194,7 @@ static void objectname_atom_parser(struct used_atom *atom, const char *arg) else if (!strcmp(arg, "short")) atom->u.objectname.option = O_SHORT; else if (skip_prefix(arg, "short=", &arg)) { - atom->u.contents.option = O_LENGTH; + atom->u.objectname.option = O_LENGTH; if (strtoul_ui(arg, 10, &atom->u.objectname.length) || atom->u.objectname.length == 0) die(_("positive value expected objectname:short=%s"), arg); @@ -180,6 +204,16 @@ static void objectname_atom_parser(struct used_atom *atom, const char *arg) die(_("unrecognized %%(objectname) argument: %s"), arg); } +static void symref_atom_parser(struct used_atom *atom, const char *arg) +{ + return refname_atom_parser_internal(&atom->u.refname, arg, atom->name); +} + +static void refname_atom_parser(struct used_atom *atom, const char *arg) +{ + return refname_atom_parser_internal(&atom->u.refname, arg, atom->name); +} + static align_type parse_align_position(const char *s) { if (!strcmp(s, "right")) @@ -242,41 +276,12 @@ static void if_atom_parser(struct used_atom *atom, const char *arg) die(_("unrecognized %%(if) argument: %s"), arg); } -static void symref_atom_parser(struct used_atom *atom, const char *arg) -{ - if (!arg) - atom->u.symref = S_FULL; - else if (!strcmp(arg, "short")) - atom->u.symref = S_SHORT; - else - die(_("unrecognized %%(symref) argument: %s"), arg); -} - -static void refname_atom_parser(struct used_atom *atom, const char *arg) -{ - if (!arg) - atom->u.refname.option = R_NORMAL; - else if (!strcmp(arg, "short")) - atom->u.refname.option = R_SHORT; - else if (skip_prefix(arg, "strip=", &arg)) { - atom->u.contents.option = R_STRIP; - if (strtoul_ui(arg, 10, &atom->u.refname.strip) || - atom->u.refname.strip <= 0) - die(_("positive value expected refname:strip=%s"), arg); - } else if (!strcmp(arg, "dir")) - atom->u.contents.option = R_DIR; - else if (!strcmp(arg, "base")) - atom->u.contents.option = R_BASE; - else - die(_("unrecognized %%(refname) argument: %s"), arg); -} - static struct { const char *name; cmp_type cmp_type; void (*parser)(struct used_atom *atom, const char *arg); } valid_atom[] = { - { "refname", FIELD_STR, refname_atom_parser }, + { "refname" , FIELD_STR, refname_atom_parser }, { "objecttype" }, { "objectsize", FIELD_ULONG }, { "objectname", FIELD_STR, objectname_atom_parser }, @@ -1098,7 +1103,7 @@ static const char *strip_ref_components(const char *refname, unsigned int len) while (remaining) { switch (*start++) { case '\0': - die("ref '%s' does not have %ud components to :strip", + die(_("ref '%s' does not have %ud components to :strip"), refname, len); case '/': remaining--; @@ -1108,12 +1113,40 @@ static const char *strip_ref_components(const char *refname, unsigned int len) return start; } +static const char *show_ref(struct refname_atom *atom, const char *refname) +{ + if (atom->option == R_SHORT) + return shorten_unambiguous_ref(refname, warn_ambiguous_refs); + else if (atom->option == R_STRIP) + return strip_ref_components(refname, atom->strip); + else if (atom->option == R_BASE) { + const char *sp, *ep; + pp+ if (skip_prefix(refname, "refs/", &sp)) { + ep = strchr(sp, '/'); + if (!ep) + return ""; + return xstrndup(sp, ep - sp); + } + return ""; + } else if (atom->option == R_DIR) { + const char *sp, *ep; + + sp = refname; + ep = strrchr(sp, '/'); + if (!ep) + return ""; + return xstrndup(sp, ep - sp); + } else + return refname; +} + static void fill_remote_ref_details(struct used_atom *atom, const char *refname, struct branch *branch, const char **s) { int num_ours, num_theirs; - if (atom->u.remote_ref.option == RR_SHORTEN) - *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs); + if (atom->u.remote_ref.option == RR_REF) + *s = show_ref(&atom->u.remote_ref.refname, refname); else if (atom->u.remote_ref.option == RR_TRACK) { if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) { @@ -1145,8 +1178,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname, *s = ">"; else *s = "<>"; - } else /* RR_NORMAL */ - *s = refname; + } else + die("BUG: unhandled RR_* enum"); } char *get_head_description(void) @@ -1184,41 +1217,15 @@ static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref { if (!ref->symref) return ""; - else if (atom->u.symref == S_SHORT) - return shorten_unambiguous_ref(ref->symref, - warn_ambiguous_refs); else - return ref->symref; + return show_ref(&atom->u.refname, ref->symref); } static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref) { if (ref->kind & FILTER_REFS_DETACHED_HEAD) return get_head_description(); - if (atom->u.refname.option == R_SHORT) - return shorten_unambiguous_ref(ref->refname, warn_ambiguous_refs); - else if (atom->u.refname.option == R_STRIP) - return strip_ref_components(ref->refname, atom->u.refname.strip); - else if (atom->u.refname.option == R_BASE) { - const char *sp, *ep; - - if (skip_prefix(ref->refname, "refs/", &sp)) { - ep = strchr(sp, '/'); - if (!ep) - return ""; - return xstrndup(sp, ep - sp); - } - return ""; - } else if (atom->u.refname.option == R_DIR) { - const char *sp, *ep; - - sp = ref->refname; - ep = strrchr(sp, '/'); - if (!ep) - return ""; - return xstrndup(sp, ep - sp); - } else - return ref->refname; + return show_ref(&atom->u.refname, ref->refname); } /* diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 0bd6368..d8edaf2 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -189,7 +189,7 @@ test_expect_success 'local-branch symrefs shortened properly' ' git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one && cat >expect <<-\EOF && ref-to-branch -> branch-one - ref-to-remote -> refs/remotes/origin/branch-one + ref-to-remote -> origin/branch-one EOF git branch >actual.raw && grep ref-to <actual.raw >actual && @@ -203,6 +203,8 @@ test_expect_success 'git branch --format option' ' Refname is refs/heads/branch-one Refname is refs/heads/branch-two Refname is refs/heads/master + Refname is refs/heads/ref-to-branch + Refname is refs/heads/ref-to-remote EOF git branch --format="Refname is %(refname)" >actual && test_cmp expect actual diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 36d32d7..8ff6568 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -57,8 +57,14 @@ test_atom head refname:dir refs/heads test_atom head refname:base heads test_atom head upstream refs/remotes/origin/master test_atom head upstream:short origin/master +test_atom head upstream:strip=2 origin/master +test_atom head upstream:dir refs/remotes/origin +test_atom head upstream:base remotes test_atom head push refs/remotes/myfork/master test_atom head push:short myfork/master +test_atom head push:strip=1 remotes/myfork/master +test_atom head push:dir refs/remotes/myfork +test_atom head push:base remotes test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname $(git rev-parse refs/heads/master) @@ -591,4 +597,31 @@ test_expect_success 'Verify usage of %(symref:short) atom' ' test_cmp expected actual ' +cat >expected <<EOF +master +EOF + +test_expect_success 'Verify usage of %(symref:strip) atom' ' + git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual && + test_cmp expected actual +' + +cat >expected <<EOF +refs/heads +EOF + +test_expect_success 'Verify usage of %(symref:dir) atom' ' + git for-each-ref --format="%(symref:dir)" refs/heads/sym > actual && + test_cmp expected actual +' + +cat >expected <<EOF +heads +EOF + +test_expect_success 'Verify usage of %(symref:base) atom' ' + git for-each-ref --format="%(symref:base)" refs/heads/sym > actual && + test_cmp expected actual +' + test_done -- 2.8.0 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html