]> asedeno.scripts.mit.edu Git - git.git/blobdiff - help.c
builtin-help: make some internal functions available to other builtins
[git.git] / help.c
diff --git a/help.c b/help.c
index 10298fb0a197f18008783214de11e424fbadb77d..88c0d5b34046550009686acb527bea7beef0e578 100644 (file)
--- a/help.c
+++ b/help.c
@@ -9,12 +9,19 @@
 #include "common-cmds.h"
 #include "parse-options.h"
 #include "run-command.h"
+#include "help.h"
 
 static struct man_viewer_list {
-       void (*exec)(const char *);
        struct man_viewer_list *next;
+       char name[FLEX_ARRAY];
 } *man_viewer_list;
 
+static struct man_viewer_info_list {
+       struct man_viewer_info_list *next;
+       const char *info;
+       char name[FLEX_ARRAY];
+} *man_viewer_info_list;
+
 enum help_format {
        HELP_FORMAT_MAN,
        HELP_FORMAT_INFO,
@@ -34,7 +41,7 @@ static struct option builtin_help_options[] = {
 };
 
 static const char * const builtin_help_usage[] = {
-       "git-help [--all] [--man|--web|--info] [command]",
+       "git help [--all] [--man|--web|--info] [command]",
        NULL
 };
 
@@ -49,6 +56,18 @@ static enum help_format parse_help_format(const char *format)
        die("unrecognized help format '%s'", format);
 }
 
+static const char *get_man_viewer_info(const char *name)
+{
+       struct man_viewer_info_list *viewer;
+
+       for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
+       {
+               if (!strcasecmp(name, viewer->name))
+                       return viewer->info;
+       }
+       return NULL;
+}
+
 static int check_emacsclient_version(void)
 {
        struct strbuf buffer = STRBUF_INIT;
@@ -95,57 +114,146 @@ static int check_emacsclient_version(void)
        return 0;
 }
 
-static void exec_woman_emacs(const char *page)
+static void exec_woman_emacs(const char* path, const char *page)
 {
        if (!check_emacsclient_version()) {
                /* This works only with emacsclient version >= 22. */
                struct strbuf man_page = STRBUF_INIT;
+
+               if (!path)
+                       path = "emacsclient";
                strbuf_addf(&man_page, "(woman \"%s\")", page);
-               execlp("emacsclient", "emacsclient", "-e", man_page.buf, NULL);
+               execlp(path, "emacsclient", "-e", man_page.buf, NULL);
+               warning("failed to exec '%s': %s", path, strerror(errno));
        }
 }
 
-static void exec_man_konqueror(const char *page)
+static void exec_man_konqueror(const char* path, const char *page)
 {
        const char *display = getenv("DISPLAY");
        if (display && *display) {
                struct strbuf man_page = STRBUF_INIT;
+               const char *filename = "kfmclient";
+
+               /* It's simpler to launch konqueror using kfmclient. */
+               if (path) {
+                       const char *file = strrchr(path, '/');
+                       if (file && !strcmp(file + 1, "konqueror")) {
+                               char *new = xstrdup(path);
+                               char *dest = strrchr(new, '/');
+
+                               /* strlen("konqueror") == strlen("kfmclient") */
+                               strcpy(dest + 1, "kfmclient");
+                               path = new;
+                       }
+                       if (file)
+                               filename = file;
+               } else
+                       path = "kfmclient";
                strbuf_addf(&man_page, "man:%s(1)", page);
-               execlp("kfmclient", "kfmclient", "newTab", man_page.buf, NULL);
+               execlp(path, filename, "newTab", man_page.buf, NULL);
+               warning("failed to exec '%s': %s", path, strerror(errno));
        }
 }
 
-static void exec_man_man(const char *page)
+static void exec_man_man(const char* path, const char *page)
 {
-       execlp("man", "man", page, NULL);
+       if (!path)
+               path = "man";
+       execlp(path, "man", page, NULL);
+       warning("failed to exec '%s': %s", path, strerror(errno));
 }
 
-static void do_add_man_viewer(void (*exec)(const char *))
+static void exec_man_cmd(const char *cmd, const char *page)
+{
+       struct strbuf shell_cmd = STRBUF_INIT;
+       strbuf_addf(&shell_cmd, "%s %s", cmd, page);
+       execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
+       warning("failed to exec '%s': %s", cmd, strerror(errno));
+}
+
+static void add_man_viewer(const char *name)
 {
        struct man_viewer_list **p = &man_viewer_list;
+       size_t len = strlen(name);
 
        while (*p)
                p = &((*p)->next);
-       *p = xmalloc(sizeof(**p));
-       (*p)->next = NULL;
-       (*p)->exec = exec;
+       *p = xcalloc(1, (sizeof(**p) + len + 1));
+       strncpy((*p)->name, name, len);
+}
+
+static int supported_man_viewer(const char *name, size_t len)
+{
+       return (!strncasecmp("man", name, len) ||
+               !strncasecmp("woman", name, len) ||
+               !strncasecmp("konqueror", name, len));
 }
 
-static int add_man_viewer(const char *value)
+static void do_add_man_viewer_info(const char *name,
+                                  size_t len,
+                                  const char *value)
 {
-       if (!strcasecmp(value, "man"))
-               do_add_man_viewer(exec_man_man);
-       else if (!strcasecmp(value, "woman"))
-               do_add_man_viewer(exec_woman_emacs);
-       else if (!strcasecmp(value, "konqueror"))
-               do_add_man_viewer(exec_man_konqueror);
+       struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
+
+       strncpy(new->name, name, len);
+       new->info = xstrdup(value);
+       new->next = man_viewer_info_list;
+       man_viewer_info_list = new;
+}
+
+static int add_man_viewer_path(const char *name,
+                              size_t len,
+                              const char *value)
+{
+       if (supported_man_viewer(name, len))
+               do_add_man_viewer_info(name, len, value);
+       else
+               warning("'%s': path for unsupported man viewer.\n"
+                       "Please consider using 'man.<tool>.cmd' instead.",
+                       name);
+
+       return 0;
+}
+
+static int add_man_viewer_cmd(const char *name,
+                             size_t len,
+                             const char *value)
+{
+       if (supported_man_viewer(name, len))
+               warning("'%s': cmd for supported man viewer.\n"
+                       "Please consider using 'man.<tool>.path' instead.",
+                       name);
        else
-               warning("'%s': unsupported man viewer.", value);
+               do_add_man_viewer_info(name, len, value);
 
        return 0;
 }
 
-static int git_help_config(const char *var, const char *value)
+static int add_man_viewer_info(const char *var, const char *value)
+{
+       const char *name = var + 4;
+       const char *subkey = strrchr(name, '.');
+
+       if (!subkey)
+               return error("Config with no key for man viewer: %s", name);
+
+       if (!strcmp(subkey, ".path")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return add_man_viewer_path(name, subkey - name, value);
+       }
+       if (!strcmp(subkey, ".cmd")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return add_man_viewer_cmd(name, subkey - name, value);
+       }
+
+       warning("'%s': unsupported man viewer sub key.", subkey);
+       return 0;
+}
+
+static int git_help_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "help.format")) {
                if (!value)
@@ -156,9 +264,13 @@ static int git_help_config(const char *var, const char *value)
        if (!strcmp(var, "man.viewer")) {
                if (!value)
                        return config_error_nonbool(var);
-               return add_man_viewer(value);
+               add_man_viewer(value);
+               return 0;
        }
-       return git_default_config(var, value);
+       if (!prefixcmp(var, "man."))
+               return add_man_viewer_info(var, value);
+
+       return git_default_config(var, value, cb);
 }
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
@@ -189,18 +301,11 @@ static inline void mput_char(char c, unsigned int num)
                putchar(c);
 }
 
-static struct cmdnames {
-       int alloc;
-       int cnt;
-       struct cmdname {
-               size_t len;
-               char name[1];
-       } **names;
-} main_cmds, other_cmds;
+struct cmdnames main_cmds, other_cmds;
 
-static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
+void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
-       struct cmdname *ent = xmalloc(sizeof(*ent) + len);
+       struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1);
 
        ent->len = len;
        memcpy(ent->name, name, len);
@@ -231,7 +336,7 @@ static void uniq(struct cmdnames *cmds)
        cmds->cnt = j;
 }
 
-static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
 {
        int ci, cj, ei;
        int cmp;
@@ -280,28 +385,61 @@ static void pretty_print_string_list(struct cmdnames *cmds, int longest)
        }
 }
 
+static int is_executable(const char *name)
+{
+       struct stat st;
+
+       if (stat(name, &st) || /* stat, not lstat */
+           !S_ISREG(st.st_mode))
+               return 0;
+
+#ifdef __MINGW32__
+       /* cannot trust the executable bit, peek into the file instead */
+       char buf[3] = { 0 };
+       int n;
+       int fd = open(name, O_RDONLY);
+       st.st_mode &= ~S_IXUSR;
+       if (fd >= 0) {
+               n = read(fd, buf, 2);
+               if (n == 2)
+                       /* DOS executables start with "MZ" */
+                       if (!strcmp(buf, "#!") || !strcmp(buf, "MZ"))
+                               st.st_mode |= S_IXUSR;
+               close(fd);
+       }
+#endif
+       return st.st_mode & S_IXUSR;
+}
+
 static unsigned int list_commands_in_dir(struct cmdnames *cmds,
-                                        const char *path)
+                                        const char *path,
+                                        const char *prefix)
 {
        unsigned int longest = 0;
-       const char *prefix = "git-";
-       int prefix_len = strlen(prefix);
+       int prefix_len;
        DIR *dir = opendir(path);
        struct dirent *de;
+       struct strbuf buf = STRBUF_INIT;
+       int len;
 
-       if (!dir || chdir(path))
+       if (!dir)
                return 0;
+       if (!prefix)
+               prefix = "git-";
+       prefix_len = strlen(prefix);
+
+       strbuf_addf(&buf, "%s/", path);
+       len = buf.len;
 
        while ((de = readdir(dir)) != NULL) {
-               struct stat st;
                int entlen;
 
                if (prefixcmp(de->d_name, prefix))
                        continue;
 
-               if (stat(de->d_name, &st) || /* stat, not lstat */
-                   !S_ISREG(st.st_mode) ||
-                   !(st.st_mode & S_IXUSR))
+               strbuf_setlen(&buf, len);
+               strbuf_addstr(&buf, de->d_name);
+               if (!is_executable(buf.buf))
                        continue;
 
                entlen = strlen(de->d_name) - prefix_len;
@@ -314,11 +452,14 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
                add_cmdname(cmds, de->d_name + prefix_len, entlen);
        }
        closedir(dir);
+       strbuf_release(&buf);
 
        return longest;
 }
 
-static unsigned int load_command_list(void)
+unsigned int load_command_list(const char *prefix,
+               struct cmdnames *main_cmds,
+               struct cmdnames *other_cmds)
 {
        unsigned int longest = 0;
        unsigned int len;
@@ -327,7 +468,7 @@ static unsigned int load_command_list(void)
        const char *exec_path = git_exec_path();
 
        if (exec_path)
-               longest = list_commands_in_dir(&main_cmds, exec_path);
+               longest = list_commands_in_dir(main_cmds, exec_path, prefix);
 
        if (!env_path) {
                fprintf(stderr, "PATH not set\n");
@@ -336,10 +477,10 @@ static unsigned int load_command_list(void)
 
        path = paths = xstrdup(env_path);
        while (1) {
-               if ((colon = strchr(path, ':')))
+               if ((colon = strchr(path, PATH_SEP)))
                        *colon = 0;
 
-               len = list_commands_in_dir(&other_cmds, path);
+               len = list_commands_in_dir(other_cmds, path, prefix);
                if (len > longest)
                        longest = len;
 
@@ -349,36 +490,38 @@ static unsigned int load_command_list(void)
        }
        free(paths);
 
-       qsort(main_cmds.names, main_cmds.cnt,
-             sizeof(*main_cmds.names), cmdname_compare);
-       uniq(&main_cmds);
+       qsort(main_cmds->names, main_cmds->cnt,
+             sizeof(*main_cmds->names), cmdname_compare);
+       uniq(main_cmds);
 
-       qsort(other_cmds.names, other_cmds.cnt,
-             sizeof(*other_cmds.names), cmdname_compare);
-       uniq(&other_cmds);
-       exclude_cmds(&other_cmds, &main_cmds);
+       qsort(other_cmds->names, other_cmds->cnt,
+             sizeof(*other_cmds->names), cmdname_compare);
+       uniq(other_cmds);
+       exclude_cmds(other_cmds, main_cmds);
 
        return longest;
 }
 
-static void list_commands(void)
+void list_commands(const char *title, unsigned int longest,
+               struct cmdnames *main_cmds, struct cmdnames *other_cmds)
 {
-       unsigned int longest = load_command_list();
        const char *exec_path = git_exec_path();
 
-       if (main_cmds.cnt) {
-               printf("available git commands in '%s'\n", exec_path);
-               printf("----------------------------");
-               mput_char('-', strlen(exec_path));
+       if (main_cmds->cnt) {
+               printf("available %s in '%s'\n", title, exec_path);
+               printf("----------------");
+               mput_char('-', strlen(title) + strlen(exec_path));
                putchar('\n');
-               pretty_print_string_list(&main_cmds, longest);
+               pretty_print_string_list(main_cmds, longest);
                putchar('\n');
        }
 
-       if (other_cmds.cnt) {
-               printf("git commands available from elsewhere on your $PATH\n");
-               printf("---------------------------------------------------\n");
-               pretty_print_string_list(&other_cmds, longest);
+       if (other_cmds->cnt) {
+               printf("%s available from elsewhere on your $PATH\n", title);
+               printf("---------------------------------------");
+               mput_char('-', strlen(title));
+               putchar('\n');
+               pretty_print_string_list(other_cmds, longest);
                putchar('\n');
        }
 }
@@ -400,7 +543,7 @@ void list_common_cmds_help(void)
        }
 }
 
-static int is_in_cmdlist(struct cmdnames *c, const char *s)
+int is_in_cmdlist(struct cmdnames *c, const char *s)
 {
        int i;
        for (i = 0; i < c->cnt; i++)
@@ -411,25 +554,30 @@ static int is_in_cmdlist(struct cmdnames *c, const char *s)
 
 static int is_git_command(const char *s)
 {
-       load_command_list();
        return is_in_cmdlist(&main_cmds, s) ||
                is_in_cmdlist(&other_cmds, s);
 }
 
+static const char *prepend(const char *prefix, const char *cmd)
+{
+       size_t pre_len = strlen(prefix);
+       size_t cmd_len = strlen(cmd);
+       char *p = xmalloc(pre_len + cmd_len + 1);
+       memcpy(p, prefix, pre_len);
+       strcpy(p + pre_len, cmd);
+       return p;
+}
+
 static const char *cmd_to_page(const char *git_cmd)
 {
        if (!git_cmd)
                return "git";
        else if (!prefixcmp(git_cmd, "git"))
                return git_cmd;
-       else {
-               int page_len = strlen(git_cmd) + 4;
-               char *p = xmalloc(page_len + 1);
-               strcpy(p, "git-");
-               strcpy(p + 4, git_cmd);
-               p[page_len] = 0;
-               return p;
-       }
+       else if (is_git_command(git_cmd))
+               return prepend("git-", git_cmd);
+       else
+               return prepend("git", git_cmd);
 }
 
 static void setup_man_path(void)
@@ -453,6 +601,22 @@ static void setup_man_path(void)
        strbuf_release(&new_path);
 }
 
+static void exec_viewer(const char *name, const char *page)
+{
+       const char *info = get_man_viewer_info(name);
+
+       if (!strcasecmp(name, "man"))
+               exec_man_man(info, page);
+       else if (!strcasecmp(name, "woman"))
+               exec_woman_emacs(info, page);
+       else if (!strcasecmp(name, "konqueror"))
+               exec_man_konqueror(info, page);
+       else if (info)
+               exec_man_cmd(info, page);
+       else
+               warning("'%s': unknown man viewer.", name);
+}
+
 static void show_man_page(const char *git_cmd)
 {
        struct man_viewer_list *viewer;
@@ -461,9 +625,9 @@ static void show_man_page(const char *git_cmd)
        setup_man_path();
        for (viewer = man_viewer_list; viewer; viewer = viewer->next)
        {
-               viewer->exec(page); /* will return when unable */
+               exec_viewer(viewer->name, page); /* will return when unable */
        }
-       exec_man_man(page);
+       exec_viewer("man", page);
        die("no man viewer handled the request");
 }
 
@@ -477,14 +641,28 @@ static void show_info_page(const char *git_cmd)
 static void get_html_page_path(struct strbuf *page_path, const char *page)
 {
        struct stat st;
+       const char *html_path = system_path(GIT_HTML_PATH);
 
        /* Check that we have a git documentation directory. */
-       if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
-               die("'%s': not a documentation directory.", GIT_HTML_PATH);
+       if (stat(mkpath("%s/git.html", html_path), &st)
+           || !S_ISREG(st.st_mode))
+               die("'%s': not a documentation directory.", html_path);
 
        strbuf_init(page_path, 0);
-       strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+       strbuf_addf(page_path, "%s/%s.html", html_path, page);
+}
+
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+       execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
 }
+#endif
 
 static void show_html_page(const char *git_cmd)
 {
@@ -493,7 +671,7 @@ static void show_html_page(const char *git_cmd)
 
        get_html_page_path(&page_path, page);
 
-       execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+       open_html(page_path.buf);
 }
 
 void help_unknown_cmd(const char *cmd)
@@ -514,20 +692,23 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        const char *alias;
 
        setup_git_directory_gently(&nongit);
-       git_config(git_help_config);
+       git_config(git_help_config, NULL);
 
        argc = parse_options(argc, argv, builtin_help_options,
                        builtin_help_usage, 0);
 
        if (show_all) {
+               unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds);
                printf("usage: %s\n\n", git_usage_string);
-               list_commands();
+               list_commands("git commands", longest, &main_cmds, &other_cmds);
+               printf("%s\n", git_more_info_string);
                return 0;
        }
 
        if (!argv[0]) {
                printf("usage: %s\n\n", git_usage_string);
                list_common_cmds_help();
+               printf("\n%s\n", git_more_info_string);
                return 0;
        }