help.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*
  2. * Builtin help command
  3. */
  4. #include "cache.h"
  5. #include "config.h"
  6. #include "builtin.h"
  7. #include "exec-cmd.h"
  8. #include "parse-options.h"
  9. #include "run-command.h"
  10. #include "column.h"
  11. #include "config-list.h"
  12. #include "help.h"
  13. #include "alias.h"
  14. #ifndef DEFAULT_HELP_FORMAT
  15. #define DEFAULT_HELP_FORMAT "man"
  16. #endif
  17. static struct man_viewer_list {
  18. struct man_viewer_list *next;
  19. char name[FLEX_ARRAY];
  20. } *man_viewer_list;
  21. static struct man_viewer_info_list {
  22. struct man_viewer_info_list *next;
  23. const char *info;
  24. char name[FLEX_ARRAY];
  25. } *man_viewer_info_list;
  26. enum help_format {
  27. HELP_FORMAT_NONE,
  28. HELP_FORMAT_MAN,
  29. HELP_FORMAT_INFO,
  30. HELP_FORMAT_WEB
  31. };
  32. static const char *html_path;
  33. static int show_all = 0;
  34. static int show_guides = 0;
  35. static int show_config;
  36. static int verbose = 1;
  37. static unsigned int colopts;
  38. static enum help_format help_format = HELP_FORMAT_NONE;
  39. static int exclude_guides;
  40. static struct option builtin_help_options[] = {
  41. OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
  42. OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
  43. OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
  44. OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")),
  45. OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN),
  46. OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
  47. OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
  48. HELP_FORMAT_WEB),
  49. OPT_SET_INT('i', "info", &help_format, N_("show info page"),
  50. HELP_FORMAT_INFO),
  51. OPT__VERBOSE(&verbose, N_("print command description")),
  52. OPT_END(),
  53. };
  54. static const char * const builtin_help_usage[] = {
  55. N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
  56. NULL
  57. };
  58. struct slot_expansion {
  59. const char *prefix;
  60. const char *placeholder;
  61. void (*fn)(struct string_list *list, const char *prefix);
  62. int found;
  63. };
  64. static void list_config_help(int for_human)
  65. {
  66. struct slot_expansion slot_expansions[] = {
  67. { "advice", "*", list_config_advices },
  68. { "color.branch", "<slot>", list_config_color_branch_slots },
  69. { "color.decorate", "<slot>", list_config_color_decorate_slots },
  70. { "color.diff", "<slot>", list_config_color_diff_slots },
  71. { "color.grep", "<slot>", list_config_color_grep_slots },
  72. { "color.interactive", "<slot>", list_config_color_interactive_slots },
  73. { "color.remote", "<slot>", list_config_color_sideband_slots },
  74. { "color.status", "<slot>", list_config_color_status_slots },
  75. { "fsck", "<msg-id>", list_config_fsck_msg_ids },
  76. { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids },
  77. { NULL, NULL, NULL }
  78. };
  79. const char **p;
  80. struct slot_expansion *e;
  81. struct string_list keys = STRING_LIST_INIT_DUP;
  82. int i;
  83. for (p = config_name_list; *p; p++) {
  84. const char *var = *p;
  85. struct strbuf sb = STRBUF_INIT;
  86. for (e = slot_expansions; e->prefix; e++) {
  87. strbuf_reset(&sb);
  88. strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder);
  89. if (!strcasecmp(var, sb.buf)) {
  90. e->fn(&keys, e->prefix);
  91. e->found++;
  92. break;
  93. }
  94. }
  95. strbuf_release(&sb);
  96. if (!e->prefix)
  97. string_list_append(&keys, var);
  98. }
  99. for (e = slot_expansions; e->prefix; e++)
  100. if (!e->found)
  101. BUG("slot_expansion %s.%s is not used",
  102. e->prefix, e->placeholder);
  103. string_list_sort(&keys);
  104. for (i = 0; i < keys.nr; i++) {
  105. const char *var = keys.items[i].string;
  106. const char *wildcard, *tag, *cut;
  107. if (for_human) {
  108. puts(var);
  109. continue;
  110. }
  111. wildcard = strchr(var, '*');
  112. tag = strchr(var, '<');
  113. if (!wildcard && !tag) {
  114. puts(var);
  115. continue;
  116. }
  117. if (wildcard && !tag)
  118. cut = wildcard;
  119. else if (!wildcard && tag)
  120. cut = tag;
  121. else
  122. cut = wildcard < tag ? wildcard : tag;
  123. /*
  124. * We may produce duplicates, but that's up to
  125. * git-completion.bash to handle
  126. */
  127. printf("%.*s\n", (int)(cut - var), var);
  128. }
  129. string_list_clear(&keys, 0);
  130. }
  131. static enum help_format parse_help_format(const char *format)
  132. {
  133. if (!strcmp(format, "man"))
  134. return HELP_FORMAT_MAN;
  135. if (!strcmp(format, "info"))
  136. return HELP_FORMAT_INFO;
  137. if (!strcmp(format, "web") || !strcmp(format, "html"))
  138. return HELP_FORMAT_WEB;
  139. /*
  140. * Please update _git_config() in git-completion.bash when you
  141. * add new help formats.
  142. */
  143. die(_("unrecognized help format '%s'"), format);
  144. }
  145. static const char *get_man_viewer_info(const char *name)
  146. {
  147. struct man_viewer_info_list *viewer;
  148. for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
  149. {
  150. if (!strcasecmp(name, viewer->name))
  151. return viewer->info;
  152. }
  153. return NULL;
  154. }
  155. static int check_emacsclient_version(void)
  156. {
  157. struct strbuf buffer = STRBUF_INIT;
  158. struct child_process ec_process = CHILD_PROCESS_INIT;
  159. const char *argv_ec[] = { "emacsclient", "--version", NULL };
  160. int version;
  161. /* emacsclient prints its version number on stderr */
  162. ec_process.argv = argv_ec;
  163. ec_process.err = -1;
  164. ec_process.stdout_to_stderr = 1;
  165. if (start_command(&ec_process))
  166. return error(_("Failed to start emacsclient."));
  167. strbuf_read(&buffer, ec_process.err, 20);
  168. close(ec_process.err);
  169. /*
  170. * Don't bother checking return value, because "emacsclient --version"
  171. * seems to always exits with code 1.
  172. */
  173. finish_command(&ec_process);
  174. if (!starts_with(buffer.buf, "emacsclient")) {
  175. strbuf_release(&buffer);
  176. return error(_("Failed to parse emacsclient version."));
  177. }
  178. strbuf_remove(&buffer, 0, strlen("emacsclient"));
  179. version = atoi(buffer.buf);
  180. if (version < 22) {
  181. strbuf_release(&buffer);
  182. return error(_("emacsclient version '%d' too old (< 22)."),
  183. version);
  184. }
  185. strbuf_release(&buffer);
  186. return 0;
  187. }
  188. static void exec_woman_emacs(const char *path, const char *page)
  189. {
  190. if (!check_emacsclient_version()) {
  191. /* This works only with emacsclient version >= 22. */
  192. struct strbuf man_page = STRBUF_INIT;
  193. if (!path)
  194. path = "emacsclient";
  195. strbuf_addf(&man_page, "(woman \"%s\")", page);
  196. execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
  197. warning_errno(_("failed to exec '%s'"), path);
  198. strbuf_release(&man_page);
  199. }
  200. }
  201. static void exec_man_konqueror(const char *path, const char *page)
  202. {
  203. const char *display = getenv("DISPLAY");
  204. if (display && *display) {
  205. struct strbuf man_page = STRBUF_INIT;
  206. const char *filename = "kfmclient";
  207. /* It's simpler to launch konqueror using kfmclient. */
  208. if (path) {
  209. size_t len;
  210. if (strip_suffix(path, "/konqueror", &len))
  211. path = xstrfmt("%.*s/kfmclient", (int)len, path);
  212. filename = basename((char *)path);
  213. } else
  214. path = "kfmclient";
  215. strbuf_addf(&man_page, "man:%s(1)", page);
  216. execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
  217. warning_errno(_("failed to exec '%s'"), path);
  218. strbuf_release(&man_page);
  219. }
  220. }
  221. static void exec_man_man(const char *path, const char *page)
  222. {
  223. if (!path)
  224. path = "man";
  225. execlp(path, "man", page, (char *)NULL);
  226. warning_errno(_("failed to exec '%s'"), path);
  227. }
  228. static void exec_man_cmd(const char *cmd, const char *page)
  229. {
  230. struct strbuf shell_cmd = STRBUF_INIT;
  231. strbuf_addf(&shell_cmd, "%s %s", cmd, page);
  232. execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL);
  233. warning(_("failed to exec '%s'"), cmd);
  234. strbuf_release(&shell_cmd);
  235. }
  236. static void add_man_viewer(const char *name)
  237. {
  238. struct man_viewer_list **p = &man_viewer_list;
  239. while (*p)
  240. p = &((*p)->next);
  241. FLEX_ALLOC_STR(*p, name, name);
  242. }
  243. static int supported_man_viewer(const char *name, size_t len)
  244. {
  245. return (!strncasecmp("man", name, len) ||
  246. !strncasecmp("woman", name, len) ||
  247. !strncasecmp("konqueror", name, len));
  248. }
  249. static void do_add_man_viewer_info(const char *name,
  250. size_t len,
  251. const char *value)
  252. {
  253. struct man_viewer_info_list *new_man_viewer;
  254. FLEX_ALLOC_MEM(new_man_viewer, name, name, len);
  255. new_man_viewer->info = xstrdup(value);
  256. new_man_viewer->next = man_viewer_info_list;
  257. man_viewer_info_list = new_man_viewer;
  258. }
  259. static int add_man_viewer_path(const char *name,
  260. size_t len,
  261. const char *value)
  262. {
  263. if (supported_man_viewer(name, len))
  264. do_add_man_viewer_info(name, len, value);
  265. else
  266. warning(_("'%s': path for unsupported man viewer.\n"
  267. "Please consider using 'man.<tool>.cmd' instead."),
  268. name);
  269. return 0;
  270. }
  271. static int add_man_viewer_cmd(const char *name,
  272. size_t len,
  273. const char *value)
  274. {
  275. if (supported_man_viewer(name, len))
  276. warning(_("'%s': cmd for supported man viewer.\n"
  277. "Please consider using 'man.<tool>.path' instead."),
  278. name);
  279. else
  280. do_add_man_viewer_info(name, len, value);
  281. return 0;
  282. }
  283. static int add_man_viewer_info(const char *var, const char *value)
  284. {
  285. const char *name, *subkey;
  286. size_t namelen;
  287. if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
  288. return 0;
  289. if (!strcmp(subkey, "path")) {
  290. if (!value)
  291. return config_error_nonbool(var);
  292. return add_man_viewer_path(name, namelen, value);
  293. }
  294. if (!strcmp(subkey, "cmd")) {
  295. if (!value)
  296. return config_error_nonbool(var);
  297. return add_man_viewer_cmd(name, namelen, value);
  298. }
  299. return 0;
  300. }
  301. static int git_help_config(const char *var, const char *value, void *cb)
  302. {
  303. if (starts_with(var, "column."))
  304. return git_column_config(var, value, "help", &colopts);
  305. if (!strcmp(var, "help.format")) {
  306. if (!value)
  307. return config_error_nonbool(var);
  308. help_format = parse_help_format(value);
  309. return 0;
  310. }
  311. if (!strcmp(var, "help.htmlpath")) {
  312. if (!value)
  313. return config_error_nonbool(var);
  314. html_path = xstrdup(value);
  315. return 0;
  316. }
  317. if (!strcmp(var, "man.viewer")) {
  318. if (!value)
  319. return config_error_nonbool(var);
  320. add_man_viewer(value);
  321. return 0;
  322. }
  323. if (starts_with(var, "man."))
  324. return add_man_viewer_info(var, value);
  325. return git_default_config(var, value, cb);
  326. }
  327. static struct cmdnames main_cmds, other_cmds;
  328. static int is_git_command(const char *s)
  329. {
  330. if (is_builtin(s))
  331. return 1;
  332. load_command_list("git-", &main_cmds, &other_cmds);
  333. return is_in_cmdlist(&main_cmds, s) ||
  334. is_in_cmdlist(&other_cmds, s);
  335. }
  336. static const char *cmd_to_page(const char *git_cmd)
  337. {
  338. if (!git_cmd)
  339. return "git";
  340. else if (starts_with(git_cmd, "git"))
  341. return git_cmd;
  342. else if (is_git_command(git_cmd))
  343. return xstrfmt("git-%s", git_cmd);
  344. else
  345. return xstrfmt("git%s", git_cmd);
  346. }
  347. static void setup_man_path(void)
  348. {
  349. struct strbuf new_path = STRBUF_INIT;
  350. const char *old_path = getenv("MANPATH");
  351. char *git_man_path = system_path(GIT_MAN_PATH);
  352. /* We should always put ':' after our path. If there is no
  353. * old_path, the ':' at the end will let 'man' to try
  354. * system-wide paths after ours to find the manual page. If
  355. * there is old_path, we need ':' as delimiter. */
  356. strbuf_addstr(&new_path, git_man_path);
  357. strbuf_addch(&new_path, ':');
  358. if (old_path)
  359. strbuf_addstr(&new_path, old_path);
  360. free(git_man_path);
  361. setenv("MANPATH", new_path.buf, 1);
  362. strbuf_release(&new_path);
  363. }
  364. static void exec_viewer(const char *name, const char *page)
  365. {
  366. const char *info = get_man_viewer_info(name);
  367. if (!strcasecmp(name, "man"))
  368. exec_man_man(info, page);
  369. else if (!strcasecmp(name, "woman"))
  370. exec_woman_emacs(info, page);
  371. else if (!strcasecmp(name, "konqueror"))
  372. exec_man_konqueror(info, page);
  373. else if (info)
  374. exec_man_cmd(info, page);
  375. else
  376. warning(_("'%s': unknown man viewer."), name);
  377. }
  378. static void show_man_page(const char *git_cmd)
  379. {
  380. struct man_viewer_list *viewer;
  381. const char *page = cmd_to_page(git_cmd);
  382. const char *fallback = getenv("GIT_MAN_VIEWER");
  383. setup_man_path();
  384. for (viewer = man_viewer_list; viewer; viewer = viewer->next)
  385. {
  386. exec_viewer(viewer->name, page); /* will return when unable */
  387. }
  388. if (fallback)
  389. exec_viewer(fallback, page);
  390. exec_viewer("man", page);
  391. die(_("no man viewer handled the request"));
  392. }
  393. static void show_info_page(const char *git_cmd)
  394. {
  395. const char *page = cmd_to_page(git_cmd);
  396. setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
  397. execlp("info", "info", "gitman", page, (char *)NULL);
  398. die(_("no info viewer handled the request"));
  399. }
  400. static void get_html_page_path(struct strbuf *page_path, const char *page)
  401. {
  402. struct stat st;
  403. char *to_free = NULL;
  404. if (!html_path)
  405. html_path = to_free = system_path(GIT_HTML_PATH);
  406. /* Check that we have a git documentation directory. */
  407. if (!strstr(html_path, "://")) {
  408. if (stat(mkpath("%s/git.html", html_path), &st)
  409. || !S_ISREG(st.st_mode))
  410. die("'%s': not a documentation directory.", html_path);
  411. }
  412. strbuf_init(page_path, 0);
  413. strbuf_addf(page_path, "%s/%s.html", html_path, page);
  414. free(to_free);
  415. }
  416. static void open_html(const char *path)
  417. {
  418. execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
  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. get_html_page_path(&page_path, page);
  425. open_html(page_path.buf);
  426. }
  427. static const char *check_git_cmd(const char* cmd)
  428. {
  429. char *alias;
  430. if (is_git_command(cmd))
  431. return cmd;
  432. alias = alias_lookup(cmd);
  433. if (alias) {
  434. const char **argv;
  435. int count;
  436. /*
  437. * handle_builtin() in git.c rewrites "git cmd --help"
  438. * to "git help --exclude-guides cmd", so we can use
  439. * exclude_guides to distinguish "git cmd --help" from
  440. * "git help cmd". In the latter case, or if cmd is an
  441. * alias for a shell command, just print the alias
  442. * definition.
  443. */
  444. if (!exclude_guides || alias[0] == '!') {
  445. printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
  446. free(alias);
  447. exit(0);
  448. }
  449. /*
  450. * Otherwise, we pretend that the command was "git
  451. * word0 --help". We use split_cmdline() to get the
  452. * first word of the alias, to ensure that we use the
  453. * same rules as when the alias is actually
  454. * used. split_cmdline() modifies alias in-place.
  455. */
  456. fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias);
  457. count = split_cmdline(alias, &argv);
  458. if (count < 0)
  459. die(_("bad alias.%s string: %s"), cmd,
  460. split_cmdline_strerror(count));
  461. free(argv);
  462. UNLEAK(alias);
  463. return alias;
  464. }
  465. if (exclude_guides)
  466. return help_unknown_cmd(cmd);
  467. return cmd;
  468. }
  469. int cmd_help(int argc, const char **argv, const char *prefix)
  470. {
  471. int nongit;
  472. enum help_format parsed_help_format;
  473. argc = parse_options(argc, argv, prefix, builtin_help_options,
  474. builtin_help_usage, 0);
  475. parsed_help_format = help_format;
  476. if (show_all) {
  477. git_config(git_help_config, NULL);
  478. if (verbose) {
  479. setup_pager();
  480. list_all_cmds_help();
  481. return 0;
  482. }
  483. printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
  484. load_command_list("git-", &main_cmds, &other_cmds);
  485. list_commands(colopts, &main_cmds, &other_cmds);
  486. }
  487. if (show_config) {
  488. int for_human = show_config == 1;
  489. if (!for_human) {
  490. list_config_help(for_human);
  491. return 0;
  492. }
  493. setup_pager();
  494. list_config_help(for_human);
  495. printf("\n%s\n", _("'git help config' for more information"));
  496. return 0;
  497. }
  498. if (show_guides)
  499. list_guides_help();
  500. if (show_all || show_guides) {
  501. printf("%s\n", _(git_more_info_string));
  502. /*
  503. * We're done. Ignore any remaining args
  504. */
  505. return 0;
  506. }
  507. if (!argv[0]) {
  508. printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
  509. list_common_cmds_help();
  510. printf("\n%s\n", _(git_more_info_string));
  511. return 0;
  512. }
  513. setup_git_directory_gently(&nongit);
  514. git_config(git_help_config, NULL);
  515. if (parsed_help_format != HELP_FORMAT_NONE)
  516. help_format = parsed_help_format;
  517. if (help_format == HELP_FORMAT_NONE)
  518. help_format = parse_help_format(DEFAULT_HELP_FORMAT);
  519. argv[0] = check_git_cmd(argv[0]);
  520. switch (help_format) {
  521. case HELP_FORMAT_NONE:
  522. case HELP_FORMAT_MAN:
  523. show_man_page(argv[0]);
  524. break;
  525. case HELP_FORMAT_INFO:
  526. show_info_page(argv[0]);
  527. break;
  528. case HELP_FORMAT_WEB:
  529. show_html_page(argv[0]);
  530. break;
  531. }
  532. return 0;
  533. }