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