Coverage Report

Created: 2026-01-09 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/sideband.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
#define DISABLE_SIGN_COMPARE_WARNINGS
3
4
#include "git-compat-util.h"
5
#include "color.h"
6
#include "config.h"
7
#include "editor.h"
8
#include "gettext.h"
9
#include "sideband.h"
10
#include "help.h"
11
#include "pkt-line.h"
12
#include "write-or-die.h"
13
14
struct keyword_entry {
15
  /*
16
   * We use keyword as config key so it should be a single alphanumeric word.
17
   */
18
  const char *keyword;
19
  char color[COLOR_MAXLEN];
20
};
21
22
static struct keyword_entry keywords[] = {
23
  { "hint", GIT_COLOR_YELLOW },
24
  { "warning",  GIT_COLOR_BOLD_YELLOW },
25
  { "success",  GIT_COLOR_BOLD_GREEN },
26
  { "error",  GIT_COLOR_BOLD_RED },
27
};
28
29
/* Returns a color setting (GIT_COLOR_NEVER, etc). */
30
static enum git_colorbool use_sideband_colors(void)
31
0
{
32
0
  static enum git_colorbool use_sideband_colors_cached = GIT_COLOR_UNKNOWN;
33
34
0
  const char *key = "color.remote";
35
0
  struct strbuf sb = STRBUF_INIT;
36
0
  const char *value;
37
0
  int i;
38
39
0
  if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN)
40
0
    return use_sideband_colors_cached;
41
42
0
  if (!repo_config_get_string_tmp(the_repository, key, &value))
43
0
    use_sideband_colors_cached = git_config_colorbool(key, value);
44
0
  else if (!repo_config_get_string_tmp(the_repository, "color.ui", &value))
45
0
    use_sideband_colors_cached = git_config_colorbool("color.ui", value);
46
0
  else
47
0
    use_sideband_colors_cached = GIT_COLOR_AUTO;
48
49
0
  for (i = 0; i < ARRAY_SIZE(keywords); i++) {
50
0
    strbuf_reset(&sb);
51
0
    strbuf_addf(&sb, "%s.%s", key, keywords[i].keyword);
52
0
    if (repo_config_get_string_tmp(the_repository, sb.buf, &value))
53
0
      continue;
54
0
    color_parse(value, keywords[i].color);
55
0
  }
56
57
0
  strbuf_release(&sb);
58
0
  return use_sideband_colors_cached;
59
0
}
60
61
void list_config_color_sideband_slots(struct string_list *list, const char *prefix)
62
0
{
63
0
  int i;
64
65
0
  for (i = 0; i < ARRAY_SIZE(keywords); i++)
66
0
    list_config_item(list, prefix, keywords[i].keyword);
67
0
}
68
69
/*
70
 * Optionally highlight one keyword in remote output if it appears at the start
71
 * of the line. This should be called for a single line only, which is
72
 * passed as the first N characters of the SRC array.
73
 *
74
 * It is fine to use "int n" here instead of "size_t n" as all calls to this
75
 * function pass an 'int' parameter. Additionally, the buffer involved in
76
 * storing these 'int' values takes input from a packet via the pkt-line
77
 * interface, which is capable of transferring only 64kB at a time.
78
 */
79
static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
80
0
{
81
0
  int i;
82
83
0
  if (!want_color_stderr(use_sideband_colors())) {
84
0
    strbuf_add(dest, src, n);
85
0
    return;
86
0
  }
87
88
0
  while (0 < n && isspace(*src)) {
89
0
    strbuf_addch(dest, *src);
90
0
    src++;
91
0
    n--;
92
0
  }
93
94
0
  for (i = 0; i < ARRAY_SIZE(keywords); i++) {
95
0
    struct keyword_entry *p = keywords + i;
96
0
    int len = strlen(p->keyword);
97
98
0
    if (n < len)
99
0
      continue;
100
    /*
101
     * Match case insensitively, so we colorize output from existing
102
     * servers regardless of the case that they use for their
103
     * messages. We only highlight the word precisely, so
104
     * "successful" stays uncolored.
105
     */
106
0
    if (!strncasecmp(p->keyword, src, len) &&
107
0
        (len == n || !isalnum(src[len]))) {
108
0
      strbuf_addstr(dest, p->color);
109
0
      strbuf_add(dest, src, len);
110
0
      strbuf_addstr(dest, GIT_COLOR_RESET);
111
0
      n -= len;
112
0
      src += len;
113
0
      break;
114
0
    }
115
0
  }
116
117
0
  strbuf_add(dest, src, n);
118
0
}
119
120
121
0
#define DISPLAY_PREFIX "remote: "
122
123
0
#define ANSI_SUFFIX "\033[K"
124
0
#define DUMB_SUFFIX "        "
125
126
int demultiplex_sideband(const char *me, int status,
127
       char *buf, int len,
128
       int die_on_error,
129
       struct strbuf *scratch,
130
       enum sideband_type *sideband_type)
131
0
{
132
0
  static const char *suffix;
133
0
  const char *b, *brk;
134
0
  int band;
135
136
0
  if (!suffix) {
137
0
    if (isatty(2) && !is_terminal_dumb())
138
0
      suffix = ANSI_SUFFIX;
139
0
    else
140
0
      suffix = DUMB_SUFFIX;
141
0
  }
142
143
0
  if (status == PACKET_READ_EOF) {
144
0
    strbuf_addf(scratch,
145
0
          "%s%s: unexpected disconnect while reading sideband packet",
146
0
          scratch->len ? "\n" : "", me);
147
0
    *sideband_type = SIDEBAND_PROTOCOL_ERROR;
148
0
    goto cleanup;
149
0
  }
150
151
0
  if (len < 0)
152
0
    BUG("negative length on non-eof packet read");
153
154
0
  if (len == 0) {
155
0
    if (status == PACKET_READ_NORMAL) {
156
0
      strbuf_addf(scratch,
157
0
            "%s%s: protocol error: missing sideband designator",
158
0
            scratch->len ? "\n" : "", me);
159
0
      *sideband_type = SIDEBAND_PROTOCOL_ERROR;
160
0
    } else {
161
      /* covers flush, delim, etc */
162
0
      *sideband_type = SIDEBAND_FLUSH;
163
0
    }
164
0
    goto cleanup;
165
0
  }
166
167
0
  band = buf[0] & 0xff;
168
0
  buf[len] = '\0';
169
0
  len--;
170
0
  switch (band) {
171
0
  case 3:
172
0
    if (die_on_error)
173
0
      die(_("remote error: %s"), buf + 1);
174
0
    strbuf_addf(scratch, "%s%s", scratch->len ? "\n" : "",
175
0
          DISPLAY_PREFIX);
176
0
    maybe_colorize_sideband(scratch, buf + 1, len);
177
178
0
    *sideband_type = SIDEBAND_REMOTE_ERROR;
179
0
    break;
180
0
  case 2:
181
0
    b = buf + 1;
182
183
    /*
184
     * Append a suffix to each nonempty line to clear the
185
     * end of the screen line.
186
     *
187
     * The output is accumulated in a buffer and
188
     * each line is printed to stderr using
189
     * write(2) to ensure inter-process atomicity.
190
     */
191
0
    while ((brk = strpbrk(b, "\n\r"))) {
192
0
      int linelen = brk - b;
193
194
      /*
195
       * For message across packet boundary, there would have
196
       * a nonempty "scratch" buffer from last call of this
197
       * function, and there may have a leading CR/LF in "buf".
198
       * For this case we should add a clear-to-eol suffix to
199
       * clean leftover letters we previously have written on
200
       * the same line.
201
       */
202
0
      if (scratch->len && !linelen)
203
0
        strbuf_addstr(scratch, suffix);
204
205
0
      if (!scratch->len)
206
0
        strbuf_addstr(scratch, DISPLAY_PREFIX);
207
208
      /*
209
       * A use case that we should not add clear-to-eol suffix
210
       * to empty lines:
211
       *
212
       * For progress reporting we may receive a bunch of
213
       * percentage updates followed by '\r' to remain on the
214
       * same line, and at the end receive a single '\n' to
215
       * move to the next line. We should preserve the final
216
       * status report line by not appending clear-to-eol
217
       * suffix to this single line break.
218
       */
219
0
      if (linelen > 0) {
220
0
        maybe_colorize_sideband(scratch, b, linelen);
221
0
        strbuf_addstr(scratch, suffix);
222
0
      }
223
224
0
      strbuf_addch(scratch, *brk);
225
0
      write_in_full(2, scratch->buf, scratch->len);
226
0
      strbuf_reset(scratch);
227
228
0
      b = brk + 1;
229
0
    }
230
231
0
    if (*b) {
232
0
      strbuf_addstr(scratch, scratch->len ?
233
0
            "" : DISPLAY_PREFIX);
234
0
      maybe_colorize_sideband(scratch, b, strlen(b));
235
0
    }
236
0
    return 0;
237
0
  case 1:
238
0
    *sideband_type = SIDEBAND_PRIMARY;
239
0
    return 1;
240
0
  default:
241
0
    strbuf_addf(scratch, "%s%s: protocol error: bad band #%d",
242
0
          scratch->len ? "\n" : "", me, band);
243
0
    *sideband_type = SIDEBAND_PROTOCOL_ERROR;
244
0
    break;
245
0
  }
246
247
0
cleanup:
248
0
  if (die_on_error && *sideband_type == SIDEBAND_PROTOCOL_ERROR)
249
0
    die("%s", scratch->buf);
250
0
  if (scratch->len) {
251
0
    strbuf_addch(scratch, '\n');
252
0
    write_in_full(2, scratch->buf, scratch->len);
253
0
  }
254
0
  strbuf_release(scratch);
255
0
  return 1;
256
0
}
257
258
/*
259
 * fd is connected to the remote side; send the sideband data
260
 * over multiplexed packet stream.
261
 */
262
void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max)
263
0
{
264
0
  const char *p = data;
265
266
0
  while (sz) {
267
0
    unsigned n;
268
0
    char hdr[5];
269
270
0
    n = sz;
271
0
    if (packet_max - 5 < n)
272
0
      n = packet_max - 5;
273
0
    if (0 <= band) {
274
0
      xsnprintf(hdr, sizeof(hdr), "%04x", n + 5);
275
0
      hdr[4] = band;
276
0
      write_or_die(fd, hdr, 5);
277
0
    } else {
278
0
      xsnprintf(hdr, sizeof(hdr), "%04x", n + 4);
279
0
      write_or_die(fd, hdr, 4);
280
0
    }
281
0
    write_or_die(fd, p, n);
282
0
    p += n;
283
0
    sz -= n;
284
0
  }
285
0
}