Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/pager.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "config.h"
3
#include "editor.h"
4
#include "pager.h"
5
#include "run-command.h"
6
#include "sigchain.h"
7
#include "alias.h"
8
#include "repository.h"
9
#include "environment.h"
10
11
int pager_use_color = 1;
12
13
#ifndef DEFAULT_PAGER
14
0
#define DEFAULT_PAGER "less"
15
#endif
16
17
static struct child_process pager_process;
18
static int old_fd1 = -1, old_fd2 = -1;
19
20
/* Is the value coming back from term_columns() just a guess? */
21
static int term_columns_guessed;
22
23
24
static void close_pager_fds(void)
25
0
{
26
  /* signal EOF to pager */
27
0
  close(1);
28
0
  if (old_fd2 != -1)
29
0
    close(2);
30
0
}
31
32
static void finish_pager(void)
33
0
{
34
0
  fflush(stdout);
35
0
  fflush(stderr);
36
0
  close_pager_fds();
37
0
  finish_command(&pager_process);
38
0
}
39
40
static void wait_for_pager_atexit(void)
41
0
{
42
0
  if (old_fd1 == -1)
43
0
    return;
44
45
0
  finish_pager();
46
0
}
47
48
void wait_for_pager(void)
49
0
{
50
0
  if (old_fd1 == -1)
51
0
    return;
52
53
0
  finish_pager();
54
0
  sigchain_pop_common();
55
0
  unsetenv("GIT_PAGER_IN_USE");
56
0
  dup2(old_fd1, 1);
57
0
  close(old_fd1);
58
0
  old_fd1 = -1;
59
0
  if (old_fd2 != -1) {
60
0
    dup2(old_fd2, 2);
61
0
    close(old_fd2);
62
0
    old_fd2 = -1;
63
0
  }
64
0
}
65
66
static void wait_for_pager_signal(int signo)
67
0
{
68
0
  if (old_fd1 == -1)
69
0
    return;
70
71
0
  close_pager_fds();
72
0
  finish_command_in_signal(&pager_process);
73
0
  sigchain_pop(signo);
74
0
  raise(signo);
75
0
}
76
77
static int core_pager_config(const char *var, const char *value,
78
           const struct config_context *ctx UNUSED,
79
           void *data)
80
0
{
81
0
  struct repository *r = data;
82
83
0
  if (!strcmp(var, "core.pager")) {
84
0
    struct repo_config_values *cfg = repo_config_values(r);
85
86
0
    FREE_AND_NULL(cfg->pager_program);
87
0
    return git_config_string(&cfg->pager_program, var, value);
88
0
  }
89
90
0
  return 0;
91
0
}
92
93
const char *git_pager(struct repository *r, int stdout_is_tty)
94
0
{
95
0
  const char *pager;
96
97
0
  if (!stdout_is_tty)
98
0
    return NULL;
99
100
0
  pager = getenv("GIT_PAGER");
101
0
  if (!pager) {
102
0
    struct repo_config_values *cfg = repo_config_values(r);
103
104
0
    if (!cfg->pager_program)
105
0
      read_early_config(r,
106
0
            core_pager_config, r);
107
0
    pager = cfg->pager_program;
108
0
  }
109
0
  if (!pager)
110
0
    pager = getenv("PAGER");
111
0
  if (!pager)
112
0
    pager = DEFAULT_PAGER;
113
0
  if (!*pager || !strcmp(pager, "cat"))
114
0
    pager = NULL;
115
116
0
  return pager;
117
0
}
118
119
static void setup_pager_env(struct strvec *env)
120
0
{
121
0
  char **argv;
122
0
  int i;
123
0
  char *pager_env = xstrdup(PAGER_ENV);
124
  /* split_cmdline splits in place, so we know the result is writable */
125
0
  int n = split_cmdline(pager_env, (const char ***)&argv);
126
127
0
  if (n < 0)
128
0
    die("malformed build-time PAGER_ENV: %s",
129
0
      split_cmdline_strerror(n));
130
131
0
  for (i = 0; i < n; i++) {
132
0
    char *cp = strchr(argv[i], '=');
133
134
0
    if (!cp)
135
0
      die("malformed build-time PAGER_ENV");
136
137
0
    *cp = '\0';
138
0
    if (!getenv(argv[i])) {
139
0
      *cp = '=';
140
0
      strvec_push(env, argv[i]);
141
0
    }
142
0
  }
143
0
  free(pager_env);
144
0
  free(argv);
145
0
}
146
147
void prepare_pager_args(struct child_process *pager_process, const char *pager)
148
0
{
149
0
  strvec_push(&pager_process->args, pager);
150
0
  pager_process->use_shell = 1;
151
0
  setup_pager_env(&pager_process->env);
152
0
  pager_process->trace2_child_class = "pager";
153
0
}
154
155
void setup_pager(struct repository *r)
156
0
{
157
0
  static int once = 0;
158
0
  const char *pager = git_pager(r, isatty(1));
159
160
0
  if (!pager)
161
0
    return;
162
163
  /*
164
   * After we redirect standard output, we won't be able to use an ioctl
165
   * to get the terminal size. Let's grab it now, and then set $COLUMNS
166
   * to communicate it to any sub-processes.
167
   */
168
0
  {
169
0
    char buf[64];
170
0
    xsnprintf(buf, sizeof(buf), "%d", term_columns());
171
0
    if (!term_columns_guessed)
172
0
      setenv("COLUMNS", buf, 0);
173
0
  }
174
175
0
  setenv("GIT_PAGER_IN_USE", "true", 1);
176
177
0
  child_process_init(&pager_process);
178
179
  /* spawn the pager */
180
0
  prepare_pager_args(&pager_process, pager);
181
0
  pager_process.in = -1;
182
0
  strvec_push(&pager_process.env, "GIT_PAGER_IN_USE");
183
0
  if (start_command(&pager_process))
184
0
    die("unable to execute pager '%s'", pager);
185
186
  /* original process continues, but writes to the pipe */
187
0
  old_fd1 = dup(1);
188
0
  dup2(pager_process.in, 1);
189
0
  if (isatty(2)) {
190
0
    old_fd2 = dup(2);
191
0
    dup2(pager_process.in, 2);
192
0
  }
193
0
  close(pager_process.in);
194
195
0
  sigchain_push_common(wait_for_pager_signal);
196
197
0
  if (!once) {
198
0
    once++;
199
0
    atexit(wait_for_pager_atexit);
200
0
  }
201
0
}
202
203
int pager_in_use(void)
204
0
{
205
0
  return git_env_bool("GIT_PAGER_IN_USE", 0);
206
0
}
207
208
/*
209
 * Return cached value (if set) or $COLUMNS environment variable (if
210
 * set and positive) or ioctl(1, TIOCGWINSZ).ws_col (if positive),
211
 * and default to 80 if all else fails.
212
 */
213
int term_columns(void)
214
0
{
215
0
  static int term_columns_at_startup;
216
217
0
  char *col_string;
218
0
  int n_cols;
219
220
0
  if (term_columns_at_startup)
221
0
    return term_columns_at_startup;
222
223
0
  term_columns_at_startup = 80;
224
0
  term_columns_guessed = 1;
225
226
0
  col_string = getenv("COLUMNS");
227
0
  if (col_string && (n_cols = atoi(col_string)) > 0) {
228
0
    term_columns_at_startup = n_cols;
229
0
    term_columns_guessed = 0;
230
0
  }
231
0
#ifdef TIOCGWINSZ
232
0
  else {
233
0
    struct winsize ws;
234
0
    if (!ioctl(1, TIOCGWINSZ, &ws) && ws.ws_col) {
235
0
      term_columns_at_startup = ws.ws_col;
236
0
      term_columns_guessed = 0;
237
0
    }
238
0
  }
239
0
#endif
240
241
0
  return term_columns_at_startup;
242
0
}
243
244
/*
245
 * Clear the entire line, leave cursor in first column.
246
 */
247
void term_clear_line(void)
248
0
{
249
0
  if (!isatty(2))
250
0
    return;
251
0
  if (is_terminal_dumb())
252
    /*
253
     * Fall back to print a terminal width worth of space
254
     * characters (hoping that the terminal is still as wide
255
     * as it was upon the first call to term_columns()).
256
     */
257
0
    fprintf(stderr, "\r%*s\r", term_columns(), "");
258
0
  else
259
    /*
260
     * On non-dumb terminals use an escape sequence to clear
261
     * the whole line, no matter how wide the terminal.
262
     */
263
0
    fputs("\r\033[K", stderr);
264
0
}
265
266
/*
267
 * How many columns do we need to show this number in decimal?
268
 */
269
int decimal_width(uintmax_t number)
270
0
{
271
0
  int width;
272
273
0
  for (width = 1; number >= 10; width++)
274
0
    number /= 10;
275
0
  return width;
276
0
}
277
278
struct pager_command_config_data {
279
  const char *cmd;
280
  int want;
281
  char *value;
282
};
283
284
static int pager_command_config(const char *var, const char *value,
285
        const struct config_context *ctx UNUSED,
286
        void *vdata)
287
0
{
288
0
  struct pager_command_config_data *data = vdata;
289
0
  const char *cmd;
290
291
0
  if (skip_prefix(var, "pager.", &cmd) && !strcmp(cmd, data->cmd)) {
292
0
    int b = git_parse_maybe_bool(value);
293
0
    if (b >= 0)
294
0
      data->want = b;
295
0
    else {
296
0
      data->want = 1;
297
0
      data->value = xstrdup(value);
298
0
    }
299
0
  }
300
301
0
  return 0;
302
0
}
303
304
/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
305
int check_pager_config(struct repository *r, const char *cmd)
306
0
{
307
0
  struct pager_command_config_data data;
308
309
0
  data.cmd = cmd;
310
0
  data.want = -1;
311
0
  data.value = NULL;
312
313
0
  read_early_config(r, pager_command_config, &data);
314
315
0
  if (data.value) {
316
0
    struct repo_config_values *cfg = repo_config_values(r);
317
318
0
    free(cfg->pager_program);
319
0
    cfg->pager_program = data.value;
320
0
  }
321
0
  return data.want;
322
0
}