summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile4
-rw-r--r--builtin-help.c462
-rw-r--r--builtin-merge.c50
-rw-r--r--help.c536
-rw-r--r--help.h29
-rwxr-xr-xt/t7600-merge.sh4
-rwxr-xr-xt/t7606-merge-custom.sh49
8 files changed, 627 insertions, 508 deletions
diff --git a/.gitignore b/.gitignore
index a213e8e..bbaf9de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@ git-gc
git-get-tar-commit-id
git-grep
git-hash-object
+git-help
git-http-fetch
git-http-push
git-imap-send
diff --git a/Makefile b/Makefile
index 4119fb3..bf400e6 100644
--- a/Makefile
+++ b/Makefile
@@ -357,6 +357,7 @@ LIB_H += git-compat-util.h
LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
+LIB_H += help.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
@@ -520,6 +521,7 @@ BUILTIN_OBJS += builtin-for-each-ref.o
BUILTIN_OBJS += builtin-fsck.o
BUILTIN_OBJS += builtin-gc.o
BUILTIN_OBJS += builtin-grep.o
+BUILTIN_OBJS += builtin-help.o
BUILTIN_OBJS += builtin-init-db.o
BUILTIN_OBJS += builtin-log.o
BUILTIN_OBJS += builtin-ls-files.o
@@ -1103,7 +1105,7 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
-help.o: help.c common-cmds.h GIT-CFLAGS
+builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
diff --git a/builtin-help.c b/builtin-help.c
new file mode 100644
index 0000000..391f749
--- /dev/null
+++ b/builtin-help.c
@@ -0,0 +1,462 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help command
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "help.h"
+
+static struct man_viewer_list {
+ 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,
+ HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_MAN;
+static struct option builtin_help_options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+ OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+ OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+ HELP_FORMAT_WEB),
+ OPT_SET_INT('i', "info", &help_format, "show info page",
+ HELP_FORMAT_INFO),
+ OPT_END(),
+};
+
+static const char * const builtin_help_usage[] = {
+ "git help [--all] [--man|--web|--info] [command]",
+ NULL
+};
+
+static enum help_format parse_help_format(const char *format)
+{
+ if (!strcmp(format, "man"))
+ return HELP_FORMAT_MAN;
+ if (!strcmp(format, "info"))
+ return HELP_FORMAT_INFO;
+ if (!strcmp(format, "web") || !strcmp(format, "html"))
+ return HELP_FORMAT_WEB;
+ 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;
+ struct child_process ec_process;
+ const char *argv_ec[] = { "emacsclient", "--version", NULL };
+ int version;
+
+ /* emacsclient prints its version number on stderr */
+ memset(&ec_process, 0, sizeof(ec_process));
+ ec_process.argv = argv_ec;
+ ec_process.err = -1;
+ ec_process.stdout_to_stderr = 1;
+ if (start_command(&ec_process)) {
+ fprintf(stderr, "Failed to start emacsclient.\n");
+ return -1;
+ }
+ strbuf_read(&buffer, ec_process.err, 20);
+ close(ec_process.err);
+
+ /*
+ * Don't bother checking return value, because "emacsclient --version"
+ * seems to always exits with code 1.
+ */
+ finish_command(&ec_process);
+
+ if (prefixcmp(buffer.buf, "emacsclient")) {
+ fprintf(stderr, "Failed to parse emacsclient version.\n");
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_remove(&buffer, 0, strlen("emacsclient"));
+ version = atoi(buffer.buf);
+
+ if (version < 22) {
+ fprintf(stderr,
+ "emacsclient version '%d' too old (< 22).\n",
+ version);
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_release(&buffer);
+ return 0;
+}
+
+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(path, "emacsclient", "-e", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+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(path, filename, "newTab", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_man(const char* path, const char *page)
+{
+ if (!path)
+ path = "man";
+ execlp(path, "man", page, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+}
+
+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 = 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 void do_add_man_viewer_info(const char *name,
+ size_t len,
+ const char *value)
+{
+ 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
+ do_add_man_viewer_info(name, len, value);
+
+ return 0;
+}
+
+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)
+ return config_error_nonbool(var);
+ help_format = parse_help_format(value);
+ return 0;
+ }
+ if (!strcmp(var, "man.viewer")) {
+ if (!value)
+ return config_error_nonbool(var);
+ add_man_viewer(value);
+ return 0;
+ }
+ if (!prefixcmp(var, "man."))
+ return add_man_viewer_info(var, value);
+
+ return git_default_config(var, value, cb);
+}
+
+struct cmdnames main_cmds, other_cmds;
+
+void list_common_cmds_help(void)
+{
+ int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (longest < strlen(common_cmds[i].name))
+ longest = strlen(common_cmds[i].name);
+ }
+
+ puts("The most commonly used git commands are:");
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ printf(" %s ", common_cmds[i].name);
+ mput_char(' ', longest - strlen(common_cmds[i].name));
+ puts(common_cmds[i].help);
+ }
+}
+
+static int is_git_command(const char *s)
+{
+ 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 if (is_git_command(git_cmd))
+ return prepend("git-", git_cmd);
+ else
+ return prepend("git", git_cmd);
+}
+
+static void setup_man_path(void)
+{
+ struct strbuf new_path;
+ const char *old_path = getenv("MANPATH");
+
+ strbuf_init(&new_path, 0);
+
+ /* We should always put ':' after our path. If there is no
+ * old_path, the ':' at the end will let 'man' to try
+ * system-wide paths after ours to find the manual page. If
+ * there is old_path, we need ':' as delimiter. */
+ strbuf_addstr(&new_path, GIT_MAN_PATH);
+ strbuf_addch(&new_path, ':');
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+
+ setenv("MANPATH", new_path.buf, 1);
+
+ 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;
+ const char *page = cmd_to_page(git_cmd);
+
+ setup_man_path();
+ for (viewer = man_viewer_list; viewer; viewer = viewer->next)
+ {
+ exec_viewer(viewer->name, page); /* will return when unable */
+ }
+ exec_viewer("man", page);
+ die("no man viewer handled the request");
+}
+
+static void show_info_page(const char *git_cmd)
+{
+ const char *page = cmd_to_page(git_cmd);
+ setenv("INFOPATH", GIT_INFO_PATH, 1);
+ execlp("info", "info", "gitman", page, NULL);
+}
+
+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(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, "%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)
+{
+ const char *page = cmd_to_page(git_cmd);
+ struct strbuf page_path; /* it leaks but we exec bellow */
+
+ get_html_page_path(&page_path, page);
+
+ open_html(page_path.buf);
+}
+
+int cmd_help(int argc, const char **argv, const char *prefix)
+{
+ int nongit;
+ const char *alias;
+ unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds);
+
+ setup_git_directory_gently(&nongit);
+ git_config(git_help_config, NULL);
+
+ argc = parse_options(argc, argv, builtin_help_options,
+ builtin_help_usage, 0);
+
+ if (show_all) {
+ printf("usage: %s\n\n", git_usage_string);
+ 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;
+ }
+
+ alias = alias_lookup(argv[0]);
+ if (alias && !is_git_command(argv[0])) {
+ printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ return 0;
+ }
+
+ switch (help_format) {
+ case HELP_FORMAT_MAN:
+ show_man_page(argv[0]);
+ break;
+ case HELP_FORMAT_INFO:
+ show_info_page(argv[0]);
+ break;
+ case HELP_FORMAT_WEB:
+ show_html_page(argv[0]);
+ break;
+ }
+
+ return 0;
+}
diff --git a/builtin-merge.c b/builtin-merge.c
index 0bff26e..d6bcbec 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -22,6 +22,7 @@
#include "log-tree.h"
#include "color.h"
#include "rerere.h"
+#include "help.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -77,7 +78,9 @@ static int option_parse_message(const struct option *opt,
static struct strategy *get_strategy(const char *name)
{
int i;
- struct strbuf err;
+ struct strategy *ret;
+ static struct cmdnames main_cmds, other_cmds;
+ static int longest;
if (!name)
return NULL;
@@ -86,12 +89,45 @@ static struct strategy *get_strategy(const char *name)
if (!strcmp(name, all_strategy[i].name))
return &all_strategy[i];
- strbuf_init(&err, 0);
- for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
- strbuf_addf(&err, " %s", all_strategy[i].name);
- fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
- fprintf(stderr, "Available strategies are:%s.\n", err.buf);
- exit(1);
+ if (!longest) {
+ struct cmdnames not_strategies;
+
+ memset(&main_cmds, 0, sizeof(struct cmdnames));
+ memset(&other_cmds, 0, sizeof(struct cmdnames));
+ memset(&not_strategies, 0, sizeof(struct cmdnames));
+ longest = load_command_list("git-merge-", &main_cmds,
+ &other_cmds);
+ for (i = 0; i < main_cmds.cnt; i++) {
+ int j, found = 0;
+ struct cmdname *ent = main_cmds.names[i];
+ for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
+ if (!strncmp(ent->name, all_strategy[j].name, ent->len)
+ && !all_strategy[j].name[ent->len])
+ found = 1;
+ if (!found)
+ add_cmdname(&not_strategies, ent->name, ent->len);
+ exclude_cmds(&main_cmds, &not_strategies);
+ }
+ }
+ if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+ fprintf(stderr, "Available strategies are:");
+ for (i = 0; i < main_cmds.cnt; i++)
+ fprintf(stderr, " %s", main_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ if (other_cmds.cnt) {
+ fprintf(stderr, "Available custom strategies are:");
+ for (i = 0; i < other_cmds.cnt; i++)
+ fprintf(stderr, " %s", other_cmds.names[i]->name);
+ fprintf(stderr, ".\n");
+ }
+ exit(1);
+ }
+
+ ret = xmalloc(sizeof(struct strategy));
+ memset(ret, 0, sizeof(struct strategy));
+ ret->name = xstrdup(name);
+ return ret;
}
static void append_strategy(struct strategy *s)
diff --git a/help.c b/help.c
index dc0786d..1afbac0 100644
--- a/help.c
+++ b/help.c
@@ -1,276 +1,7 @@
-/*
- * builtin-help.c
- *
- * Builtin help-related commands (help, usage, version)
- */
#include "cache.h"
#include "builtin.h"
#include "exec_cmd.h"
-#include "common-cmds.h"
-#include "parse-options.h"
-#include "run-command.h"
-
-static struct man_viewer_list {
- 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,
- HELP_FORMAT_WEB,
-};
-
-static int show_all = 0;
-static enum help_format help_format = HELP_FORMAT_MAN;
-static struct option builtin_help_options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
- OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
- OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
- HELP_FORMAT_WEB),
- OPT_SET_INT('i', "info", &help_format, "show info page",
- HELP_FORMAT_INFO),
- OPT_END(),
-};
-
-static const char * const builtin_help_usage[] = {
- "git help [--all] [--man|--web|--info] [command]",
- NULL
-};
-
-static enum help_format parse_help_format(const char *format)
-{
- if (!strcmp(format, "man"))
- return HELP_FORMAT_MAN;
- if (!strcmp(format, "info"))
- return HELP_FORMAT_INFO;
- if (!strcmp(format, "web") || !strcmp(format, "html"))
- return HELP_FORMAT_WEB;
- 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;
- struct child_process ec_process;
- const char *argv_ec[] = { "emacsclient", "--version", NULL };
- int version;
-
- /* emacsclient prints its version number on stderr */
- memset(&ec_process, 0, sizeof(ec_process));
- ec_process.argv = argv_ec;
- ec_process.err = -1;
- ec_process.stdout_to_stderr = 1;
- if (start_command(&ec_process)) {
- fprintf(stderr, "Failed to start emacsclient.\n");
- return -1;
- }
- strbuf_read(&buffer, ec_process.err, 20);
- close(ec_process.err);
-
- /*
- * Don't bother checking return value, because "emacsclient --version"
- * seems to always exits with code 1.
- */
- finish_command(&ec_process);
-
- if (prefixcmp(buffer.buf, "emacsclient")) {
- fprintf(stderr, "Failed to parse emacsclient version.\n");
- strbuf_release(&buffer);
- return -1;
- }
-
- strbuf_remove(&buffer, 0, strlen("emacsclient"));
- version = atoi(buffer.buf);
-
- if (version < 22) {
- fprintf(stderr,
- "emacsclient version '%d' too old (< 22).\n",
- version);
- strbuf_release(&buffer);
- return -1;
- }
-
- strbuf_release(&buffer);
- return 0;
-}
-
-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(path, "emacsclient", "-e", man_page.buf, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
- }
-}
-
-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(path, filename, "newTab", man_page.buf, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
- }
-}
-
-static void exec_man_man(const char* path, const char *page)
-{
- if (!path)
- path = "man";
- execlp(path, "man", page, NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
-}
-
-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 = 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 void do_add_man_viewer_info(const char *name,
- size_t len,
- const char *value)
-{
- 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
- do_add_man_viewer_info(name, len, value);
-
- return 0;
-}
-
-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)
- return config_error_nonbool(var);
- help_format = parse_help_format(value);
- return 0;
- }
- if (!strcmp(var, "man.viewer")) {
- if (!value)
- return config_error_nonbool(var);
- add_man_viewer(value);
- return 0;
- }
- if (!prefixcmp(var, "man."))
- return add_man_viewer_info(var, value);
-
- return git_default_config(var, value, cb);
-}
+#include "help.h"
/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
@@ -294,24 +25,9 @@ static int term_columns(void)
return 80;
}
-static inline void mput_char(char c, unsigned int num)
+void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
- while(num--)
- putchar(c);
-}
-
-static struct cmdnames {
- int alloc;
- int cnt;
- struct cmdname {
- size_t len;
- char name[1];
- } **names;
-} main_cmds, other_cmds;
-
-static 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);
@@ -342,7 +58,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;
@@ -418,11 +134,11 @@ static int is_executable(const char *name)
}
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;
@@ -430,6 +146,9 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
if (!dir)
return 0;
+ if (!prefix)
+ prefix = "git-";
+ prefix_len = strlen(prefix);
strbuf_addf(&buf, "%s/", path);
len = buf.len;
@@ -460,7 +179,9 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
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;
@@ -469,7 +190,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");
@@ -481,7 +202,7 @@ static unsigned int load_command_list(void)
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;
@@ -491,58 +212,43 @@ 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');
}
}
-void list_common_cmds_help(void)
-{
- int i, longest = 0;
-
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- if (longest < strlen(common_cmds[i].name))
- longest = strlen(common_cmds[i].name);
- }
-
- puts("The most commonly used git commands are:");
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- printf(" %s ", common_cmds[i].name);
- mput_char(' ', longest - strlen(common_cmds[i].name));
- puts(common_cmds[i].help);
- }
-}
-
-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++)
@@ -551,130 +257,6 @@ static int is_in_cmdlist(struct cmdnames *c, const char *s)
return 0;
}
-static int is_git_command(const char *s)
-{
- load_command_list();
- return is_in_cmdlist(&main_cmds, s) ||
- is_in_cmdlist(&other_cmds, s) ||
- !strcmp(s, "help");
-}
-
-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 if (is_git_command(git_cmd))
- return prepend("git-", git_cmd);
- else
- return prepend("git", git_cmd);
-}
-
-static void setup_man_path(void)
-{
- struct strbuf new_path;
- const char *old_path = getenv("MANPATH");
-
- strbuf_init(&new_path, 0);
-
- /* We should always put ':' after our path. If there is no
- * old_path, the ':' at the end will let 'man' to try
- * system-wide paths after ours to find the manual page. If
- * there is old_path, we need ':' as delimiter. */
- strbuf_addstr(&new_path, GIT_MAN_PATH);
- strbuf_addch(&new_path, ':');
- if (old_path)
- strbuf_addstr(&new_path, old_path);
-
- setenv("MANPATH", new_path.buf, 1);
-
- 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;
- const char *page = cmd_to_page(git_cmd);
-
- setup_man_path();
- for (viewer = man_viewer_list; viewer; viewer = viewer->next)
- {
- exec_viewer(viewer->name, page); /* will return when unable */
- }
- exec_viewer("man", page);
- die("no man viewer handled the request");
-}
-
-static void show_info_page(const char *git_cmd)
-{
- const char *page = cmd_to_page(git_cmd);
- setenv("INFOPATH", GIT_INFO_PATH, 1);
- execlp("info", "info", "gitman", page, NULL);
-}
-
-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(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, "%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)
-{
- const char *page = cmd_to_page(git_cmd);
- struct strbuf page_path; /* it leaks but we exec bellow */
-
- get_html_page_path(&page_path, page);
-
- open_html(page_path.buf);
-}
-
void help_unknown_cmd(const char *cmd)
{
fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
@@ -686,49 +268,3 @@ int cmd_version(int argc, const char **argv, const char *prefix)
printf("git version %s\n", git_version_string);
return 0;
}
-
-int cmd_help(int argc, const char **argv, const char *prefix)
-{
- int nongit;
- const char *alias;
-
- setup_git_directory_gently(&nongit);
- git_config(git_help_config, NULL);
-
- argc = parse_options(argc, argv, builtin_help_options,
- builtin_help_usage, 0);
-
- if (show_all) {
- printf("usage: %s\n\n", git_usage_string);
- list_commands();
- 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;
- }
-
- alias = alias_lookup(argv[0]);
- if (alias && !is_git_command(argv[0])) {
- printf("`git %s' is aliased to `%s'\n", argv[0], alias);
- return 0;
- }
-
- switch (help_format) {
- case HELP_FORMAT_MAN:
- show_man_page(argv[0]);
- break;
- case HELP_FORMAT_INFO:
- show_info_page(argv[0]);
- break;
- case HELP_FORMAT_WEB:
- show_html_page(argv[0]);
- break;
- }
-
- return 0;
-}
diff --git a/help.h b/help.h
new file mode 100644
index 0000000..3f1ae89
--- /dev/null
+++ b/help.h
@@ -0,0 +1,29 @@
+#ifndef HELP_H
+#define HELP_H
+
+struct cmdnames {
+ int alloc;
+ int cnt;
+ struct cmdname {
+ size_t len;
+ char name[FLEX_ARRAY];
+ } **names;
+};
+
+static inline void mput_char(char c, unsigned int num)
+{
+ while(num--)
+ putchar(c);
+}
+
+unsigned int load_command_list(const char *prefix,
+ struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds);
+void add_cmdname(struct cmdnames *cmds, const char *name, int len);
+/* Here we require that excludes is a sorted list. */
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
+int is_in_cmdlist(struct cmdnames *c, const char *s);
+void list_commands(const char *title, unsigned int longest,
+ struct cmdnames *main_cmds, struct cmdnames *other_cmds);
+
+#endif /* HELP_H */
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index dbc90bc..6a2b125 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -230,6 +230,10 @@ test_expect_success 'test option parsing' '
test_must_fail git merge
'
+test_expect_success 'reject non-strategy with a git-merge-foo name' '
+ test_must_fail git merge -s index c1
+'
+
test_expect_success 'merge c0 with c1' '
git reset --hard c0 &&
git merge c1 &&
diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh
new file mode 100755
index 0000000..de9b6ed
--- /dev/null
+++ b/t/t7606-merge-custom.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing a custom strategy.'
+
+. ./test-lib.sh
+
+cat >git-merge-theirs <<EOF
+#!$SHELL_PATH
+eval git read-tree --reset -u \\\$\$#
+EOF
+chmod +x git-merge-theirs
+PATH=.:$PATH
+export PATH
+
+test_expect_success 'setup' '
+ echo c0 >c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ echo c1 >c1.c &&
+ git add c1.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ echo c1c1 >c1.c &&
+ echo c2 >c2.c &&
+ git add c1.c c2.c &&
+ git commit -m c2 &&
+ git tag c2
+'
+
+test_expect_success 'merge c2 with a custom strategy' '
+ git reset --hard c1 &&
+ git merge -s theirs c2 &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+ test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+ test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" &&
+ git diff --exit-code &&
+ git diff --exit-code c2 HEAD &&
+ git diff --exit-code c2 &&
+ test -f c0.c &&
+ grep c1c1 c1.c &&
+ test -f c2.c
+'
+
+test_done