PageRenderTime 90ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/git-1.7.11.2/builtin/help.c

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