Documentation: help: describe 'man.viewer' config variable
[git/git.git] / help.c
CommitLineData
70827b15
LT
1/*
2 * builtin-help.c
3 *
4 * Builtin help-related commands (help, usage, version)
5 */
6#include "cache.h"
7#include "builtin.h"
8#include "exec_cmd.h"
9#include "common-cmds.h"
41eb33bd 10#include "parse-options.h"
64949984
CC
11#include "run-command.h"
12
13static const char *man_viewer;
41eb33bd
JK
14
15enum help_format {
16 HELP_FORMAT_MAN,
17 HELP_FORMAT_INFO,
18 HELP_FORMAT_WEB,
19};
20
21static int show_all = 0;
22static enum help_format help_format = HELP_FORMAT_MAN;
23static struct option builtin_help_options[] = {
24 OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
25 OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
26 OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
27 HELP_FORMAT_WEB),
28 OPT_SET_INT('i', "info", &help_format, "show info page",
29 HELP_FORMAT_INFO),
30};
31
32static const char * const builtin_help_usage[] = {
33 "git-help [--all] [--man|--web|--info] [command]",
34 NULL
35};
36
37static enum help_format parse_help_format(const char *format)
70087cdb 38{
41eb33bd
JK
39 if (!strcmp(format, "man"))
40 return HELP_FORMAT_MAN;
41 if (!strcmp(format, "info"))
42 return HELP_FORMAT_INFO;
43 if (!strcmp(format, "web") || !strcmp(format, "html"))
44 return HELP_FORMAT_WEB;
70087cdb
CC
45 die("unrecognized help format '%s'", format);
46}
47
48static int git_help_config(const char *var, const char *value)
49{
41eb33bd
JK
50 if (!strcmp(var, "help.format")) {
51 if (!value)
52 return config_error_nonbool(var);
53 help_format = parse_help_format(value);
54 return 0;
55 }
64949984
CC
56 if (!strcmp(var, "man.viewer"))
57 return git_config_string(&man_viewer, var, value);
70087cdb
CC
58 return git_default_config(var, value);
59}
60
82e5a82f 61/* most GUI terminals set COLUMNS (although some don't export it) */
70827b15
LT
62static int term_columns(void)
63{
64 char *col_string = getenv("COLUMNS");
96f1e58f 65 int n_cols;
70827b15
LT
66
67 if (col_string && (n_cols = atoi(col_string)) > 0)
68 return n_cols;
69
70#ifdef TIOCGWINSZ
71 {
72 struct winsize ws;
73 if (!ioctl(1, TIOCGWINSZ, &ws)) {
74 if (ws.ws_col)
75 return ws.ws_col;
76 }
77 }
78#endif
79
80 return 80;
81}
82
70827b15
LT
83static inline void mput_char(char c, unsigned int num)
84{
85 while(num--)
86 putchar(c);
87}
88
1eb05690
SP
89static struct cmdnames {
90 int alloc;
91 int cnt;
92 struct cmdname {
93 size_t len;
94 char name[1];
95 } **names;
96} main_cmds, other_cmds;
97
98static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
70827b15 99{
1eb05690
SP
100 struct cmdname *ent = xmalloc(sizeof(*ent) + len);
101
70827b15
LT
102 ent->len = len;
103 memcpy(ent->name, name, len);
104 ent->name[len] = 0;
1eb05690
SP
105
106 ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
107 cmds->names[cmds->cnt++] = ent;
70827b15
LT
108}
109
110static int cmdname_compare(const void *a_, const void *b_)
111{
112 struct cmdname *a = *(struct cmdname **)a_;
113 struct cmdname *b = *(struct cmdname **)b_;
114 return strcmp(a->name, b->name);
115}
116
1eb05690
SP
117static void uniq(struct cmdnames *cmds)
118{
119 int i, j;
120
121 if (!cmds->cnt)
122 return;
123
124 for (i = j = 1; i < cmds->cnt; i++)
125 if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
126 cmds->names[j++] = cmds->names[i];
127
128 cmds->cnt = j;
129}
130
f3fa1838
JH
131static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
132{
1eb05690
SP
133 int ci, cj, ei;
134 int cmp;
135
136 ci = cj = ei = 0;
137 while (ci < cmds->cnt && ei < excludes->cnt) {
138 cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
139 if (cmp < 0)
140 cmds->names[cj++] = cmds->names[ci++];
141 else if (cmp == 0)
142 ci++, ei++;
143 else if (cmp > 0)
144 ei++;
145 }
146
147 while (ci < cmds->cnt)
148 cmds->names[cj++] = cmds->names[ci++];
149
150 cmds->cnt = cj;
151}
152
153static void pretty_print_string_list(struct cmdnames *cmds, int longest)
70827b15
LT
154{
155 int cols = 1, rows;
156 int space = longest + 1; /* min 1 SP between words */
157 int max_cols = term_columns() - 1; /* don't print *on* the edge */
158 int i, j;
159
160 if (space < max_cols)
161 cols = max_cols / space;
1eb05690 162 rows = (cmds->cnt + cols - 1) / cols;
70827b15
LT
163
164 for (i = 0; i < rows; i++) {
165 printf(" ");
166
167 for (j = 0; j < cols; j++) {
168 int n = j * rows + i;
169 int size = space;
1eb05690 170 if (n >= cmds->cnt)
70827b15 171 break;
1eb05690 172 if (j == cols-1 || n + rows >= cmds->cnt)
70827b15 173 size = 1;
1eb05690 174 printf("%-*s", size, cmds->names[n]->name);
70827b15
LT
175 }
176 putchar('\n');
177 }
178}
179
1eb05690
SP
180static unsigned int list_commands_in_dir(struct cmdnames *cmds,
181 const char *path)
70827b15
LT
182{
183 unsigned int longest = 0;
edb6ddc5
SP
184 const char *prefix = "git-";
185 int prefix_len = strlen(prefix);
1eb05690 186 DIR *dir = opendir(path);
70827b15
LT
187 struct dirent *de;
188
1eb05690
SP
189 if (!dir || chdir(path))
190 return 0;
70827b15 191
70827b15
LT
192 while ((de = readdir(dir)) != NULL) {
193 struct stat st;
194 int entlen;
195
edb6ddc5 196 if (prefixcmp(de->d_name, prefix))
70827b15 197 continue;
0966003c
SP
198
199 if (stat(de->d_name, &st) || /* stat, not lstat */
70827b15
LT
200 !S_ISREG(st.st_mode) ||
201 !(st.st_mode & S_IXUSR))
202 continue;
203
edb6ddc5 204 entlen = strlen(de->d_name) - prefix_len;
5bb1cda5 205 if (has_extension(de->d_name, ".exe"))
70827b15
LT
206 entlen -= 4;
207
208 if (longest < entlen)
209 longest = entlen;
210
1eb05690 211 add_cmdname(cmds, de->d_name + prefix_len, entlen);
70827b15
LT
212 }
213 closedir(dir);
214
1eb05690
SP
215 return longest;
216}
217
2156435f 218static unsigned int load_command_list(void)
1eb05690
SP
219{
220 unsigned int longest = 0;
221 unsigned int len;
222 const char *env_path = getenv("PATH");
223 char *paths, *path, *colon;
224 const char *exec_path = git_exec_path();
225
226 if (exec_path)
227 longest = list_commands_in_dir(&main_cmds, exec_path);
228
229 if (!env_path) {
230 fprintf(stderr, "PATH not set\n");
231 exit(1);
232 }
233
234 path = paths = xstrdup(env_path);
235 while (1) {
236 if ((colon = strchr(path, ':')))
237 *colon = 0;
238
239 len = list_commands_in_dir(&other_cmds, path);
240 if (len > longest)
241 longest = len;
242
243 if (!colon)
244 break;
245 path = colon + 1;
246 }
247 free(paths);
248
249 qsort(main_cmds.names, main_cmds.cnt,
250 sizeof(*main_cmds.names), cmdname_compare);
251 uniq(&main_cmds);
252
253 qsort(other_cmds.names, other_cmds.cnt,
254 sizeof(*other_cmds.names), cmdname_compare);
255 uniq(&other_cmds);
256 exclude_cmds(&other_cmds, &main_cmds);
257
2156435f
JK
258 return longest;
259}
260
261static void list_commands(void)
262{
263 unsigned int longest = load_command_list();
264 const char *exec_path = git_exec_path();
265
1eb05690
SP
266 if (main_cmds.cnt) {
267 printf("available git commands in '%s'\n", exec_path);
268 printf("----------------------------");
269 mput_char('-', strlen(exec_path));
270 putchar('\n');
271 pretty_print_string_list(&main_cmds, longest);
272 putchar('\n');
273 }
274
275 if (other_cmds.cnt) {
276 printf("git commands available from elsewhere on your $PATH\n");
277 printf("---------------------------------------------------\n");
278 pretty_print_string_list(&other_cmds, longest);
279 putchar('\n');
280 }
70827b15
LT
281}
282
3d7e2d85 283void list_common_cmds_help(void)
70827b15
LT
284{
285 int i, longest = 0;
286
287 for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
288 if (longest < strlen(common_cmds[i].name))
289 longest = strlen(common_cmds[i].name);
290 }
291
292 puts("The most commonly used git commands are:");
293 for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
4f75b115
RS
294 printf(" %s ", common_cmds[i].name);
295 mput_char(' ', longest - strlen(common_cmds[i].name));
70827b15
LT
296 puts(common_cmds[i].help);
297 }
70827b15
LT
298}
299
2156435f
JK
300static int is_in_cmdlist(struct cmdnames *c, const char *s)
301{
302 int i;
303 for (i = 0; i < c->cnt; i++)
304 if (!strcmp(s, c->names[i]->name))
305 return 1;
306 return 0;
307}
308
309static int is_git_command(const char *s)
310{
311 load_command_list();
312 return is_in_cmdlist(&main_cmds, s) ||
313 is_in_cmdlist(&other_cmds, s);
314}
315
df55c9cb 316static const char *cmd_to_page(const char *git_cmd)
70827b15 317{
5d6491c7
CC
318 if (!git_cmd)
319 return "git";
320 else if (!prefixcmp(git_cmd, "git"))
df55c9cb 321 return git_cmd;
70827b15
LT
322 else {
323 int page_len = strlen(git_cmd) + 4;
2d7320d0 324 char *p = xmalloc(page_len + 1);
70827b15
LT
325 strcpy(p, "git-");
326 strcpy(p + 4, git_cmd);
327 p[page_len] = 0;
df55c9cb 328 return p;
70827b15 329 }
df55c9cb 330}
70827b15 331
8e566f24
SO
332static void setup_man_path(void)
333{
334 struct strbuf new_path;
335 const char *old_path = getenv("MANPATH");
336
337 strbuf_init(&new_path, 0);
338
339 /* We should always put ':' after our path. If there is no
340 * old_path, the ':' at the end will let 'man' to try
341 * system-wide paths after ours to find the manual page. If
342 * there is old_path, we need ':' as delimiter. */
343 strbuf_addstr(&new_path, GIT_MAN_PATH);
344 strbuf_addch(&new_path, ':');
345 if (old_path)
346 strbuf_addstr(&new_path, old_path);
347
348 setenv("MANPATH", new_path.buf, 1);
349
350 strbuf_release(&new_path);
351}
352
64949984
CC
353static int check_emacsclient_version(void)
354{
355 struct strbuf buffer = STRBUF_INIT;
356 struct child_process ec_process;
357 const char *argv_ec[] = { "emacsclient", "--version", NULL };
358 int version;
359
360 /* emacsclient prints its version number on stderr */
361 memset(&ec_process, 0, sizeof(ec_process));
362 ec_process.argv = argv_ec;
363 ec_process.err = -1;
364 ec_process.stdout_to_stderr = 1;
365 if (start_command(&ec_process)) {
366 fprintf(stderr, "Failed to start emacsclient.\n");
367 return -1;
368 }
369 strbuf_read(&buffer, ec_process.err, 20);
370 close(ec_process.err);
371
372 /*
373 * Don't bother checking return value, because "emacsclient --version"
374 * seems to always exits with code 1.
375 */
376 finish_command(&ec_process);
377
378 if (prefixcmp(buffer.buf, "emacsclient")) {
379 fprintf(stderr, "Failed to parse emacsclient version.\n");
380 strbuf_release(&buffer);
381 return -1;
382 }
383
384 strbuf_remove(&buffer, 0, strlen("emacsclient"));
385 version = atoi(buffer.buf);
386
387 if (version < 22) {
388 fprintf(stderr,
389 "emacsclient version '%d' too old (< 22).\n",
390 version);
391 strbuf_release(&buffer);
392 return -1;
393 }
394
395 strbuf_release(&buffer);
396 return 0;
397}
398
399static void exec_woman_emacs(const char *page)
400{
401 if (!check_emacsclient_version()) {
402 /* This works only with emacsclient version >= 22. */
403 struct strbuf man_page = STRBUF_INIT;
404 strbuf_addf(&man_page, "(woman \"%s\")", page);
405 execlp("emacsclient", "emacsclient", "-e", man_page.buf, NULL);
406 } else
407 execlp("man", "man", page, NULL);
408}
409
410static void exec_man_konqueror(const char *page)
411{
412 const char *display = getenv("DISPLAY");
413 if (display && *display) {
414 struct strbuf man_page = STRBUF_INIT;
415 strbuf_addf(&man_page, "man:%s(1)", page);
416 execlp("kfmclient", "kfmclient", "newTab", man_page.buf, NULL);
417 } else
418 execlp("man", "man", page, NULL);
419}
420
df55c9cb
CC
421static void show_man_page(const char *git_cmd)
422{
423 const char *page = cmd_to_page(git_cmd);
8e566f24 424 setup_man_path();
64949984
CC
425 if (!man_viewer || !strcmp(man_viewer, "man"))
426 execlp("man", "man", page, NULL);
427 if (!strcmp(man_viewer, "woman"))
428 exec_woman_emacs(page);
429 if (!strcmp(man_viewer, "konqueror"))
430 exec_man_konqueror(page);
431 die("'%s': unsupported man viewer.", man_viewer);
70827b15
LT
432}
433
df55c9cb
CC
434static void show_info_page(const char *git_cmd)
435{
436 const char *page = cmd_to_page(git_cmd);
a149a1a4 437 setenv("INFOPATH", GIT_INFO_PATH, 1);
78d39f98 438 execlp("info", "info", "gitman", page, NULL);
df55c9cb
CC
439}
440
482cce82
CC
441static void get_html_page_path(struct strbuf *page_path, const char *page)
442{
443 struct stat st;
444
445 /* Check that we have a git documentation directory. */
446 if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
447 die("'%s': not a documentation directory.", GIT_HTML_PATH);
448
449 strbuf_init(page_path, 0);
450 strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
451}
452
5d6491c7
CC
453static void show_html_page(const char *git_cmd)
454{
455 const char *page = cmd_to_page(git_cmd);
482cce82
CC
456 struct strbuf page_path; /* it leaks but we exec bellow */
457
458 get_html_page_path(&page_path, page);
459
5884f1fe 460 execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
5d6491c7
CC
461}
462
822a7d50
RJ
463void help_unknown_cmd(const char *cmd)
464{
a238917b 465 fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
822a7d50
RJ
466 exit(1);
467}
468
a633fca0 469int cmd_version(int argc, const char **argv, const char *prefix)
70827b15
LT
470{
471 printf("git version %s\n", git_version_string);
472 return 0;
473}
474
5d6491c7 475int cmd_help(int argc, const char **argv, const char *prefix)
70827b15 476{
41eb33bd 477 int nongit;
2156435f 478 const char *alias;
5d6491c7 479
41eb33bd
JK
480 setup_git_directory_gently(&nongit);
481 git_config(git_help_config);
822a7d50 482
41eb33bd
JK
483 argc = parse_options(argc, argv, builtin_help_options,
484 builtin_help_usage, 0);
485
486 if (show_all) {
822a7d50 487 printf("usage: %s\n\n", git_usage_string);
1eb05690 488 list_commands();
41eb33bd 489 return 0;
822a7d50 490 }
df55c9cb 491
41eb33bd
JK
492 if (!argv[0]) {
493 printf("usage: %s\n\n", git_usage_string);
494 list_common_cmds_help();
495 return 0;
70087cdb
CC
496 }
497
2156435f
JK
498 alias = alias_lookup(argv[0]);
499 if (alias && !is_git_command(argv[0])) {
500 printf("`git %s' is aliased to `%s'\n", argv[0], alias);
501 return 0;
502 }
503
41eb33bd
JK
504 switch (help_format) {
505 case HELP_FORMAT_MAN:
506 show_man_page(argv[0]);
507 break;
508 case HELP_FORMAT_INFO:
509 show_info_page(argv[0]);
510 break;
511 case HELP_FORMAT_WEB:
512 show_html_page(argv[0]);
513 break;
70087cdb 514 }
822a7d50 515
70827b15
LT
516 return 0;
517}