Skip to content

Commit a56fb3d

Browse files
committed
Merge branch 'js/colored-push-errors'
Error messages from "git push" can be painted for more visibility. * js/colored-push-errors: config: document the settings to colorize push errors/hints push: test to verify that push errors are colored push: colorize errors color: introduce support for colorizing stderr
2 parents 3915f9a + 79f62e7 commit a56fb3d

File tree

8 files changed

+211
-15
lines changed

8 files changed

+211
-15
lines changed

Documentation/config.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,16 @@ clean.requireForce::
10981098
A boolean to make git-clean do nothing unless given -f,
10991099
-i or -n. Defaults to true.
11001100

1101+
color.advice::
1102+
A boolean to enable/disable color in hints (e.g. when a push
1103+
failed, see `advice.*` for a list). May be set to `always`,
1104+
`false` (or `never`) or `auto` (or `true`), in which case colors
1105+
are used only when the error output goes to a terminal. If
1106+
unset, then the value of `color.ui` is used (`auto` by default).
1107+
1108+
color.advice.hint::
1109+
Use customized color for hints.
1110+
11011111
color.branch::
11021112
A boolean to enable/disable color in the output of
11031113
linkgit:git-branch[1]. May be set to `always`,
@@ -1200,6 +1210,15 @@ color.pager::
12001210
A boolean to enable/disable colored output when the pager is in
12011211
use (default is true).
12021212

1213+
color.push::
1214+
A boolean to enable/disable color in push errors. May be set to
1215+
`always`, `false` (or `never`) or `auto` (or `true`), in which
1216+
case colors are used only when the error output goes to a terminal.
1217+
If unset, then the value of `color.ui` is used (`auto` by default).
1218+
1219+
color.push.error::
1220+
Use customized color for push errors.
1221+
12031222
color.showBranch::
12041223
A boolean to enable/disable color in the output of
12051224
linkgit:git-show-branch[1]. May be set to `always`,
@@ -1228,6 +1247,15 @@ color.status.<slot>::
12281247
status short-format), or
12291248
`unmerged` (files which have unmerged changes).
12301249

1250+
color.transport::
1251+
A boolean to enable/disable color when pushes are rejected. May be
1252+
set to `always`, `false` (or `never`) or `auto` (or `true`), in which
1253+
case colors are used only when the error output goes to a terminal.
1254+
If unset, then the value of `color.ui` is used (`auto` by default).
1255+
1256+
color.transport.rejected::
1257+
Use customized color when a push was rejected.
1258+
12311259
color.ui::
12321260
This variable determines the default value for variables such
12331261
as `color.diff` and `color.grep` that control the use of color

advice.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "cache.h"
22
#include "config.h"
3+
#include "color.h"
34

45
int advice_push_update_rejected = 1;
56
int advice_push_non_ff_current = 1;
@@ -20,6 +21,33 @@ int advice_add_embedded_repo = 1;
2021
int advice_ignored_hook = 1;
2122
int advice_waiting_for_editor = 1;
2223

24+
static int advice_use_color = -1;
25+
static char advice_colors[][COLOR_MAXLEN] = {
26+
GIT_COLOR_RESET,
27+
GIT_COLOR_YELLOW, /* HINT */
28+
};
29+
30+
enum color_advice {
31+
ADVICE_COLOR_RESET = 0,
32+
ADVICE_COLOR_HINT = 1,
33+
};
34+
35+
static int parse_advise_color_slot(const char *slot)
36+
{
37+
if (!strcasecmp(slot, "reset"))
38+
return ADVICE_COLOR_RESET;
39+
if (!strcasecmp(slot, "hint"))
40+
return ADVICE_COLOR_HINT;
41+
return -1;
42+
}
43+
44+
static const char *advise_get_color(enum color_advice ix)
45+
{
46+
if (want_color_stderr(advice_use_color))
47+
return advice_colors[ix];
48+
return "";
49+
}
50+
2351
static struct {
2452
const char *name;
2553
int *preference;
@@ -59,7 +87,10 @@ void advise(const char *advice, ...)
5987

6088
for (cp = buf.buf; *cp; cp = np) {
6189
np = strchrnul(cp, '\n');
62-
fprintf(stderr, _("hint: %.*s\n"), (int)(np - cp), cp);
90+
fprintf(stderr, _("%shint: %.*s%s\n"),
91+
advise_get_color(ADVICE_COLOR_HINT),
92+
(int)(np - cp), cp,
93+
advise_get_color(ADVICE_COLOR_RESET));
6394
if (*np)
6495
np++;
6596
}
@@ -68,9 +99,23 @@ void advise(const char *advice, ...)
6899

69100
int git_default_advice_config(const char *var, const char *value)
70101
{
71-
const char *k;
102+
const char *k, *slot_name;
72103
int i;
73104

105+
if (!strcmp(var, "color.advice")) {
106+
advice_use_color = git_config_colorbool(var, value);
107+
return 0;
108+
}
109+
110+
if (skip_prefix(var, "color.advice.", &slot_name)) {
111+
int slot = parse_advise_color_slot(slot_name);
112+
if (slot < 0)
113+
return 0;
114+
if (!value)
115+
return config_error_nonbool(var);
116+
return color_parse(value, advice_colors[slot]);
117+
}
118+
74119
if (!skip_prefix(var, "advice.", &k))
75120
return 0;
76121

builtin/push.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,40 @@
1212
#include "submodule.h"
1313
#include "submodule-config.h"
1414
#include "send-pack.h"
15+
#include "color.h"
1516

1617
static const char * const push_usage[] = {
1718
N_("git push [<options>] [<repository> [<refspec>...]]"),
1819
NULL,
1920
};
2021

22+
static int push_use_color = -1;
23+
static char push_colors[][COLOR_MAXLEN] = {
24+
GIT_COLOR_RESET,
25+
GIT_COLOR_RED, /* ERROR */
26+
};
27+
28+
enum color_push {
29+
PUSH_COLOR_RESET = 0,
30+
PUSH_COLOR_ERROR = 1
31+
};
32+
33+
static int parse_push_color_slot(const char *slot)
34+
{
35+
if (!strcasecmp(slot, "reset"))
36+
return PUSH_COLOR_RESET;
37+
if (!strcasecmp(slot, "error"))
38+
return PUSH_COLOR_ERROR;
39+
return -1;
40+
}
41+
42+
static const char *push_get_color(enum color_push ix)
43+
{
44+
if (want_color_stderr(push_use_color))
45+
return push_colors[ix];
46+
return "";
47+
}
48+
2149
static int thin = 1;
2250
static int deleterefs;
2351
static const char *receivepack;
@@ -337,8 +365,11 @@ static int push_with_options(struct transport *transport, int flags)
337365
fprintf(stderr, _("Pushing to %s\n"), transport->url);
338366
err = transport_push(transport, refspec_nr, refspec, flags,
339367
&reject_reasons);
340-
if (err != 0)
368+
if (err != 0) {
369+
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
341370
error(_("failed to push some refs to '%s'"), transport->url);
371+
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET));
372+
}
342373

343374
err |= transport_disconnect(transport);
344375
if (!err)
@@ -467,6 +498,7 @@ static void set_push_cert_flags(int *flags, int v)
467498

468499
static int git_push_config(const char *k, const char *v, void *cb)
469500
{
501+
const char *slot_name;
470502
int *flags = cb;
471503
int status;
472504

@@ -514,6 +546,16 @@ static int git_push_config(const char *k, const char *v, void *cb)
514546
else
515547
string_list_append(&push_options_config, v);
516548
return 0;
549+
} else if (!strcmp(k, "color.push")) {
550+
push_use_color = git_config_colorbool(k, v);
551+
return 0;
552+
} else if (skip_prefix(k, "color.push.", &slot_name)) {
553+
int slot = parse_push_color_slot(slot_name);
554+
if (slot < 0)
555+
return 0;
556+
if (!v)
557+
return config_error_nonbool(k);
558+
return color_parse(v, push_colors[slot]);
517559
}
518560

519561
return git_default_config(k, v, NULL);

color.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -319,18 +319,20 @@ int git_config_colorbool(const char *var, const char *value)
319319
return GIT_COLOR_AUTO;
320320
}
321321

322-
static int check_auto_color(void)
322+
static int check_auto_color(int fd)
323323
{
324-
if (color_stdout_is_tty < 0)
325-
color_stdout_is_tty = isatty(1);
326-
if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) {
324+
static int color_stderr_is_tty = -1;
325+
int *is_tty_p = fd == 1 ? &color_stdout_is_tty : &color_stderr_is_tty;
326+
if (*is_tty_p < 0)
327+
*is_tty_p = isatty(fd);
328+
if (*is_tty_p || (fd == 1 && pager_in_use() && pager_use_color)) {
327329
if (!is_terminal_dumb())
328330
return 1;
329331
}
330332
return 0;
331333
}
332334

333-
int want_color(int var)
335+
int want_color_fd(int fd, int var)
334336
{
335337
/*
336338
* NEEDSWORK: This function is sometimes used from multiple threads, and
@@ -339,15 +341,15 @@ int want_color(int var)
339341
* is listed in .tsan-suppressions for the time being.
340342
*/
341343

342-
static int want_auto = -1;
344+
static int want_auto[3] = { -1, -1, -1 };
343345

344346
if (var < 0)
345347
var = git_use_color_default;
346348

347349
if (var == GIT_COLOR_AUTO) {
348-
if (want_auto < 0)
349-
want_auto = check_auto_color();
350-
return want_auto;
350+
if (want_auto[fd] < 0)
351+
want_auto[fd] = check_auto_color(fd);
352+
return want_auto[fd];
351353
}
352354
return var;
353355
}

color.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ int git_config_colorbool(const char *var, const char *value);
8888
* Return a boolean whether to use color, where the argument 'var' is
8989
* one of GIT_COLOR_UNKNOWN, GIT_COLOR_NEVER, GIT_COLOR_ALWAYS, GIT_COLOR_AUTO.
9090
*/
91-
int want_color(int var);
91+
int want_color_fd(int fd, int var);
92+
#define want_color(colorbool) want_color_fd(1, (colorbool))
93+
#define want_color_stderr(colorbool) want_color_fd(2, (colorbool))
9294

9395
/*
9496
* Translate a Git color from 'value' into a string that the terminal can

config.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ int git_default_config(const char *var, const char *value, void *dummy)
14521452
if (starts_with(var, "mailmap."))
14531453
return git_default_mailmap_config(var, value);
14541454

1455-
if (starts_with(var, "advice."))
1455+
if (starts_with(var, "advice.") || starts_with(var, "color.advice"))
14561456
return git_default_advice_config(var, value);
14571457

14581458
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {

t/t5541-http-push-smart.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,5 +377,17 @@ test_expect_success 'push status output scrubs password' '
377377
grep "^To $HTTPD_URL/smart/test_repo.git" status
378378
'
379379

380+
test_expect_success 'colorize errors/hints' '
381+
cd "$ROOT_PATH"/test_repo_clone &&
382+
test_must_fail git -c color.transport=always -c color.advice=always \
383+
-c color.push=always \
384+
push origin origin/master^:master 2>act &&
385+
test_decode_color <act >decoded &&
386+
test_i18ngrep "<RED>.*rejected.*<RESET>" decoded &&
387+
test_i18ngrep "<RED>error: failed to push some refs" decoded &&
388+
test_i18ngrep "<YELLOW>hint: " decoded &&
389+
test_i18ngrep ! "^hint: " decoded
390+
'
391+
380392
stop_httpd
381393
test_done

transport.c

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,56 @@
2020
#include "transport-internal.h"
2121
#include "protocol.h"
2222
#include "object-store.h"
23+
#include "color.h"
24+
25+
static int transport_use_color = -1;
26+
static char transport_colors[][COLOR_MAXLEN] = {
27+
GIT_COLOR_RESET,
28+
GIT_COLOR_RED /* REJECTED */
29+
};
30+
31+
enum color_transport {
32+
TRANSPORT_COLOR_RESET = 0,
33+
TRANSPORT_COLOR_REJECTED = 1
34+
};
35+
36+
static int transport_color_config(void)
37+
{
38+
const char *keys[] = {
39+
"color.transport.reset",
40+
"color.transport.rejected"
41+
}, *key = "color.transport";
42+
char *value;
43+
int i;
44+
static int initialized;
45+
46+
if (initialized)
47+
return 0;
48+
initialized = 1;
49+
50+
if (!git_config_get_string(key, &value))
51+
transport_use_color = git_config_colorbool(key, value);
52+
53+
if (!want_color_stderr(transport_use_color))
54+
return 0;
55+
56+
for (i = 0; i < ARRAY_SIZE(keys); i++)
57+
if (!git_config_get_string(keys[i], &value)) {
58+
if (!value)
59+
return config_error_nonbool(keys[i]);
60+
if (color_parse(value, transport_colors[i]) < 0)
61+
return -1;
62+
}
63+
64+
return 0;
65+
}
66+
67+
static const char *transport_get_color(enum color_transport ix)
68+
{
69+
if (want_color_stderr(transport_use_color))
70+
return transport_colors[ix];
71+
return "";
72+
}
2373

2474
static void set_upstreams(struct transport *transport, struct ref *refs,
2575
int pretend)
@@ -373,7 +423,13 @@ static void print_ref_status(char flag, const char *summary,
373423
else
374424
fprintf(stdout, "%s\n", summary);
375425
} else {
376-
fprintf(stderr, " %c %-*s ", flag, summary_width, summary);
426+
const char *red = "", *reset = "";
427+
if (push_had_errors(to)) {
428+
red = transport_get_color(TRANSPORT_COLOR_REJECTED);
429+
reset = transport_get_color(TRANSPORT_COLOR_RESET);
430+
}
431+
fprintf(stderr, " %s%c %-*s%s ", red, flag, summary_width,
432+
summary, reset);
377433
if (from)
378434
fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
379435
else
@@ -522,6 +578,9 @@ void transport_print_push_status(const char *dest, struct ref *refs,
522578
char *head;
523579
int summary_width = transport_summary_width(refs);
524580

581+
if (transport_color_config() < 0)
582+
warning(_("could not parse transport.color.* config"));
583+
525584
head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
526585

527586
if (verbose) {
@@ -588,6 +647,9 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
588647
struct send_pack_args args;
589648
int ret = 0;
590649

650+
if (transport_color_config() < 0)
651+
return -1;
652+
591653
if (!data->got_remote_heads)
592654
get_refs_via_connect(transport, 1, NULL);
593655

@@ -1036,6 +1098,9 @@ int transport_push(struct transport *transport,
10361098
*reject_reasons = 0;
10371099
transport_verify_remote_names(refspec_nr, refspec);
10381100

1101+
if (transport_color_config() < 0)
1102+
return -1;
1103+
10391104
if (transport->vtable->push_refs) {
10401105
struct ref *remote_refs;
10411106
struct ref *local_refs = get_local_heads();

0 commit comments

Comments
 (0)