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
This commit is contained in:
commit
cb6bd5722f
@ -91,7 +91,19 @@ objectname::
|
|||||||
upstream::
|
upstream::
|
||||||
The name of a local ref which can be considered ``upstream''
|
The name of a local ref which can be considered ``upstream''
|
||||||
from the displayed ref. Respects `:short` in the same way as
|
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), ' '
|
||||||
|
otherwise.
|
||||||
|
|
||||||
|
color::
|
||||||
|
Change output color. Followed by `:<colorname>`, where names
|
||||||
|
are described in `color.branch.*`.
|
||||||
|
|
||||||
In addition to the above, for commit and tag objects, the header
|
In addition to the above, for commit and tag objects, the header
|
||||||
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
|
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
|
#include "color.h"
|
||||||
|
|
||||||
/* Quoting styles */
|
/* Quoting styles */
|
||||||
#define QUOTE_NONE 0
|
#define QUOTE_NONE 0
|
||||||
@ -75,6 +76,8 @@ static struct {
|
|||||||
{ "upstream" },
|
{ "upstream" },
|
||||||
{ "symref" },
|
{ "symref" },
|
||||||
{ "flag" },
|
{ "flag" },
|
||||||
|
{ "HEAD" },
|
||||||
|
{ "color" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -90,6 +93,7 @@ static struct {
|
|||||||
static const char **used_atom;
|
static const char **used_atom;
|
||||||
static cmp_type *used_atom_type;
|
static cmp_type *used_atom_type;
|
||||||
static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
|
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
|
* Used to parse format string and sort specifiers
|
||||||
@ -176,13 +180,21 @@ static const char *find_next(const char *cp)
|
|||||||
static int verify_format(const char *format)
|
static int verify_format(const char *format)
|
||||||
{
|
{
|
||||||
const char *cp, *sp;
|
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)); ) {
|
for (cp = format; *cp && (sp = find_next(cp)); ) {
|
||||||
const char *ep = strchr(sp, ')');
|
const char *ep = strchr(sp, ')');
|
||||||
|
int at;
|
||||||
|
|
||||||
if (!ep)
|
if (!ep)
|
||||||
return error("malformed format string %s", sp);
|
return error("malformed format string %s", sp);
|
||||||
/* sp points at "%(" and ep points at the closing ")" */
|
/* sp points at "%(" and ep points at the closing ")" */
|
||||||
parse_atom(sp + 2, ep);
|
at = parse_atom(sp + 2, ep);
|
||||||
cp = ep + 1;
|
cp = ep + 1;
|
||||||
|
|
||||||
|
if (!memcmp(used_atom[at], "color:", 6))
|
||||||
|
need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -649,6 +661,7 @@ static void populate_value(struct refinfo *ref)
|
|||||||
int deref = 0;
|
int deref = 0;
|
||||||
const char *refname;
|
const char *refname;
|
||||||
const char *formatp;
|
const char *formatp;
|
||||||
|
struct branch *branch = NULL;
|
||||||
|
|
||||||
if (*name == '*') {
|
if (*name == '*') {
|
||||||
deref = 1;
|
deref = 1;
|
||||||
@ -660,7 +673,6 @@ static void populate_value(struct refinfo *ref)
|
|||||||
else if (!prefixcmp(name, "symref"))
|
else if (!prefixcmp(name, "symref"))
|
||||||
refname = ref->symref ? ref->symref : "";
|
refname = ref->symref ? ref->symref : "";
|
||||||
else if (!prefixcmp(name, "upstream")) {
|
else if (!prefixcmp(name, "upstream")) {
|
||||||
struct branch *branch;
|
|
||||||
/* only local branches may have an upstream */
|
/* only local branches may have an upstream */
|
||||||
if (prefixcmp(ref->refname, "refs/heads/"))
|
if (prefixcmp(ref->refname, "refs/heads/"))
|
||||||
continue;
|
continue;
|
||||||
@ -670,8 +682,13 @@ static void populate_value(struct refinfo *ref)
|
|||||||
!branch->merge[0]->dst)
|
!branch->merge[0]->dst)
|
||||||
continue;
|
continue;
|
||||||
refname = branch->merge[0]->dst;
|
refname = branch->merge[0]->dst;
|
||||||
}
|
} else if (!prefixcmp(name, "color:")) {
|
||||||
else if (!strcmp(name, "flag")) {
|
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;
|
char buf[256], *cp = buf;
|
||||||
if (ref->flag & REF_ISSYMREF)
|
if (ref->flag & REF_ISSYMREF)
|
||||||
cp = copy_advance(cp, ",symref");
|
cp = copy_advance(cp, ",symref");
|
||||||
@ -684,20 +701,62 @@ static void populate_value(struct refinfo *ref)
|
|||||||
v->s = xstrdup(buf + 1);
|
v->s = xstrdup(buf + 1);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (!deref && grab_objectname(name, ref->objectname, v)) {
|
||||||
else if (!deref && grab_objectname(name, ref->objectname, v))
|
|
||||||
continue;
|
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;
|
continue;
|
||||||
|
|
||||||
formatp = strchr(name, ':');
|
formatp = strchr(name, ':');
|
||||||
/* look for "short" refname format */
|
|
||||||
if (formatp) {
|
if (formatp) {
|
||||||
|
int num_ours, num_theirs;
|
||||||
|
|
||||||
formatp++;
|
formatp++;
|
||||||
if (!strcmp(formatp, "short"))
|
if (!strcmp(formatp, "short"))
|
||||||
refname = shorten_unambiguous_ref(refname,
|
refname = shorten_unambiguous_ref(refname,
|
||||||
warn_ambiguous_refs);
|
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",
|
die("unknown %.*s format %s",
|
||||||
(int)(formatp - name), name, formatp);
|
(int)(formatp - name), name, formatp);
|
||||||
}
|
}
|
||||||
@ -875,11 +934,9 @@ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs
|
|||||||
qsort(refs, num_refs, sizeof(struct refinfo *), compare_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;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
get_value(ref, atom, &v);
|
|
||||||
switch (quote_style) {
|
switch (quote_style) {
|
||||||
case QUOTE_NONE:
|
case QUOTE_NONE:
|
||||||
fputs(v->s, stdout);
|
fputs(v->s, stdout);
|
||||||
@ -946,15 +1003,26 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style)
|
|||||||
const char *cp, *sp, *ep;
|
const char *cp, *sp, *ep;
|
||||||
|
|
||||||
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
|
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
|
||||||
|
struct atom_value *atomv;
|
||||||
|
|
||||||
ep = strchr(sp, ')');
|
ep = strchr(sp, ')');
|
||||||
if (cp < sp)
|
if (cp < sp)
|
||||||
emit(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) {
|
if (*cp) {
|
||||||
sp = cp + strlen(cp);
|
sp = cp + strlen(cp);
|
||||||
emit(cp, sp);
|
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');
|
putchar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,16 +18,13 @@ setdate_and_increment () {
|
|||||||
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
|
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
|
||||||
}
|
}
|
||||||
|
|
||||||
test_expect_success 'Create sample commit with known timestamp' '
|
test_expect_success setup '
|
||||||
setdate_and_increment &&
|
setdate_and_increment &&
|
||||||
echo "Using $datestamp" > one &&
|
echo "Using $datestamp" > one &&
|
||||||
git add one &&
|
git add one &&
|
||||||
git commit -m "Initial" &&
|
git commit -m "Initial" &&
|
||||||
setdate_and_increment &&
|
setdate_and_increment &&
|
||||||
git tag -a -m "Tagging at $datestamp" testtag
|
git tag -a -m "Tagging at $datestamp" testtag &&
|
||||||
'
|
|
||||||
|
|
||||||
test_expect_success 'Create upstream config' '
|
|
||||||
git update-ref refs/remotes/origin/master master &&
|
git update-ref refs/remotes/origin/master master &&
|
||||||
git remote add origin nowhere &&
|
git remote add origin nowhere &&
|
||||||
git config branch.master.remote origin &&
|
git config branch.master.remote origin &&
|
||||||
@ -52,8 +49,8 @@ test_atom head refname refs/heads/master
|
|||||||
test_atom head upstream refs/remotes/origin/master
|
test_atom head upstream refs/remotes/origin/master
|
||||||
test_atom head objecttype commit
|
test_atom head objecttype commit
|
||||||
test_atom head objectsize 171
|
test_atom head objectsize 171
|
||||||
test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
|
test_atom head objectname $(git rev-parse refs/heads/master)
|
||||||
test_atom head tree 0e51c00fcb93dffc755546f27593d511e1bdb46f
|
test_atom head tree $(git rev-parse refs/heads/master^{tree})
|
||||||
test_atom head parent ''
|
test_atom head parent ''
|
||||||
test_atom head numparent 0
|
test_atom head numparent 0
|
||||||
test_atom head object ''
|
test_atom head object ''
|
||||||
@ -82,16 +79,17 @@ test_atom head contents:body ''
|
|||||||
test_atom head contents:signature ''
|
test_atom head contents:signature ''
|
||||||
test_atom head contents 'Initial
|
test_atom head contents 'Initial
|
||||||
'
|
'
|
||||||
|
test_atom head HEAD '*'
|
||||||
|
|
||||||
test_atom tag refname refs/tags/testtag
|
test_atom tag refname refs/tags/testtag
|
||||||
test_atom tag upstream ''
|
test_atom tag upstream ''
|
||||||
test_atom tag objecttype tag
|
test_atom tag objecttype tag
|
||||||
test_atom tag objectsize 154
|
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 tree ''
|
||||||
test_atom tag parent ''
|
test_atom tag parent ''
|
||||||
test_atom tag numparent ''
|
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 type 'commit'
|
||||||
test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
|
test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
|
||||||
test_atom tag '*objecttype' 'commit'
|
test_atom tag '*objecttype' 'commit'
|
||||||
@ -117,6 +115,7 @@ test_atom tag contents:body ''
|
|||||||
test_atom tag contents:signature ''
|
test_atom tag contents:signature ''
|
||||||
test_atom tag contents 'Tagging at 1151939927
|
test_atom tag contents 'Tagging at 1151939927
|
||||||
'
|
'
|
||||||
|
test_atom tag HEAD ' '
|
||||||
|
|
||||||
test_expect_success 'Check invalid atoms names are errors' '
|
test_expect_success 'Check invalid atoms names are errors' '
|
||||||
test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
|
test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
|
||||||
@ -308,8 +307,35 @@ test_expect_success 'Check short upstream format' '
|
|||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup for upstream:track[short]' '
|
||||||
|
test_commit two
|
||||||
|
'
|
||||||
|
|
||||||
cat >expected <<EOF
|
cat >expected <<EOF
|
||||||
67a36f1
|
[ahead 1]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'Check upstream:track format' '
|
||||||
|
git for-each-ref --format="%(upstream:track)" refs/heads >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expected <<EOF
|
||||||
|
>
|
||||||
|
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 <<EOF
|
||||||
|
$(git rev-parse --short HEAD)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'Check short objectname format' '
|
test_expect_success 'Check short objectname format' '
|
||||||
@ -321,6 +347,23 @@ test_expect_success 'Check for invalid refname format' '
|
|||||||
test_must_fail git for-each-ref --format="%(refname:INVALID)"
|
test_must_fail git for-each-ref --format="%(refname:INVALID)"
|
||||||
'
|
'
|
||||||
|
|
||||||
|
get_color ()
|
||||||
|
{
|
||||||
|
git config --get-color no.such.slot "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
cat >expected <<EOF
|
||||||
|
$(git rev-parse --short refs/heads/master) $(get_color green)master$(get_color reset)
|
||||||
|
$(git rev-parse --short refs/remotes/origin/master) $(get_color green)origin/master$(get_color reset)
|
||||||
|
$(git rev-parse --short refs/tags/testtag) $(get_color green)testtag$(get_color reset)
|
||||||
|
$(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)" >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
cat >expected <<\EOF
|
cat >expected <<\EOF
|
||||||
heads/master
|
heads/master
|
||||||
tags/master
|
tags/master
|
||||||
@ -460,9 +503,9 @@ test_atom refs/tags/signed-long contents "subject line
|
|||||||
body contents
|
body contents
|
||||||
$sig"
|
$sig"
|
||||||
|
|
||||||
cat >expected <<\EOF
|
cat >expected <<EOF
|
||||||
408fe76d02a785a006c2e9c669b7be5589ede96d <committer@example.com> refs/tags/master
|
$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
|
||||||
90b5ebede4899eda64893bc2a4c8f1d6fb6dfc40 <committer@example.com> refs/tags/bogo
|
$(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'Verify sort with multiple keys' '
|
test_expect_success 'Verify sort with multiple keys' '
|
||||||
|
Loading…
Reference in New Issue
Block a user