Merge branch 'jk/ref-filter-colors' into maint

"%C(color name)" in the pretty print format always produced ANSI
color escape codes, which was an early design mistake.  They now
honor the configuration (e.g. "color.ui = never") and also tty-ness
of the output medium.

* jk/ref-filter-colors:
  ref-filter: consult want_color() before emitting colors
  pretty: respect color settings for %C placeholders
  rev-list: pass diffopt->use_colors through to pretty-print
  for-each-ref: load config earlier
  color: check color.ui in git_default_config()
  ref-filter: pass ref_format struct to atom parsers
  ref-filter: factor out the parsing of sorting atoms
  ref-filter: make parse_ref_filter_atom a private function
  ref-filter: provide a function for parsing sort options
  ref-filter: move need_color_reset_at_eol into ref_format
  ref-filter: abstract ref format into its own struct
  ref-filter: simplify automatic color reset
  t: use test_decode_color rather than literal ANSI codes
  docs/for-each-ref: update pointer to color syntax
  check return value of verify_ref_format()
This commit is contained in:
Junio C Hamano 2017-08-23 14:33:42 -07:00
commit 447f80f508
22 changed files with 366 additions and 224 deletions

View File

@ -156,8 +156,10 @@ HEAD::
otherwise.
color::
Change output color. Followed by `:<colorname>`, where names
are described in `color.branch.*`.
Change output color. Followed by `:<colorname>`, where color
names are described under Values in the "CONFIGURATION FILE"
section of linkgit:git-config[1]. For example,
`%(color:bold red)`.
align::
Left-, middle-, or right-align the content between

View File

@ -173,13 +173,17 @@ endif::git-rev-list[]
- '%Cblue': switch color to blue
- '%Creset': reset color
- '%C(...)': color specification, as described under Values in the
"CONFIGURATION FILE" section of linkgit:git-config[1];
adding `auto,` at the beginning (e.g. `%C(auto,red)`) will emit
color only when colors are enabled for log output (by `color.diff`,
`color.ui`, or `--color`, and respecting the `auto` settings of the
former if we are going to a terminal). `auto` alone (i.e.
`%C(auto)`) will turn on auto coloring on the next placeholders
until the color is switched again.
"CONFIGURATION FILE" section of linkgit:git-config[1].
By default, colors are shown only when enabled for log output (by
`color.diff`, `color.ui`, or `--color`, and respecting the `auto`
settings of the former if we are going to a terminal). `%C(auto,...)`
is accepted as a historical synonym for the default (e.g.,
`%C(auto,red)`). Specifying `%C(always,...) will show the colors
even when color is not otherwise enabled (though consider
just using `--color=always` to enable color for the whole output,
including this format and anything else git might color). `auto`
alone (i.e. `%C(auto)`) will turn on auto coloring on the next
placeholders until the color is switched again.
- '%m': left (`<`), right (`>`) or boundary (`-`) mark
- '%n': newline
- '%%': a raw '%'

View File

@ -92,7 +92,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
return git_color_default_config(var, value, cb);
return git_default_config(var, value, cb);
}
static const char *branch_get_color(enum color_branch ix)
@ -383,7 +383,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
return strbuf_detach(&fmt, NULL);
}
static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format)
{
int i;
struct ref_array array;
@ -407,14 +407,17 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
if (!format)
format = to_free = build_format(filter, maxwidth, remote_prefix);
verify_ref_format(format);
if (!format->format)
format->format = to_free = build_format(filter, maxwidth, remote_prefix);
format->use_color = branch_use_color;
if (verify_ref_format(format))
die(_("unable to parse format string"));
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++) {
format_ref_array_item(array.items[i], format, 0, &out);
format_ref_array_item(array.items[i], format, &out);
if (column_active(colopts)) {
assert(!filter->verbose && "--column and --verbose are incompatible");
/* format to a string_list to let print_columns() do its job */
@ -549,7 +552,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
struct ref_filter filter;
int icase = 0;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
const char *format = NULL;
struct ref_format format = REF_FORMAT_INIT;
struct option options[] = {
OPT_GROUP(N_("Generic options")),
@ -593,7 +596,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
N_("print only branches of the object"), 0, parse_opt_object_name
},
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
@ -667,7 +670,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!sorting)
sorting = ref_default_sorting();
sorting->ignore_case = icase;
print_ref_list(&filter, sorting, format);
print_ref_list(&filter, sorting, &format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
return 0;

View File

@ -125,8 +125,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)
return 0;
}
/* inspect the color.ui config variable and others */
return git_color_default_config(var, value, cb);
return git_default_config(var, value, cb);
}
static const char *clean_get_color(enum color_clean ix)

View File

@ -17,25 +17,25 @@ static char const * const for_each_ref_usage[] = {
int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
{
int i;
const char *format = "%(objectname) %(objecttype)\t%(refname)";
struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
int maxcount = 0, quote_style = 0, icase = 0;
int maxcount = 0, icase = 0;
struct ref_array array;
struct ref_filter filter;
struct ref_format format = REF_FORMAT_INIT;
struct option opts[] = {
OPT_BIT('s', "shell", &quote_style,
OPT_BIT('s', "shell", &format.quote_style,
N_("quote placeholders suitably for shells"), QUOTE_SHELL),
OPT_BIT('p', "perl", &quote_style,
OPT_BIT('p', "perl", &format.quote_style,
N_("quote placeholders suitably for perl"), QUOTE_PERL),
OPT_BIT(0 , "python", &quote_style,
OPT_BIT(0 , "python", &format.quote_style,
N_("quote placeholders suitably for python"), QUOTE_PYTHON),
OPT_BIT(0 , "tcl", &quote_style,
OPT_BIT(0 , "tcl", &format.quote_style,
N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
OPT_GROUP(""),
OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
N_("field name to sort on"), &parse_opt_ref_sorting),
OPT_CALLBACK(0, "points-at", &filter.points_at,
@ -52,16 +52,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
memset(&array, 0, sizeof(array));
memset(&filter, 0, sizeof(filter));
format.format = "%(objectname) %(objecttype)\t%(refname)";
git_config(git_default_config, NULL);
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
if (maxcount < 0) {
error("invalid --count argument: `%d'", maxcount);
usage_with_options(for_each_ref_usage, opts);
}
if (HAS_MULTI_BITS(quote_style)) {
if (HAS_MULTI_BITS(format.quote_style)) {
error("more than one quoting style?");
usage_with_options(for_each_ref_usage, opts);
}
if (verify_ref_format(format))
if (verify_ref_format(&format))
usage_with_options(for_each_ref_usage, opts);
if (!sorting)
@ -69,9 +73,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
sorting->ignore_case = icase;
filter.ignore_case = icase;
/* for warn_ambiguous_refs */
git_config(git_default_config, NULL);
filter.name_patterns = argv;
filter.match_as_path = 1;
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
@ -80,7 +81,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (!maxcount || array.nr < maxcount)
maxcount = array.nr;
for (i = 0; i < maxcount; i++)
show_ref_array_item(array.items[i], format, quote_style);
show_ref_array_item(array.items[i], &format);
ref_array_clear(&array);
return 0;
}

View File

@ -284,7 +284,7 @@ static int wait_all(void)
static int grep_cmd_config(const char *var, const char *value, void *cb)
{
int st = grep_config(var, value, cb);
if (git_color_default_config(var, value, cb) < 0)
if (git_default_config(var, value, cb) < 0)
st = -1;
if (!strcmp(var, "grep.threads")) {

View File

@ -122,6 +122,7 @@ static void show_commit(struct commit *commit, void *data)
ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
ctx.output_encoding = get_log_output_encoding();
ctx.color = revs->diffopt.use_color;
pretty_print_commit(&ctx, commit, &buf);
if (buf.len) {
if (revs->commit_format != CMIT_FMT_ONELINE)

View File

@ -554,7 +554,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
return 0;
}
return git_color_default_config(var, value, cb);
return git_default_config(var, value, cb);
}
static int omit_in_dense(struct commit *commit, struct commit **rev, int n)

View File

@ -32,7 +32,8 @@ static const char * const git_tag_usage[] = {
static unsigned int colopts;
static int force_sign_annotate;
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format)
{
struct ref_array array;
char *to_free = NULL;
@ -43,23 +44,24 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
if (filter->lines == -1)
filter->lines = 0;
if (!format) {
if (!format->format) {
if (filter->lines) {
to_free = xstrfmt("%s %%(contents:lines=%d)",
"%(align:15)%(refname:lstrip=2)%(end)",
filter->lines);
format = to_free;
format->format = to_free;
} else
format = "%(refname:lstrip=2)";
format->format = "%(refname:lstrip=2)";
}
verify_ref_format(format);
if (verify_ref_format(format))
die(_("unable to parse format string"));
filter->with_commit_tag_algo = 1;
filter_refs(&array, filter, FILTER_REFS_TAGS);
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++)
show_ref_array_item(array.items[i], format, 0);
show_ref_array_item(array.items[i], format);
ref_array_clear(&array);
free(to_free);
@ -105,17 +107,17 @@ static int verify_tag(const char *name, const char *ref,
const struct object_id *oid, const void *cb_data)
{
int flags;
const char *fmt_pretty = cb_data;
const struct ref_format *format = cb_data;
flags = GPG_VERIFY_VERBOSE;
if (fmt_pretty)
if (format->format)
flags = GPG_VERIFY_OMIT_STATUS;
if (gpg_verify_tag(oid->hash, name, flags))
return -1;
if (fmt_pretty)
pretty_print_ref(name, oid->hash, fmt_pretty);
if (format->format)
pretty_print_ref(name, oid->hash, format);
return 0;
}
@ -134,30 +136,6 @@ static const char tag_template_nocleanup[] =
"Lines starting with '%c' will be kept; you may remove them"
" yourself if you want to.\n");
/* Parse arg given and add it the ref_sorting array */
static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
{
struct ref_sorting *s;
int len;
s = xcalloc(1, sizeof(*s));
s->next = *sorting_tail;
*sorting_tail = s;
if (*arg == '-') {
s->reverse = 1;
arg++;
}
if (skip_prefix(arg, "version:", &arg) ||
skip_prefix(arg, "v:", &arg))
s->version = 1;
len = strlen(arg);
s->atom = parse_ref_filter_atom(arg, arg+len);
return 0;
}
static int git_tag_config(const char *var, const char *value, void *cb)
{
int status;
@ -166,7 +144,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "tag.sort")) {
if (!value)
return config_error_nonbool(var);
parse_sorting_string(value, sorting_tail);
parse_ref_sorting(sorting_tail, value);
return 0;
}
@ -392,7 +370,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct strbuf err = STRBUF_INIT;
struct ref_filter filter;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
const char *format = NULL;
struct ref_format format = REF_FORMAT_INIT;
int icase = 0;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
@ -431,7 +409,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
parse_opt_object_name, (intptr_t) "HEAD"
},
OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
OPT_STRING( 0 , "format", &format.format, N_("format"),
N_("format to use for the output")),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END()
};
@ -483,7 +462,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
run_column_filter(colopts, &copts);
}
filter.name_patterns = argv;
ret = list_tags(&filter, sorting, format);
ret = list_tags(&filter, sorting, &format);
if (column_active(colopts))
stop_column_filter();
return ret;
@ -501,9 +480,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag, NULL);
if (cmdmode == 'v') {
if (format)
verify_ref_format(format);
return for_each_tag_name(argv, verify_tag, format);
if (format.format && verify_ref_format(&format))
usage_with_options(git_tag_usage, options);
return for_each_tag_name(argv, verify_tag, &format);
}
if (msg.given || msgfile) {

View File

@ -32,11 +32,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
{
int i = 1, verbose = 0, had_error = 0;
unsigned flags = 0;
char *fmt_pretty = NULL;
struct ref_format format = REF_FORMAT_INIT;
const struct option verify_tag_options[] = {
OPT__VERBOSE(&verbose, N_("print tag contents")),
OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")),
OPT_STRING(0, "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END()
};
@ -50,8 +50,10 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
if (verbose)
flags |= GPG_VERIFY_VERBOSE;
if (fmt_pretty) {
verify_ref_format(fmt_pretty);
if (format.format) {
if (verify_ref_format(&format))
usage_with_options(verify_tag_usage,
verify_tag_options);
flags |= GPG_VERIFY_OMIT_STATUS;
}
@ -68,8 +70,8 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
continue;
}
if (fmt_pretty)
pretty_print_ref(name, sha1, fmt_pretty);
if (format.format)
pretty_print_ref(name, sha1, &format);
}
return had_error;
}

View File

@ -361,14 +361,6 @@ int git_color_config(const char *var, const char *value, void *cb)
return 0;
}
int git_color_default_config(const char *var, const char *value, void *cb)
{
if (git_color_config(var, value, cb) < 0)
return -1;
return git_default_config(var, value, cb);
}
void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)
{
if (*color)

View File

@ -16,6 +16,7 @@
#include "string-list.h"
#include "utf8.h"
#include "dir.h"
#include "color.h"
struct config_source {
struct config_source *prev;
@ -1350,6 +1351,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
if (starts_with(var, "advice."))
return git_default_advice_config(var, value);
if (git_color_config(var, value, dummy) < 0)
return -1;
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;

3
diff.c
View File

@ -299,9 +299,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
return 0;
}
if (git_color_config(var, value, cb) < 0)
return -1;
return git_diff_basic_config(var, value, cb);
}

View File

@ -947,6 +947,7 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
struct format_commit_context *c)
{
const char *rest = placeholder;
const char *basic_color = NULL;
if (placeholder[1] == '(') {
const char *begin = placeholder + 2;
@ -955,23 +956,41 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
if (!end)
return 0;
if (skip_prefix(begin, "auto,", &begin)) {
if (!want_color(c->pretty_ctx->color))
return end - placeholder + 1;
} else if (skip_prefix(begin, "always,", &begin)) {
/* nothing to do; we do not respect want_color at all */
} else {
/* the default is the same as "auto" */
if (!want_color(c->pretty_ctx->color))
return end - placeholder + 1;
}
if (color_parse_mem(begin, end - begin, color) < 0)
die(_("unable to parse --pretty format"));
strbuf_addstr(sb, color);
return end - placeholder + 1;
}
/*
* We handle things like "%C(red)" above; for historical reasons, there
* are a few colors that can be specified without parentheses (and
* they cannot support things like "auto" or "always" at all).
*/
if (skip_prefix(placeholder + 1, "red", &rest))
strbuf_addstr(sb, GIT_COLOR_RED);
basic_color = GIT_COLOR_RED;
else if (skip_prefix(placeholder + 1, "green", &rest))
strbuf_addstr(sb, GIT_COLOR_GREEN);
basic_color = GIT_COLOR_GREEN;
else if (skip_prefix(placeholder + 1, "blue", &rest))
strbuf_addstr(sb, GIT_COLOR_BLUE);
basic_color = GIT_COLOR_BLUE;
else if (skip_prefix(placeholder + 1, "reset", &rest))
strbuf_addstr(sb, GIT_COLOR_RESET);
basic_color = GIT_COLOR_RESET;
if (basic_color && want_color(c->pretty_ctx->color))
strbuf_addstr(sb, basic_color);
return rest - placeholder;
}

View File

@ -97,14 +97,19 @@ static struct used_atom {
} u;
} *used_atom;
static int used_atom_cnt, need_tagged, need_symref;
static int need_color_reset_at_eol;
static void color_atom_parser(struct used_atom *atom, const char *color_value)
static void color_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *color_value)
{
if (!color_value)
die(_("expected format: %%(color:<color>)"));
if (color_parse(color_value, atom->u.color) < 0)
die(_("unrecognized color: %%(color:%s)"), color_value);
/*
* We check this after we've parsed the color, which lets us complain
* about syntactically bogus color names even if they won't be used.
*/
if (!want_color(format->use_color))
color_parse("", atom->u.color);
}
static void refname_atom_parser_internal(struct refname_atom *atom,
@ -127,7 +132,7 @@ static void refname_atom_parser_internal(struct refname_atom *atom,
die(_("unrecognized %%(%s) argument: %s"), name, arg);
}
static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
static void remote_ref_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
struct string_list params = STRING_LIST_INIT_DUP;
int i;
@ -161,28 +166,28 @@ static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
string_list_clear(&params, 0);
}
static void body_atom_parser(struct used_atom *atom, const char *arg)
static void body_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (arg)
die(_("%%(body) does not take arguments"));
atom->u.contents.option = C_BODY_DEP;
}
static void subject_atom_parser(struct used_atom *atom, const char *arg)
static void subject_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (arg)
die(_("%%(subject) does not take arguments"));
atom->u.contents.option = C_SUB;
}
static void trailers_atom_parser(struct used_atom *atom, const char *arg)
static void trailers_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (arg)
die(_("%%(trailers) does not take arguments"));
atom->u.contents.option = C_TRAILERS;
}
static void contents_atom_parser(struct used_atom *atom, const char *arg)
static void contents_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (!arg)
atom->u.contents.option = C_BARE;
@ -202,7 +207,7 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg)
die(_("unrecognized %%(contents) argument: %s"), arg);
}
static void objectname_atom_parser(struct used_atom *atom, const char *arg)
static void objectname_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (!arg)
atom->u.objectname.option = O_FULL;
@ -219,7 +224,7 @@ static void objectname_atom_parser(struct used_atom *atom, const char *arg)
die(_("unrecognized %%(objectname) argument: %s"), arg);
}
static void refname_atom_parser(struct used_atom *atom, const char *arg)
static void refname_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
}
@ -235,7 +240,7 @@ static align_type parse_align_position(const char *s)
return -1;
}
static void align_atom_parser(struct used_atom *atom, const char *arg)
static void align_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
struct align *align = &atom->u.align;
struct string_list params = STRING_LIST_INIT_DUP;
@ -274,7 +279,7 @@ static void align_atom_parser(struct used_atom *atom, const char *arg)
string_list_clear(&params, 0);
}
static void if_atom_parser(struct used_atom *atom, const char *arg)
static void if_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
if (!arg) {
atom->u.if_then_else.cmp_status = COMPARE_NONE;
@ -288,7 +293,7 @@ static void if_atom_parser(struct used_atom *atom, const char *arg)
}
}
static void head_atom_parser(struct used_atom *atom, const char *arg)
static void head_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg)
{
struct object_id unused;
@ -298,7 +303,7 @@ static void head_atom_parser(struct used_atom *atom, const char *arg)
static struct {
const char *name;
cmp_type cmp_type;
void (*parser)(struct used_atom *atom, const char *arg);
void (*parser)(const struct ref_format *format, struct used_atom *atom, const char *arg);
} valid_atom[] = {
{ "refname" , FIELD_STR, refname_atom_parser },
{ "objecttype" },
@ -365,7 +370,8 @@ struct atom_value {
/*
* Used to parse format string and sort specifiers
*/
int parse_ref_filter_atom(const char *atom, const char *ep)
static int parse_ref_filter_atom(const struct ref_format *format,
const char *atom, const char *ep)
{
const char *sp;
const char *arg;
@ -413,7 +419,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
arg = used_atom[at].name + (arg - atom) + 1;
memset(&used_atom[at].u, 0, sizeof(used_atom[at].u));
if (valid_atom[i].parser)
valid_atom[i].parser(&used_atom[at], arg);
valid_atom[i].parser(format, &used_atom[at], arg);
if (*atom == '*')
need_tagged = 1;
if (!strcmp(valid_atom[i].name, "symref"))
@ -657,24 +663,26 @@ static const char *find_next(const char *cp)
* Make sure the format string is well formed, and parse out
* the used atoms.
*/
int verify_ref_format(const char *format)
int verify_ref_format(struct ref_format *format)
{
const char *cp, *sp;
need_color_reset_at_eol = 0;
for (cp = format; *cp && (sp = find_next(cp)); ) {
format->need_color_reset_at_eol = 0;
for (cp = format->format; *cp && (sp = find_next(cp)); ) {
const char *color, *ep = strchr(sp, ')');
int at;
if (!ep)
return error(_("malformed format string %s"), sp);
/* sp points at "%(" and ep points at the closing ")" */
at = parse_ref_filter_atom(sp + 2, ep);
at = parse_ref_filter_atom(format, sp + 2, ep);
cp = ep + 1;
if (skip_prefix(used_atom[at].name, "color:", &color))
need_color_reset_at_eol = !!strcmp(color, "reset");
format->need_color_reset_at_eol = !!strcmp(color, "reset");
}
if (format->need_color_reset_at_eol && !want_color(format->use_color))
format->need_color_reset_at_eol = 0;
return 0;
}
@ -2060,35 +2068,34 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
}
}
void format_ref_array_item(struct ref_array_item *info, const char *format,
int quote_style, struct strbuf *final_buf)
void format_ref_array_item(struct ref_array_item *info,
const struct ref_format *format,
struct strbuf *final_buf)
{
const char *cp, *sp, *ep;
struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
state.quote_style = quote_style;
state.quote_style = format->quote_style;
push_stack_element(&state.stack);
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
for (cp = format->format; *cp && (sp = find_next(cp)); cp = ep + 1) {
struct atom_value *atomv;
ep = strchr(sp, ')');
if (cp < sp)
append_literal(cp, sp, &state);
get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
get_ref_atom_value(info,
parse_ref_filter_atom(format, sp + 2, ep),
&atomv);
atomv->handler(atomv, &state);
}
if (*cp) {
sp = cp + strlen(cp);
append_literal(cp, sp, &state);
}
if (need_color_reset_at_eol) {
if (format->need_color_reset_at_eol) {
struct atom_value resetv;
char color[COLOR_MAXLEN] = "";
if (color_parse("reset", color) < 0)
die("BUG: couldn't parse 'reset' as a color");
resetv.s = color;
resetv.s = GIT_COLOR_RESET;
append_atom(&resetv, &state);
}
if (state.stack->prev)
@ -2097,26 +2104,38 @@ void format_ref_array_item(struct ref_array_item *info, const char *format,
pop_stack_element(&state.stack);
}
void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
void show_ref_array_item(struct ref_array_item *info,
const struct ref_format *format)
{
struct strbuf final_buf = STRBUF_INIT;
format_ref_array_item(info, format, quote_style, &final_buf);
format_ref_array_item(info, format, &final_buf);
fwrite(final_buf.buf, 1, final_buf.len, stdout);
strbuf_release(&final_buf);
putchar('\n');
}
void pretty_print_ref(const char *name, const unsigned char *sha1,
const char *format)
const struct ref_format *format)
{
struct ref_array_item *ref_item;
ref_item = new_ref_array_item(name, sha1, 0);
ref_item->kind = ref_kind_from_refname(name);
show_ref_array_item(ref_item, format, 0);
show_ref_array_item(ref_item, format);
free_array_item(ref_item);
}
static int parse_sorting_atom(const char *atom)
{
/*
* This parses an atom using a dummy ref_format, since we don't
* actually care about the formatting details.
*/
struct ref_format dummy = REF_FORMAT_INIT;
const char *end = atom + strlen(atom);
return parse_ref_filter_atom(&dummy, atom, end);
}
/* If no sorting option is given, use refname to sort as default */
struct ref_sorting *ref_default_sorting(void)
{
@ -2125,18 +2144,13 @@ struct ref_sorting *ref_default_sorting(void)
struct ref_sorting *sorting = xcalloc(1, sizeof(*sorting));
sorting->next = NULL;
sorting->atom = parse_ref_filter_atom(cstr_name, cstr_name + strlen(cstr_name));
sorting->atom = parse_sorting_atom(cstr_name);
return sorting;
}
int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg)
{
struct ref_sorting **sorting_tail = opt->value;
struct ref_sorting *s;
int len;
if (!arg) /* should --no-sort void the list ? */
return -1;
s = xcalloc(1, sizeof(*s));
s->next = *sorting_tail;
@ -2149,8 +2163,14 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
if (skip_prefix(arg, "version:", &arg) ||
skip_prefix(arg, "v:", &arg))
s->version = 1;
len = strlen(arg);
s->atom = parse_ref_filter_atom(arg, arg+len);
s->atom = parse_sorting_atom(arg);
}
int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
{
if (!arg) /* should --no-sort void the list ? */
return -1;
parse_ref_sorting(opt->value, arg);
return 0;
}

View File

@ -72,6 +72,21 @@ struct ref_filter {
verbose;
};
struct ref_format {
/*
* Set these to define the format; make sure you call
* verify_ref_format() afterwards to finalize.
*/
const char *format;
int quote_style;
int use_color;
/* Internal state to ref-filter */
int need_color_reset_at_eol;
};
#define REF_FORMAT_INIT { NULL, 0, -1 }
/* Macros for checking --merged and --no-merged options */
#define _OPT_MERGED_NO_MERGED(option, filter, h) \
{ OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
@ -90,17 +105,18 @@ struct ref_filter {
int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type);
/* Clear all memory allocated to ref_array */
void ref_array_clear(struct ref_array *array);
/* Parse format string and sort specifiers */
int parse_ref_filter_atom(const char *atom, const char *ep);
/* Used to verify if the given format is correct and to parse out the used atoms */
int verify_ref_format(const char *format);
int verify_ref_format(struct ref_format *format);
/* Sort the given ref_array as per the ref_sorting provided */
void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
/* Based on the given format and quote_style, fill the strbuf */
void format_ref_array_item(struct ref_array_item *info, const char *format,
int quote_style, struct strbuf *final_buf);
void format_ref_array_item(struct ref_array_item *info,
const struct ref_format *format,
struct strbuf *final_buf);
/* Print the ref using the given format and quote_style */
void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style);
void show_ref_array_item(struct ref_array_item *info, const struct ref_format *format);
/* Parse a single sort specifier and add it to the list */
void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *atom);
/* Callback function for parsing the sort option */
int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
/* Default sort option based on refname */
@ -117,6 +133,6 @@ void setup_ref_filter_porcelain_msg(void);
* name must be a fully qualified refname.
*/
void pretty_print_ref(const char *name, const unsigned char *sha1,
const char *format);
const struct ref_format *format);
#endif /* REF_FILTER_H */

View File

@ -2,6 +2,7 @@
test_description='git branch display tests'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
test_expect_success 'make commits' '
echo content >file &&
@ -239,4 +240,34 @@ test_expect_success 'git branch --format option' '
test_i18ncmp expect actual
'
test_expect_success "set up color tests" '
echo "<RED>master<RESET>" >expect.color &&
echo "master" >expect.bare &&
color_args="--format=%(color:red)%(refname:short) --list master"
'
test_expect_success '%(color) omitted without tty' '
TERM=vt100 git branch $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.bare actual
'
test_expect_success TTY '%(color) present with tty' '
test_terminal env TERM=vt100 git branch $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
test_expect_success 'color.branch=always overrides auto-color' '
git -c color.branch=always branch $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
test_expect_success '--color overrides auto-color' '
git branch --color $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
test_done

View File

@ -7,11 +7,6 @@ test_description='Test for "git log --decorate" colors'
. ./test-lib.sh
get_color ()
{
git config --get-color no.such.slot "$1"
}
test_expect_success setup '
git config diff.color.commit yellow &&
git config color.decorate.branch green &&
@ -20,14 +15,14 @@ test_expect_success setup '
git config color.decorate.stash magenta &&
git config color.decorate.HEAD cyan &&
c_reset=$(get_color reset) &&
c_reset="<RESET>" &&
c_commit=$(get_color yellow) &&
c_branch=$(get_color green) &&
c_remoteBranch=$(get_color red) &&
c_tag=$(get_color "reverse bold yellow") &&
c_stash=$(get_color magenta) &&
c_HEAD=$(get_color cyan) &&
c_commit="<YELLOW>" &&
c_branch="<GREEN>" &&
c_remoteBranch="<RED>" &&
c_tag="<BOLD;REVERSE;YELLOW>" &&
c_stash="<MAGENTA>" &&
c_HEAD="<CYAN>" &&
test_commit A &&
git clone . other &&
@ -59,7 +54,8 @@ EOF
# to this test since it does not contain any decoration, hence --first-parent
test_expect_success 'Commit Decorations Colored Correctly' '
git log --first-parent --abbrev=10 --all --decorate --oneline --color=always |
sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" >out &&
sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" |
test_decode_color >out &&
test_cmp expected out
'

View File

@ -59,10 +59,14 @@ test_format () {
}
# Feed to --format to provide predictable colored sequences.
BASIC_COLOR='%Credfoo%Creset'
COLOR='%C(red)foo%C(reset)'
AUTO_COLOR='%C(auto,red)foo%C(auto,reset)'
ALWAYS_COLOR='%C(always,red)foo%C(always,reset)'
has_color () {
printf '\033[31mfoo\033[m\n' >expect &&
test_cmp expect "$1"
test_decode_color <"$1" >decoded &&
echo "<RED>foo<RESET>" >expect &&
test_cmp expect decoded
}
has_no_color () {
@ -170,62 +174,84 @@ $added
EOF
test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<EOF
commit $head2
foobarbazxyzzy
commit $head1
foobarbazxyzzy
EOF
test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<EOF
commit $head2
foo
commit $head1
foo
EOF
test_expect_success '%C(auto,...) does not enable color by default' '
git log --format=$AUTO_COLOR -1 >actual &&
has_no_color actual
test_expect_success 'basic colors' '
cat >expect <<-EOF &&
commit $head2
<RED>foo<GREEN>bar<BLUE>baz<RESET>xyzzy
EOF
format="%Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy" &&
git rev-list --color --format="$format" -1 master >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect actual
'
test_expect_success '%C(auto,...) enables colors for color.diff' '
git -c color.diff=always log --format=$AUTO_COLOR -1 >actual &&
has_color actual
test_expect_success 'advanced colors' '
cat >expect <<-EOF &&
commit $head2
<BOLD;RED;BYELLOW>foo<RESET>
EOF
format="%C(red yellow bold)foo%C(reset)" &&
git rev-list --color --format="$format" -1 master >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect actual
'
test_expect_success '%C(auto,...) enables colors for color.ui' '
git -c color.ui=always log --format=$AUTO_COLOR -1 >actual &&
has_color actual
'
test_expect_success '%C(auto,...) respects --color' '
git log --format=$AUTO_COLOR -1 --color >actual &&
has_color actual
'
test_expect_success '%C(auto,...) respects --no-color' '
git -c color.ui=always log --format=$AUTO_COLOR -1 --no-color >actual &&
has_no_color actual
'
test_expect_success TTY '%C(auto,...) respects --color=auto (stdout is tty)' '
test_terminal env TERM=vt100 \
git log --format=$AUTO_COLOR -1 --color=auto >actual &&
has_color actual
'
test_expect_success '%C(auto,...) respects --color=auto (stdout not tty)' '
(
TERM=vt100 && export TERM &&
git log --format=$AUTO_COLOR -1 --color=auto >actual &&
for spec in \
"%Cred:$BASIC_COLOR" \
"%C(...):$COLOR" \
"%C(auto,...):$AUTO_COLOR"
do
desc=${spec%%:*}
color=${spec#*:}
test_expect_success "$desc does not enable color by default" '
git log --format=$color -1 >actual &&
has_no_color actual
)
'
test_expect_success "$desc enables colors for color.diff" '
git -c color.diff=always log --format=$color -1 >actual &&
has_color actual
'
test_expect_success "$desc enables colors for color.ui" '
git -c color.ui=always log --format=$color -1 >actual &&
has_color actual
'
test_expect_success "$desc respects --color" '
git log --format=$color -1 --color >actual &&
has_color actual
'
test_expect_success "$desc respects --no-color" '
git -c color.ui=always log --format=$color -1 --no-color >actual &&
has_no_color actual
'
test_expect_success TTY "$desc respects --color=auto (stdout is tty)" '
test_terminal env TERM=vt100 \
git log --format=$color -1 --color=auto >actual &&
has_color actual
'
test_expect_success "$desc respects --color=auto (stdout not tty)" '
(
TERM=vt100 && export TERM &&
git log --format=$color -1 --color=auto >actual &&
has_no_color actual
)
'
done
test_expect_success '%C(always,...) enables color even without tty' '
git log --format=$ALWAYS_COLOR -1 >actual &&
has_color actual
'
test_expect_success '%C(auto) respects --color' '
git log --color --format="%C(auto)%H" -1 >actual &&
printf "\\033[33m%s\\033[m\\n" $(git rev-parse HEAD) >expect &&
git log --color --format="%C(auto)%H" -1 >actual.raw &&
test_decode_color <actual.raw >actual &&
echo "<YELLOW>$(git rev-parse HEAD)<RESET>" >expect &&
test_cmp expect actual
'
@ -235,6 +261,17 @@ test_expect_success '%C(auto) respects --no-color' '
test_cmp expect actual
'
test_expect_success 'rev-list %C(auto,...) respects --color' '
git rev-list --color --format="%C(auto,green)foo%C(auto,reset)" \
-1 HEAD >actual.raw &&
test_decode_color <actual.raw >actual &&
cat >expect <<-EOF &&
commit $(git rev-parse HEAD)
<GREEN>foo<RESET>
EOF
test_cmp expect actual
'
iconv -f utf-8 -t $test_encoding > commit-msg <<EOF
Test printing of complex bodies

View File

@ -7,6 +7,7 @@ test_description='for-each-ref test'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
# Mon Jul 3 23:18:43 2006 +0000
datestamp=1151968723
@ -412,21 +413,33 @@ 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"
}
test_expect_success 'set up color tests' '
cat >expected.color <<-EOF &&
$(git rev-parse --short refs/heads/master) <GREEN>master<RESET>
$(git rev-parse --short refs/remotes/origin/master) <GREEN>origin/master<RESET>
$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
EOF
sed "s/<[^>]*>//g" <expected.color >expected.bare &&
color_format="%(objectname:short) %(color:green)%(refname:short)"
'
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 TTY '%(color) shows color with a tty' '
test_terminal env TERM=vt100 \
git for-each-ref --format="$color_format" >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expected.color actual
'
test_expect_success 'Check %(color:...) ' '
git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual &&
test_cmp expected actual
test_expect_success '%(color) does not show color without tty' '
TERM=vt100 git for-each-ref --format="$color_format" >actual &&
test_cmp expected.bare actual
'
test_expect_success 'color.ui=always can override tty check' '
git -c color.ui=always for-each-ref --format="$color_format" >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expected.color actual
'
cat >expected <<\EOF

View File

@ -9,6 +9,7 @@ Tests for operations with tags.'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
# creating and listing lightweight tags:
@ -1900,6 +1901,30 @@ test_expect_success '--format should list tags as per format given' '
test_cmp expect actual
'
test_expect_success "set up color tests" '
echo "<RED>v1.0<RESET>" >expect.color &&
echo "v1.0" >expect.bare &&
color_args="--format=%(color:red)%(refname:short) --list v1.0"
'
test_expect_success '%(color) omitted without tty' '
TERM=vt100 git tag $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.bare actual
'
test_expect_success TTY '%(color) present with tty' '
test_terminal env TERM=vt100 git tag $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
test_expect_success 'color.ui=always overrides auto-color' '
git -c color.ui=always tag $color_args >actual.raw &&
test_decode_color <actual.raw >actual &&
test_cmp expect.color actual
'
test_expect_success 'setup --merged test tags' '
git tag mergetest-1 HEAD~2 &&
git tag mergetest-2 HEAD~1 &&

View File

@ -42,6 +42,7 @@ test_decode_color () {
function name(n) {
if (n == 0) return "RESET";
if (n == 1) return "BOLD";
if (n == 7) return "REVERSE";
if (n == 30) return "BLACK";
if (n == 31) return "RED";
if (n == 32) return "GREEN";