From bc147968a4ab0d4fb069b00c9a08ff353266c1f6 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Mon, 18 Nov 2013 23:09:08 +0530 Subject: [PATCH 1/6] t6300 (for-each-ref): clearly demarcate setup Condense the two-step setup into one step, and give it an appropriate name. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 752f5cb7d0..64301e7940 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -18,16 +18,13 @@ setdate_and_increment () { export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } -test_expect_success 'Create sample commit with known timestamp' ' +test_expect_success setup ' setdate_and_increment && echo "Using $datestamp" > one && git add one && git commit -m "Initial" && setdate_and_increment && - git tag -a -m "Tagging at $datestamp" testtag -' - -test_expect_success 'Create upstream config' ' + git tag -a -m "Tagging at $datestamp" testtag && git update-ref refs/remotes/origin/master master && git remote add origin nowhere && git config branch.master.remote origin && From 189a546797413a1c71173c8e5c5073f77acb33f9 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Mon, 18 Nov 2013 23:09:09 +0530 Subject: [PATCH 2/6] t6300 (for-each-ref): don't hardcode SHA-1 hexes Use rev-parse in its place, making it easier for future patches to modify the test script. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- t/t6300-for-each-ref.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 64301e7940..675c2a2cfe 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -49,8 +49,8 @@ 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 -test_atom head tree 0e51c00fcb93dffc755546f27593d511e1bdb46f +test_atom head objectname $(git rev-parse refs/heads/master) +test_atom head tree $(git rev-parse refs/heads/master^{tree}) test_atom head parent '' test_atom head numparent 0 test_atom head object '' @@ -82,11 +82,11 @@ 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 +test_atom tag objectname $(git rev-parse refs/tags/testtag) test_atom tag tree '' test_atom tag parent '' test_atom tag numparent '' -test_atom tag object '67a36f10722846e891fbada1ba48ed035de75581' +test_atom tag object $(git rev-parse refs/tags/testtag^0) test_atom tag type 'commit' test_atom tag author '' test_atom tag authorname '' @@ -302,7 +302,7 @@ test_expect_success 'Check short upstream format' ' ' cat >expected <expected <<\EOF -408fe76d02a785a006c2e9c669b7be5589ede96d refs/tags/master -90b5ebede4899eda64893bc2a4c8f1d6fb6dfc40 refs/tags/bogo +cat >expected < refs/tags/master +$(git rev-parse refs/tags/bogo) refs/tags/bogo EOF test_expect_success 'Verify sort with multiple keys' ' From 7a48b8321934384098a8392b3c4c814ec5058d00 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Mon, 18 Nov 2013 23:09:10 +0530 Subject: [PATCH 3/6] for-each-ref: introduce %(HEAD) asterisk marker 'git branch' shows which branch you are currently on with an '*', but 'git for-each-ref' misses this feature. So, extend its format with %(HEAD) for the same effect. Now you can use the following format in for-each-ref: %(HEAD) %(refname:short) to display an asterisk next to the current ref. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 4 ++++ builtin/for-each-ref.c | 13 +++++++++++-- t/t6300-for-each-ref.sh | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index f2e08d11c1..8f87c9a1ef 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -93,6 +93,10 @@ upstream:: from the displayed ref. Respects `:short` in the same way as `refname` above. +HEAD:: + '*' if HEAD matches current ref (the checked out branch), ' ' + otherwise. + 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 1d4083c2dd..5f1842f48f 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -75,6 +75,7 @@ static struct { { "upstream" }, { "symref" }, { "flag" }, + { "HEAD" }, }; /* @@ -675,8 +676,16 @@ static void populate_value(struct refinfo *ref) v->s = xstrdup(buf + 1); } continue; - } - else + } else if (!strcmp(name, "HEAD")) { + const char *head; + unsigned char sha1[20]; + head = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (!strcmp(ref->refname, head)) + v->s = "*"; + else + v->s = " "; + continue; + } else continue; formatp = strchr(name, ':'); diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 675c2a2cfe..1d998f8550 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -77,6 +77,7 @@ test_atom head contents:body '' test_atom head contents:signature '' test_atom head contents 'Initial ' +test_atom head HEAD '*' test_atom tag refname refs/tags/testtag test_atom tag upstream '' @@ -110,6 +111,7 @@ test_atom tag contents:body '' test_atom tag contents:signature '' test_atom tag contents 'Tagging at 1151939927 ' +test_atom tag HEAD ' ' test_expect_success 'Check invalid atoms names are errors' ' test_must_fail git for-each-ref --format="%(INVALID)" refs/heads From b28061ce0d2e31d390fee5a5724b8a061d0492a7 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Mon, 18 Nov 2013 23:09:11 +0530 Subject: [PATCH 4/6] for-each-ref: introduce %(upstream:track[short]) Introduce %(upstream:track) to display "[ahead M, behind N]" and %(upstream:trackshort) to display "=", ">", "<", or "<>" appropriately (inspired by contrib/completion/git-prompt.sh). Now you can use the following format in for-each-ref: %(refname:short)%(upstream:trackshort) to display refs with terse tracking information. Note that :track and :trackshort only work with "upstream", and error out when used with anything else. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 6 ++++- builtin/for-each-ref.c | 40 +++++++++++++++++++++++++++--- t/t6300-for-each-ref.sh | 27 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 8f87c9a1ef..92e82fdb02 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -91,7 +91,11 @@ 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. + `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). Has no effect if the ref does not have + tracking information associated with it. HEAD:: '*' if HEAD matches current ref (the checked out branch), ' ' diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 5f1842f48f..9e45abd40a 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -641,6 +641,7 @@ static void populate_value(struct refinfo *ref) int deref = 0; const char *refname; const char *formatp; + struct branch *branch = NULL; if (*name == '*') { deref = 1; @@ -652,7 +653,6 @@ static void populate_value(struct refinfo *ref) else if (!prefixcmp(name, "symref")) refname = ref->symref ? ref->symref : ""; else if (!prefixcmp(name, "upstream")) { - struct branch *branch; /* only local branches may have an upstream */ if (prefixcmp(ref->refname, "refs/heads/")) continue; @@ -679,6 +679,7 @@ static void populate_value(struct refinfo *ref) } else if (!strcmp(name, "HEAD")) { const char *head; unsigned char sha1[20]; + head = resolve_ref_unsafe("HEAD", sha1, 1, NULL); if (!strcmp(ref->refname, head)) v->s = "*"; @@ -689,13 +690,46 @@ static void populate_value(struct refinfo *ref) continue; formatp = strchr(name, ':'); - /* look for "short" refname format */ if (formatp) { + int num_ours, num_theirs; + formatp++; if (!strcmp(formatp, "short")) refname = shorten_unambiguous_ref(refname, warn_ambiguous_refs); - else + else if (!strcmp(formatp, "track") && + !prefixcmp(name, "upstream")) { + char buf[40]; + + stat_tracking_info(branch, &num_ours, &num_theirs); + if (!num_ours && !num_theirs) + v->s = ""; + else if (!num_ours) { + sprintf(buf, "[behind %d]", num_theirs); + v->s = xstrdup(buf); + } else if (!num_theirs) { + sprintf(buf, "[ahead %d]", num_ours); + v->s = xstrdup(buf); + } else { + sprintf(buf, "[ahead %d, behind %d]", + num_ours, num_theirs); + v->s = xstrdup(buf); + } + continue; + } else if (!strcmp(formatp, "trackshort") && + !prefixcmp(name, "upstream")) { + assert(branch); + stat_tracking_info(branch, &num_ours, &num_theirs); + if (!num_ours && !num_theirs) + v->s = "="; + else if (!num_ours) + v->s = "<"; + else if (!num_theirs) + v->s = ">"; + else + v->s = "<>"; + continue; + } else die("unknown %.*s format %s", (int)(formatp - name), name, formatp); } diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 1d998f8550..d88d7acbdd 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -303,6 +303,33 @@ test_expect_success 'Check short upstream format' ' test_cmp expected actual ' +test_expect_success 'setup for upstream:track[short]' ' + test_commit two +' + +cat >expected <actual && + test_cmp expected actual +' + +cat >expected < +EOF + +test_expect_success 'Check upstream:trackshort format' ' + git for-each-ref --format="%(upstream:trackshort)" refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'Check that :track[short] cannot be used with other atoms' ' + test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null && + test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null +' + cat >expected < Date: Mon, 18 Nov 2013 23:09:12 +0530 Subject: [PATCH 5/6] for-each-ref: introduce %(color:...) for color Enhance 'git for-each-ref' with color formatting options. You can now use the following format in for-each-ref: %(color:green)%(refname:short)%(color:reset) where color names are described in color.branch.*. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- Documentation/git-for-each-ref.txt | 4 ++++ builtin/for-each-ref.c | 11 +++++++++-- t/t6300-for-each-ref.sh | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 92e82fdb02..94f5c465da 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -101,6 +101,10 @@ HEAD:: '*' if HEAD matches current ref (the checked out branch), ' ' otherwise. +color:: + Change output color. Followed by `:`, where names + are described in `color.branch.*`. + 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 9e45abd40a..bc655b5042 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -9,6 +9,7 @@ #include "quote.h" #include "parse-options.h" #include "remote.h" +#include "color.h" /* Quoting styles */ #define QUOTE_NONE 0 @@ -76,6 +77,7 @@ static struct { { "symref" }, { "flag" }, { "HEAD" }, + { "color" }, }; /* @@ -662,8 +664,13 @@ static void populate_value(struct refinfo *ref) !branch->merge[0]->dst) continue; refname = branch->merge[0]->dst; - } - else if (!strcmp(name, "flag")) { + } else if (!prefixcmp(name, "color:")) { + char color[COLOR_MAXLEN] = ""; + + color_parse(name + 6, "--format", color); + v->s = xstrdup(color); + continue; + } else if (!strcmp(name, "flag")) { char buf[256], *cp = buf; if (ref->flag & REF_ISSYMREF) cp = copy_advance(cp, ",symref"); diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index d88d7acbdd..69e3155a62 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -343,6 +343,23 @@ test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' +get_color () +{ + git config --get-color no.such.slot "$1" +} + +cat >expected <actual && + test_cmp expected actual +' + cat >expected <<\EOF heads/master tags/master From db64eb655b48ea2c635480c6cc992b0156817aeb Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Mon, 18 Nov 2013 23:09:13 +0530 Subject: [PATCH 6/6] for-each-ref: avoid color leakage To make sure that an invocation like the following doesn't leak color, $ git for-each-ref --format='%(subject)%(color:green)' auto-reset at the end of the format string when the last color token seen in the format string isn't a color-reset. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- builtin/for-each-ref.c | 28 +++++++++++++++++++++++----- t/t6300-for-each-ref.sh | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index bc655b5042..5ff51d1d32 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -93,6 +93,7 @@ static struct { static const char **used_atom; static cmp_type *used_atom_type; static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref; +static int need_color_reset_at_eol; /* * Used to parse format string and sort specifiers @@ -179,13 +180,21 @@ static const char *find_next(const char *cp) static int verify_format(const char *format) { const char *cp, *sp; + static const char color_reset[] = "color:reset"; + + need_color_reset_at_eol = 0; for (cp = format; *cp && (sp = find_next(cp)); ) { const char *ep = strchr(sp, ')'); + int at; + if (!ep) return error("malformed format string %s", sp); /* sp points at "%(" and ep points at the closing ")" */ - parse_atom(sp + 2, ep); + at = parse_atom(sp + 2, ep); cp = ep + 1; + + if (!memcmp(used_atom[at], "color:", 6)) + need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset); } return 0; } @@ -914,11 +923,9 @@ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); } -static void print_value(struct refinfo *ref, int atom, int quote_style) +static void print_value(struct atom_value *v, int quote_style) { - struct atom_value *v; struct strbuf sb = STRBUF_INIT; - get_value(ref, atom, &v); switch (quote_style) { case QUOTE_NONE: fputs(v->s, stdout); @@ -985,15 +992,26 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style) const char *cp, *sp, *ep; for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { + struct atom_value *atomv; + ep = strchr(sp, ')'); if (cp < sp) emit(cp, sp); - print_value(info, parse_atom(sp + 2, ep), quote_style); + get_value(info, parse_atom(sp + 2, ep), &atomv); + print_value(atomv, quote_style); } if (*cp) { sp = cp + strlen(cp); emit(cp, sp); } + if (need_color_reset_at_eol) { + struct atom_value resetv; + char color[COLOR_MAXLEN] = ""; + + color_parse("reset", "--format", color); + resetv.s = color; + print_value(&resetv, quote_style); + } putchar('\n'); } diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 69e3155a62..46866bab01 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -356,7 +356,7 @@ $(git rev-parse --short refs/tags/two) $(get_color green)two$(get_color reset) EOF test_expect_success 'Check %(color:...) ' ' - git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)%(color:reset)" >actual && + git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual && test_cmp expected actual '