Allow help.htmlpath to be a URL prefix
[git] / builtin / help.c
1 /*
2  * builtin-help.c
3  *
4  * Builtin help command
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"
12 #include "column.h"
13 #include "help.h"
14
15 static struct man_viewer_list {
16         struct man_viewer_list *next;
17         char name[FLEX_ARRAY];
18 } *man_viewer_list;
19
20 static struct man_viewer_info_list {
21         struct man_viewer_info_list *next;
22         const char *info;
23         char name[FLEX_ARRAY];
24 } *man_viewer_info_list;
25
26 enum help_format {
27         HELP_FORMAT_NONE,
28         HELP_FORMAT_MAN,
29         HELP_FORMAT_INFO,
30         HELP_FORMAT_WEB
31 };
32
33 static const char *html_path;
34
35 static int show_all = 0;
36 static unsigned int colopts;
37 static enum help_format help_format = HELP_FORMAT_NONE;
38 static struct option builtin_help_options[] = {
39         OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
40         OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
41         OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
42                         HELP_FORMAT_WEB),
43         OPT_SET_INT('i', "info", &help_format, "show info page",
44                         HELP_FORMAT_INFO),
45         OPT_END(),
46 };
47
48 static const char * const builtin_help_usage[] = {
49         "git help [--all] [--man|--web|--info] [command]",
50         NULL
51 };
52
53 static enum help_format parse_help_format(const char *format)
54 {
55         if (!strcmp(format, "man"))
56                 return HELP_FORMAT_MAN;
57         if (!strcmp(format, "info"))
58                 return HELP_FORMAT_INFO;
59         if (!strcmp(format, "web") || !strcmp(format, "html"))
60                 return HELP_FORMAT_WEB;
61         die(_("unrecognized help format '%s'"), format);
62 }
63
64 static const char *get_man_viewer_info(const char *name)
65 {
66         struct man_viewer_info_list *viewer;
67
68         for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
69         {
70                 if (!strcasecmp(name, viewer->name))
71                         return viewer->info;
72         }
73         return NULL;
74 }
75
76 static int check_emacsclient_version(void)
77 {
78         struct strbuf buffer = STRBUF_INIT;
79         struct child_process ec_process;
80         const char *argv_ec[] = { "emacsclient", "--version", NULL };
81         int version;
82
83         /* emacsclient prints its version number on stderr */
84         memset(&ec_process, 0, sizeof(ec_process));
85         ec_process.argv = argv_ec;
86         ec_process.err = -1;
87         ec_process.stdout_to_stderr = 1;
88         if (start_command(&ec_process))
89                 return error(_("Failed to start emacsclient."));
90
91         strbuf_read(&buffer, ec_process.err, 20);
92         close(ec_process.err);
93
94         /*
95          * Don't bother checking return value, because "emacsclient --version"
96          * seems to always exits with code 1.
97          */
98         finish_command(&ec_process);
99
100         if (prefixcmp(buffer.buf, "emacsclient")) {
101                 strbuf_release(&buffer);
102                 return error(_("Failed to parse emacsclient version."));
103         }
104
105         strbuf_remove(&buffer, 0, strlen("emacsclient"));
106         version = atoi(buffer.buf);
107
108         if (version < 22) {
109                 strbuf_release(&buffer);
110                 return error(_("emacsclient version '%d' too old (< 22)."),
111                         version);
112         }
113
114         strbuf_release(&buffer);
115         return 0;
116 }
117
118 static void exec_woman_emacs(const char *path, const char *page)
119 {
120         if (!check_emacsclient_version()) {
121                 /* This works only with emacsclient version >= 22. */
122                 struct strbuf man_page = STRBUF_INIT;
123
124                 if (!path)
125                         path = "emacsclient";
126                 strbuf_addf(&man_page, "(woman \"%s\")", page);
127                 execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
128                 warning(_("failed to exec '%s': %s"), path, strerror(errno));
129         }
130 }
131
132 static void exec_man_konqueror(const char *path, const char *page)
133 {
134         const char *display = getenv("DISPLAY");
135         if (display && *display) {
136                 struct strbuf man_page = STRBUF_INIT;
137                 const char *filename = "kfmclient";
138
139                 /* It's simpler to launch konqueror using kfmclient. */
140                 if (path) {
141                         const char *file = strrchr(path, '/');
142                         if (file && !strcmp(file + 1, "konqueror")) {
143                                 char *new = xstrdup(path);
144                                 char *dest = strrchr(new, '/');
145
146                                 /* strlen("konqueror") == strlen("kfmclient") */
147                                 strcpy(dest + 1, "kfmclient");
148                                 path = new;
149                         }
150                         if (file)
151                                 filename = file;
152                 } else
153                         path = "kfmclient";
154                 strbuf_addf(&man_page, "man:%s(1)", page);
155                 execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
156                 warning(_("failed to exec '%s': %s"), path, strerror(errno));
157         }
158 }
159
160 static void exec_man_man(const char *path, const char *page)
161 {
162         if (!path)
163                 path = "man";
164         execlp(path, "man", page, (char *)NULL);
165         warning(_("failed to exec '%s': %s"), path, strerror(errno));
166 }
167
168 static void exec_man_cmd(const char *cmd, const char *page)
169 {
170         struct strbuf shell_cmd = STRBUF_INIT;
171         strbuf_addf(&shell_cmd, "%s %s", cmd, page);
172         execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
173         warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
174 }
175
176 static void add_man_viewer(const char *name)
177 {
178         struct man_viewer_list **p = &man_viewer_list;
179         size_t len = strlen(name);
180
181         while (*p)
182                 p = &((*p)->next);
183         *p = xcalloc(1, (sizeof(**p) + len + 1));
184         strncpy((*p)->name, name, len);
185 }
186
187 static int supported_man_viewer(const char *name, size_t len)
188 {
189         return (!strncasecmp("man", name, len) ||
190                 !strncasecmp("woman", name, len) ||
191                 !strncasecmp("konqueror", name, len));
192 }
193
194 static void do_add_man_viewer_info(const char *name,
195                                    size_t len,
196                                    const char *value)
197 {
198         struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
199
200         strncpy(new->name, name, len);
201         new->info = xstrdup(value);
202         new->next = man_viewer_info_list;
203         man_viewer_info_list = new;
204 }
205
206 static int add_man_viewer_path(const char *name,
207                                size_t len,
208                                const char *value)
209 {
210         if (supported_man_viewer(name, len))
211                 do_add_man_viewer_info(name, len, value);
212         else
213                 warning(_("'%s': path for unsupported man viewer.\n"
214                           "Please consider using 'man.<tool>.cmd' instead."),
215                         name);
216
217         return 0;
218 }
219
220 static int add_man_viewer_cmd(const char *name,
221                               size_t len,
222                               const char *value)
223 {
224         if (supported_man_viewer(name, len))
225                 warning(_("'%s': cmd for supported man viewer.\n"
226                           "Please consider using 'man.<tool>.path' instead."),
227                         name);
228         else
229                 do_add_man_viewer_info(name, len, value);
230
231         return 0;
232 }
233
234 static int add_man_viewer_info(const char *var, const char *value)
235 {
236         const char *name = var + 4;
237         const char *subkey = strrchr(name, '.');
238
239         if (!subkey)
240                 return 0;
241
242         if (!strcmp(subkey, ".path")) {
243                 if (!value)
244                         return config_error_nonbool(var);
245                 return add_man_viewer_path(name, subkey - name, value);
246         }
247         if (!strcmp(subkey, ".cmd")) {
248                 if (!value)
249                         return config_error_nonbool(var);
250                 return add_man_viewer_cmd(name, subkey - name, value);
251         }
252
253         return 0;
254 }
255
256 static int git_help_config(const char *var, const char *value, void *cb)
257 {
258         if (!prefixcmp(var, "column."))
259                 return git_column_config(var, value, "help", &colopts);
260         if (!strcmp(var, "help.format")) {
261                 if (!value)
262                         return config_error_nonbool(var);
263                 help_format = parse_help_format(value);
264                 return 0;
265         }
266         if (!strcmp(var, "help.htmlpath")) {
267                 if (!value)
268                         return config_error_nonbool(var);
269                 html_path = xstrdup(value);
270                 return 0;
271         }
272         if (!strcmp(var, "man.viewer")) {
273                 if (!value)
274                         return config_error_nonbool(var);
275                 add_man_viewer(value);
276                 return 0;
277         }
278         if (!prefixcmp(var, "man."))
279                 return add_man_viewer_info(var, value);
280
281         return git_default_config(var, value, cb);
282 }
283
284 static struct cmdnames main_cmds, other_cmds;
285
286 void list_common_cmds_help(void)
287 {
288         int i, longest = 0;
289
290         for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
291                 if (longest < strlen(common_cmds[i].name))
292                         longest = strlen(common_cmds[i].name);
293         }
294
295         puts(_("The most commonly used git commands are:"));
296         for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
297                 printf("   %s   ", common_cmds[i].name);
298                 mput_char(' ', longest - strlen(common_cmds[i].name));
299                 puts(_(common_cmds[i].help));
300         }
301 }
302
303 static int is_git_command(const char *s)
304 {
305         return is_in_cmdlist(&main_cmds, s) ||
306                 is_in_cmdlist(&other_cmds, s);
307 }
308
309 static const char *prepend(const char *prefix, const char *cmd)
310 {
311         size_t pre_len = strlen(prefix);
312         size_t cmd_len = strlen(cmd);
313         char *p = xmalloc(pre_len + cmd_len + 1);
314         memcpy(p, prefix, pre_len);
315         strcpy(p + pre_len, cmd);
316         return p;
317 }
318
319 static const char *cmd_to_page(const char *git_cmd)
320 {
321         if (!git_cmd)
322                 return "git";
323         else if (!prefixcmp(git_cmd, "git"))
324                 return git_cmd;
325         else if (is_git_command(git_cmd))
326                 return prepend("git-", git_cmd);
327         else
328                 return prepend("git", git_cmd);
329 }
330
331 static void setup_man_path(void)
332 {
333         struct strbuf new_path = STRBUF_INIT;
334         const char *old_path = getenv("MANPATH");
335
336         /* We should always put ':' after our path. If there is no
337          * old_path, the ':' at the end will let 'man' to try
338          * system-wide paths after ours to find the manual page. If
339          * there is old_path, we need ':' as delimiter. */
340         strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
341         strbuf_addch(&new_path, ':');
342         if (old_path)
343                 strbuf_addstr(&new_path, old_path);
344
345         setenv("MANPATH", new_path.buf, 1);
346
347         strbuf_release(&new_path);
348 }
349
350 static void exec_viewer(const char *name, const char *page)
351 {
352         const char *info = get_man_viewer_info(name);
353
354         if (!strcasecmp(name, "man"))
355                 exec_man_man(info, page);
356         else if (!strcasecmp(name, "woman"))
357                 exec_woman_emacs(info, page);
358         else if (!strcasecmp(name, "konqueror"))
359                 exec_man_konqueror(info, page);
360         else if (info)
361                 exec_man_cmd(info, page);
362         else
363                 warning(_("'%s': unknown man viewer."), name);
364 }
365
366 static void show_man_page(const char *git_cmd)
367 {
368         struct man_viewer_list *viewer;
369         const char *page = cmd_to_page(git_cmd);
370         const char *fallback = getenv("GIT_MAN_VIEWER");
371
372         setup_man_path();
373         for (viewer = man_viewer_list; viewer; viewer = viewer->next)
374         {
375                 exec_viewer(viewer->name, page); /* will return when unable */
376         }
377         if (fallback)
378                 exec_viewer(fallback, page);
379         exec_viewer("man", page);
380         die(_("no man viewer handled the request"));
381 }
382
383 static void show_info_page(const char *git_cmd)
384 {
385         const char *page = cmd_to_page(git_cmd);
386         setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
387         execlp("info", "info", "gitman", page, (char *)NULL);
388         die(_("no info viewer handled the request"));
389 }
390
391 static void get_html_page_path(struct strbuf *page_path, const char *page)
392 {
393         struct stat st;
394         if (!html_path)
395                 html_path = system_path(GIT_HTML_PATH);
396
397         /* Check that we have a git documentation directory. */
398         if (!strstr(html_path, "://")) {
399                 if (stat(mkpath("%s/git.html", html_path), &st)
400                     || !S_ISREG(st.st_mode))
401                         die("'%s': not a documentation directory.", html_path);
402         }
403
404         strbuf_init(page_path, 0);
405         strbuf_addf(page_path, "%s/%s.html", html_path, page);
406 }
407
408 /*
409  * If open_html is not defined in a platform-specific way (see for
410  * example compat/mingw.h), we use the script web--browse to display
411  * HTML.
412  */
413 #ifndef open_html
414 static void open_html(const char *path)
415 {
416         execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
417 }
418 #endif
419
420 static void show_html_page(const char *git_cmd)
421 {
422         const char *page = cmd_to_page(git_cmd);
423         struct strbuf page_path; /* it leaks but we exec bellow */
424
425         get_html_page_path(&page_path, page);
426
427         open_html(page_path.buf);
428 }
429
430 int cmd_help(int argc, const char **argv, const char *prefix)
431 {
432         int nongit;
433         const char *alias;
434         enum help_format parsed_help_format;
435         load_command_list("git-", &main_cmds, &other_cmds);
436
437         argc = parse_options(argc, argv, prefix, builtin_help_options,
438                         builtin_help_usage, 0);
439         parsed_help_format = help_format;
440
441         if (show_all) {
442                 git_config(git_help_config, NULL);
443                 printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
444                 list_commands(colopts, &main_cmds, &other_cmds);
445                 printf("%s\n", _(git_more_info_string));
446                 return 0;
447         }
448
449         if (!argv[0]) {
450                 printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
451                 list_common_cmds_help();
452                 printf("\n%s\n", _(git_more_info_string));
453                 return 0;
454         }
455
456         setup_git_directory_gently(&nongit);
457         git_config(git_help_config, NULL);
458
459         if (parsed_help_format != HELP_FORMAT_NONE)
460                 help_format = parsed_help_format;
461
462         alias = alias_lookup(argv[0]);
463         if (alias && !is_git_command(argv[0])) {
464                 printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
465                 return 0;
466         }
467
468         switch (help_format) {
469         case HELP_FORMAT_NONE:
470         case HELP_FORMAT_MAN:
471                 show_man_page(argv[0]);
472                 break;
473         case HELP_FORMAT_INFO:
474                 show_info_page(argv[0]);
475                 break;
476         case HELP_FORMAT_WEB:
477                 show_html_page(argv[0]);
478                 break;
479         }
480
481         return 0;
482 }