Skip to content

Commit cb6bd57

Browse files
committed
Merge branch 'rr/for-each-ref-decoration'
Add a few formatting directives to "git for-each-ref --format=...", to paint them in color, etc. * rr/for-each-ref-decoration: for-each-ref: avoid color leakage for-each-ref: introduce %(color:...) for color for-each-ref: introduce %(upstream:track[short]) for-each-ref: introduce %(HEAD) asterisk marker t6300 (for-each-ref): don't hardcode SHA-1 hexes t6300 (for-each-ref): clearly demarcate setup
2 parents 10a3638 + db64eb6 commit cb6bd57

File tree

3 files changed

+150
-27
lines changed

3 files changed

+150
-27
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,19 @@ objectname::
9191
upstream::
9292
The name of a local ref which can be considered ``upstream''
9393
from the displayed ref. Respects `:short` in the same way as
94-
`refname` above.
94+
`refname` above. Additionally respects `:track` to show
95+
"[ahead N, behind M]" and `:trackshort` to show the terse
96+
version: ">" (ahead), "<" (behind), "<>" (ahead and behind),
97+
or "=" (in sync). Has no effect if the ref does not have
98+
tracking information associated with it.
99+
100+
HEAD::
101+
'*' if HEAD matches current ref (the checked out branch), ' '
102+
otherwise.
103+
104+
color::
105+
Change output color. Followed by `:<colorname>`, where names
106+
are described in `color.branch.*`.
95107

96108
In addition to the above, for commit and tag objects, the header
97109
field names (`tree`, `parent`, `object`, `type`, and `tag`) can

builtin/for-each-ref.c

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "quote.h"
1010
#include "parse-options.h"
1111
#include "remote.h"
12+
#include "color.h"
1213

1314
/* Quoting styles */
1415
#define QUOTE_NONE 0
@@ -75,6 +76,8 @@ static struct {
7576
{ "upstream" },
7677
{ "symref" },
7778
{ "flag" },
79+
{ "HEAD" },
80+
{ "color" },
7881
};
7982

8083
/*
@@ -90,6 +93,7 @@ static struct {
9093
static const char **used_atom;
9194
static cmp_type *used_atom_type;
9295
static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
96+
static int need_color_reset_at_eol;
9397

9498
/*
9599
* Used to parse format string and sort specifiers
@@ -176,13 +180,21 @@ static const char *find_next(const char *cp)
176180
static int verify_format(const char *format)
177181
{
178182
const char *cp, *sp;
183+
static const char color_reset[] = "color:reset";
184+
185+
need_color_reset_at_eol = 0;
179186
for (cp = format; *cp && (sp = find_next(cp)); ) {
180187
const char *ep = strchr(sp, ')');
188+
int at;
189+
181190
if (!ep)
182191
return error("malformed format string %s", sp);
183192
/* sp points at "%(" and ep points at the closing ")" */
184-
parse_atom(sp + 2, ep);
193+
at = parse_atom(sp + 2, ep);
185194
cp = ep + 1;
195+
196+
if (!memcmp(used_atom[at], "color:", 6))
197+
need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset);
186198
}
187199
return 0;
188200
}
@@ -649,6 +661,7 @@ static void populate_value(struct refinfo *ref)
649661
int deref = 0;
650662
const char *refname;
651663
const char *formatp;
664+
struct branch *branch = NULL;
652665

653666
if (*name == '*') {
654667
deref = 1;
@@ -660,7 +673,6 @@ static void populate_value(struct refinfo *ref)
660673
else if (!prefixcmp(name, "symref"))
661674
refname = ref->symref ? ref->symref : "";
662675
else if (!prefixcmp(name, "upstream")) {
663-
struct branch *branch;
664676
/* only local branches may have an upstream */
665677
if (prefixcmp(ref->refname, "refs/heads/"))
666678
continue;
@@ -670,8 +682,13 @@ static void populate_value(struct refinfo *ref)
670682
!branch->merge[0]->dst)
671683
continue;
672684
refname = branch->merge[0]->dst;
673-
}
674-
else if (!strcmp(name, "flag")) {
685+
} else if (!prefixcmp(name, "color:")) {
686+
char color[COLOR_MAXLEN] = "";
687+
688+
color_parse(name + 6, "--format", color);
689+
v->s = xstrdup(color);
690+
continue;
691+
} else if (!strcmp(name, "flag")) {
675692
char buf[256], *cp = buf;
676693
if (ref->flag & REF_ISSYMREF)
677694
cp = copy_advance(cp, ",symref");
@@ -684,20 +701,62 @@ static void populate_value(struct refinfo *ref)
684701
v->s = xstrdup(buf + 1);
685702
}
686703
continue;
687-
}
688-
else if (!deref && grab_objectname(name, ref->objectname, v))
704+
} else if (!deref && grab_objectname(name, ref->objectname, v)) {
689705
continue;
690-
else
706+
} else if (!strcmp(name, "HEAD")) {
707+
const char *head;
708+
unsigned char sha1[20];
709+
710+
head = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
711+
if (!strcmp(ref->refname, head))
712+
v->s = "*";
713+
else
714+
v->s = " ";
715+
continue;
716+
} else
691717
continue;
692718

693719
formatp = strchr(name, ':');
694-
/* look for "short" refname format */
695720
if (formatp) {
721+
int num_ours, num_theirs;
722+
696723
formatp++;
697724
if (!strcmp(formatp, "short"))
698725
refname = shorten_unambiguous_ref(refname,
699726
warn_ambiguous_refs);
700-
else
727+
else if (!strcmp(formatp, "track") &&
728+
!prefixcmp(name, "upstream")) {
729+
char buf[40];
730+
731+
stat_tracking_info(branch, &num_ours, &num_theirs);
732+
if (!num_ours && !num_theirs)
733+
v->s = "";
734+
else if (!num_ours) {
735+
sprintf(buf, "[behind %d]", num_theirs);
736+
v->s = xstrdup(buf);
737+
} else if (!num_theirs) {
738+
sprintf(buf, "[ahead %d]", num_ours);
739+
v->s = xstrdup(buf);
740+
} else {
741+
sprintf(buf, "[ahead %d, behind %d]",
742+
num_ours, num_theirs);
743+
v->s = xstrdup(buf);
744+
}
745+
continue;
746+
} else if (!strcmp(formatp, "trackshort") &&
747+
!prefixcmp(name, "upstream")) {
748+
assert(branch);
749+
stat_tracking_info(branch, &num_ours, &num_theirs);
750+
if (!num_ours && !num_theirs)
751+
v->s = "=";
752+
else if (!num_ours)
753+
v->s = "<";
754+
else if (!num_theirs)
755+
v->s = ">";
756+
else
757+
v->s = "<>";
758+
continue;
759+
} else
701760
die("unknown %.*s format %s",
702761
(int)(formatp - name), name, formatp);
703762
}
@@ -875,11 +934,9 @@ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs
875934
qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
876935
}
877936

878-
static void print_value(struct refinfo *ref, int atom, int quote_style)
937+
static void print_value(struct atom_value *v, int quote_style)
879938
{
880-
struct atom_value *v;
881939
struct strbuf sb = STRBUF_INIT;
882-
get_value(ref, atom, &v);
883940
switch (quote_style) {
884941
case QUOTE_NONE:
885942
fputs(v->s, stdout);
@@ -946,15 +1003,26 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style)
9461003
const char *cp, *sp, *ep;
9471004

9481005
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
1006+
struct atom_value *atomv;
1007+
9491008
ep = strchr(sp, ')');
9501009
if (cp < sp)
9511010
emit(cp, sp);
952-
print_value(info, parse_atom(sp + 2, ep), quote_style);
1011+
get_value(info, parse_atom(sp + 2, ep), &atomv);
1012+
print_value(atomv, quote_style);
9531013
}
9541014
if (*cp) {
9551015
sp = cp + strlen(cp);
9561016
emit(cp, sp);
9571017
}
1018+
if (need_color_reset_at_eol) {
1019+
struct atom_value resetv;
1020+
char color[COLOR_MAXLEN] = "";
1021+
1022+
color_parse("reset", "--format", color);
1023+
resetv.s = color;
1024+
print_value(&resetv, quote_style);
1025+
}
9581026
putchar('\n');
9591027
}
9601028

t/t6300-for-each-ref.sh

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ setdate_and_increment () {
1818
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
1919
}
2020

21-
test_expect_success 'Create sample commit with known timestamp' '
21+
test_expect_success setup '
2222
setdate_and_increment &&
2323
echo "Using $datestamp" > one &&
2424
git add one &&
2525
git commit -m "Initial" &&
2626
setdate_and_increment &&
27-
git tag -a -m "Tagging at $datestamp" testtag
28-
'
29-
30-
test_expect_success 'Create upstream config' '
27+
git tag -a -m "Tagging at $datestamp" testtag &&
3128
git update-ref refs/remotes/origin/master master &&
3229
git remote add origin nowhere &&
3330
git config branch.master.remote origin &&
@@ -52,8 +49,8 @@ test_atom head refname refs/heads/master
5249
test_atom head upstream refs/remotes/origin/master
5350
test_atom head objecttype commit
5451
test_atom head objectsize 171
55-
test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
56-
test_atom head tree 0e51c00fcb93dffc755546f27593d511e1bdb46f
52+
test_atom head objectname $(git rev-parse refs/heads/master)
53+
test_atom head tree $(git rev-parse refs/heads/master^{tree})
5754
test_atom head parent ''
5855
test_atom head numparent 0
5956
test_atom head object ''
@@ -82,16 +79,17 @@ test_atom head contents:body ''
8279
test_atom head contents:signature ''
8380
test_atom head contents 'Initial
8481
'
82+
test_atom head HEAD '*'
8583

8684
test_atom tag refname refs/tags/testtag
8785
test_atom tag upstream ''
8886
test_atom tag objecttype tag
8987
test_atom tag objectsize 154
90-
test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
88+
test_atom tag objectname $(git rev-parse refs/tags/testtag)
9189
test_atom tag tree ''
9290
test_atom tag parent ''
9391
test_atom tag numparent ''
94-
test_atom tag object '67a36f10722846e891fbada1ba48ed035de75581'
92+
test_atom tag object $(git rev-parse refs/tags/testtag^0)
9593
test_atom tag type 'commit'
9694
test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
9795
test_atom tag '*objecttype' 'commit'
@@ -117,6 +115,7 @@ test_atom tag contents:body ''
117115
test_atom tag contents:signature ''
118116
test_atom tag contents 'Tagging at 1151939927
119117
'
118+
test_atom tag HEAD ' '
120119

121120
test_expect_success 'Check invalid atoms names are errors' '
122121
test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
@@ -308,8 +307,35 @@ test_expect_success 'Check short upstream format' '
308307
test_cmp expected actual
309308
'
310309

310+
test_expect_success 'setup for upstream:track[short]' '
311+
test_commit two
312+
'
313+
314+
cat >expected <<EOF
315+
[ahead 1]
316+
EOF
317+
318+
test_expect_success 'Check upstream:track format' '
319+
git for-each-ref --format="%(upstream:track)" refs/heads >actual &&
320+
test_cmp expected actual
321+
'
322+
323+
cat >expected <<EOF
324+
>
325+
EOF
326+
327+
test_expect_success 'Check upstream:trackshort format' '
328+
git for-each-ref --format="%(upstream:trackshort)" refs/heads >actual &&
329+
test_cmp expected actual
330+
'
331+
332+
test_expect_success 'Check that :track[short] cannot be used with other atoms' '
333+
test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
334+
test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
335+
'
336+
311337
cat >expected <<EOF
312-
67a36f1
338+
$(git rev-parse --short HEAD)
313339
EOF
314340

315341
test_expect_success 'Check short objectname format' '
@@ -321,6 +347,23 @@ test_expect_success 'Check for invalid refname format' '
321347
test_must_fail git for-each-ref --format="%(refname:INVALID)"
322348
'
323349

350+
get_color ()
351+
{
352+
git config --get-color no.such.slot "$1"
353+
}
354+
355+
cat >expected <<EOF
356+
$(git rev-parse --short refs/heads/master) $(get_color green)master$(get_color reset)
357+
$(git rev-parse --short refs/remotes/origin/master) $(get_color green)origin/master$(get_color reset)
358+
$(git rev-parse --short refs/tags/testtag) $(get_color green)testtag$(get_color reset)
359+
$(git rev-parse --short refs/tags/two) $(get_color green)two$(get_color reset)
360+
EOF
361+
362+
test_expect_success 'Check %(color:...) ' '
363+
git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual &&
364+
test_cmp expected actual
365+
'
366+
324367
cat >expected <<\EOF
325368
heads/master
326369
tags/master
@@ -460,9 +503,9 @@ test_atom refs/tags/signed-long contents "subject line
460503
body contents
461504
$sig"
462505

463-
cat >expected <<\EOF
464-
408fe76d02a785a006c2e9c669b7be5589ede96d <[email protected]> refs/tags/master
465-
90b5ebede4899eda64893bc2a4c8f1d6fb6dfc40 <[email protected]> refs/tags/bogo
506+
cat >expected <<EOF
507+
$(git rev-parse refs/tags/master) <[email protected]> refs/tags/master
508+
$(git rev-parse refs/tags/bogo) <[email protected]> refs/tags/bogo
466509
EOF
467510

468511
test_expect_success 'Verify sort with multiple keys' '

0 commit comments

Comments
 (0)