help: add "man.viewer" config var to use "woman" or "konqueror"
[git.git] / help.c
blob2cb152d2e10cad363c639a8e65f37cbd6fc42d64
1 /*
2 * builtin-help.c
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"
10 #include "parse-options.h"
11 #include "run-command.h"
13 static const char *man_viewer;
15 enum help_format {
16 HELP_FORMAT_MAN,
17 HELP_FORMAT_INFO,
18 HELP_FORMAT_WEB,
21 static int show_all = 0;
22 static enum help_format help_format = HELP_FORMAT_MAN;
23 static 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),
32 static const char * const builtin_help_usage[] = {
33 "git-help [--all] [--man|--web|--info] [command]",
34 NULL
37 static enum help_format parse_help_format(const char *format)
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;
45 die("unrecognized help format '%s'", format);
48 static int git_help_config(const char *var, const char *value)
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;
56 if (!strcmp(var, "man.viewer"))
57 return git_config_string(&man_viewer, var, value);
58 return git_default_config(var, value);
61 /* most GUI terminals set COLUMNS (although some don't export it) */
62 static int term_columns(void)
64 char *col_string = getenv("COLUMNS");
65 int n_cols;
67 if (col_string && (n_cols = atoi(col_string)) > 0)
68 return n_cols;
70 #ifdef TIOCGWINSZ
72 struct winsize ws;
73 if (!ioctl(1, TIOCGWINSZ, &ws)) {
74 if (ws.ws_col)
75 return ws.ws_col;
78 #endif
80 return 80;
83 static inline void mput_char(char c, unsigned int num)
85 while(num--)
86 putchar(c);
89 static 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;
98 static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
100 struct cmdname *ent = xmalloc(sizeof(*ent) + len);
102 ent->len = len;
103 memcpy(ent->name, name, len);
104 ent->name[len] = 0;
106 ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
107 cmds->names[cmds->cnt++] = ent;
110 static int cmdname_compare(const void *a_, const void *b_)
112 struct cmdname *a = *(struct cmdname **)a_;
113 struct cmdname *b = *(struct cmdname **)b_;
114 return strcmp(a->name, b->name);
117 static void uniq(struct cmdnames *cmds)
119 int i, j;
121 if (!cmds->cnt)
122 return;
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];
128 cmds->cnt = j;
131 static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
133 int ci, cj, ei;
134 int cmp;
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++;
147 while (ci < cmds->cnt)
148 cmds->names[cj++] = cmds->names[ci++];
150 cmds->cnt = cj;
153 static void pretty_print_string_list(struct cmdnames *cmds, int longest)
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;
160 if (space < max_cols)
161 cols = max_cols / space;
162 rows = (cmds->cnt + cols - 1) / cols;
164 for (i = 0; i < rows; i++) {
165 printf(" ");
167 for (j = 0; j < cols; j++) {
168 int n = j * rows + i;
169 int size = space;
170 if (n >= cmds->cnt)
171 break;
172 if (j == cols-1 || n + rows >= cmds->cnt)
173 size = 1;
174 printf("%-*s", size, cmds->names[n]->name);
176 putchar('\n');
180 static unsigned int list_commands_in_dir(struct cmdnames *cmds,
181 const char *path)
183 unsigned int longest = 0;
184 const char *prefix = "git-";
185 int prefix_len = strlen(prefix);
186 DIR *dir = opendir(path);
187 struct dirent *de;
189 if (!dir || chdir(path))
190 return 0;
192 while ((de = readdir(dir)) != NULL) {
193 struct stat st;
194 int entlen;
196 if (prefixcmp(de->d_name, prefix))
197 continue;
199 if (stat(de->d_name, &st) || /* stat, not lstat */
200 !S_ISREG(st.st_mode) ||
201 !(st.st_mode & S_IXUSR))
202 continue;
204 entlen = strlen(de->d_name) - prefix_len;
205 if (has_extension(de->d_name, ".exe"))
206 entlen -= 4;
208 if (longest < entlen)
209 longest = entlen;
211 add_cmdname(cmds, de->d_name + prefix_len, entlen);
213 closedir(dir);
215 return longest;
218 static unsigned int load_command_list(void)
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();
226 if (exec_path)
227 longest = list_commands_in_dir(&main_cmds, exec_path);
229 if (!env_path) {
230 fprintf(stderr, "PATH not set\n");
231 exit(1);
234 path = paths = xstrdup(env_path);
235 while (1) {
236 if ((colon = strchr(path, ':')))
237 *colon = 0;
239 len = list_commands_in_dir(&other_cmds, path);
240 if (len > longest)
241 longest = len;
243 if (!colon)
244 break;
245 path = colon + 1;
247 free(paths);
249 qsort(main_cmds.names, main_cmds.cnt,
250 sizeof(*main_cmds.names), cmdname_compare);
251 uniq(&main_cmds);
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);
258 return longest;
261 static void list_commands(void)
263 unsigned int longest = load_command_list();
264 const char *exec_path = git_exec_path();
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');
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');
283 void list_common_cmds_help(void)
285 int i, longest = 0;
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);
292 puts("The most commonly used git commands are:");
293 for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
294 printf(" %s ", common_cmds[i].name);
295 mput_char(' ', longest - strlen(common_cmds[i].name));
296 puts(common_cmds[i].help);
300 static int is_in_cmdlist(struct cmdnames *c, const char *s)
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;
309 static int is_git_command(const char *s)
311 load_command_list();
312 return is_in_cmdlist(&main_cmds, s) ||
313 is_in_cmdlist(&other_cmds, s);
316 static const char *cmd_to_page(const char *git_cmd)
318 if (!git_cmd)
319 return "git";
320 else if (!prefixcmp(git_cmd, "git"))
321 return git_cmd;
322 else {
323 int page_len = strlen(git_cmd) + 4;
324 char *p = xmalloc(page_len + 1);
325 strcpy(p, "git-");
326 strcpy(p + 4, git_cmd);
327 p[page_len] = 0;
328 return p;
332 static void setup_man_path(void)
334 struct strbuf new_path;
335 const char *old_path = getenv("MANPATH");
337 strbuf_init(&new_path, 0);
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);
348 setenv("MANPATH", new_path.buf, 1);
350 strbuf_release(&new_path);
353 static int check_emacsclient_version(void)
355 struct strbuf buffer = STRBUF_INIT;
356 struct child_process ec_process;
357 const char *argv_ec[] = { "emacsclient", "--version", NULL };
358 int version;
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;
369 strbuf_read(&buffer, ec_process.err, 20);
370 close(ec_process.err);
373 * Don't bother checking return value, because "emacsclient --version"
374 * seems to always exits with code 1.
376 finish_command(&ec_process);
378 if (prefixcmp(buffer.buf, "emacsclient")) {
379 fprintf(stderr, "Failed to parse emacsclient version.\n");
380 strbuf_release(&buffer);
381 return -1;
384 strbuf_remove(&buffer, 0, strlen("emacsclient"));
385 version = atoi(buffer.buf);
387 if (version < 22) {
388 fprintf(stderr,
389 "emacsclient version '%d' too old (< 22).\n",
390 version);
391 strbuf_release(&buffer);
392 return -1;
395 strbuf_release(&buffer);
396 return 0;
399 static void exec_woman_emacs(const char *page)
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);
410 static void exec_man_konqueror(const char *page)
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);
421 static void show_man_page(const char *git_cmd)
423 const char *page = cmd_to_page(git_cmd);
424 setup_man_path();
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);
434 static void show_info_page(const char *git_cmd)
436 const char *page = cmd_to_page(git_cmd);
437 setenv("INFOPATH", GIT_INFO_PATH, 1);
438 execlp("info", "info", "gitman", page, NULL);
441 static void get_html_page_path(struct strbuf *page_path, const char *page)
443 struct stat st;
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);
449 strbuf_init(page_path, 0);
450 strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
453 static void show_html_page(const char *git_cmd)
455 const char *page = cmd_to_page(git_cmd);
456 struct strbuf page_path; /* it leaks but we exec bellow */
458 get_html_page_path(&page_path, page);
460 execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
463 void help_unknown_cmd(const char *cmd)
465 fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
466 exit(1);
469 int cmd_version(int argc, const char **argv, const char *prefix)
471 printf("git version %s\n", git_version_string);
472 return 0;
475 int cmd_help(int argc, const char **argv, const char *prefix)
477 int nongit;
478 const char *alias;
480 setup_git_directory_gently(&nongit);
481 git_config(git_help_config);
483 argc = parse_options(argc, argv, builtin_help_options,
484 builtin_help_usage, 0);
486 if (show_all) {
487 printf("usage: %s\n\n", git_usage_string);
488 list_commands();
489 return 0;
492 if (!argv[0]) {
493 printf("usage: %s\n\n", git_usage_string);
494 list_common_cmds_help();
495 return 0;
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;
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;
516 return 0;