diff options
Diffstat (limited to 'help.c')
| -rw-r--r-- | help.c | 341 |
1 files changed, 212 insertions, 129 deletions
@@ -1,9 +1,11 @@ -#include "cache.h" +#include "git-compat-util.h" +#include "alloc.h" #include "config.h" #include "builtin.h" #include "exec-cmd.h" #include "run-command.h" #include "levenshtein.h" +#include "gettext.h" #include "help.h" #include "command-list.h" #include "string-list.h" @@ -11,6 +13,8 @@ #include "version.h" #include "refs.h" #include "parse-options.h" +#include "prompt.h" +#include "fsmonitor-ipc.h" struct category_description { uint32_t category; @@ -34,21 +38,32 @@ static struct category_description main_categories[] = { { CAT_foreignscminterface, N_("Interacting with Others") }, { CAT_plumbingmanipulators, N_("Low-level Commands / Manipulators") }, { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") }, - { CAT_synchingrepositories, N_("Low-level Commands / Synching Repositories") }, + { CAT_synchingrepositories, N_("Low-level Commands / Syncing Repositories") }, { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") }, + { CAT_userinterfaces, N_("User-facing repository, command and file interfaces") }, + { CAT_developerinterfaces, N_("Developer-facing file formats, protocols and other interfaces") }, { 0, NULL } }; static const char *drop_prefix(const char *name, uint32_t category) { const char *new_name; + const char *prefix; - if (skip_prefix(name, "git-", &new_name)) - return new_name; - if (category == CAT_guide && skip_prefix(name, "git", &new_name)) + switch (category) { + case CAT_guide: + case CAT_userinterfaces: + case CAT_developerinterfaces: + prefix = "git"; + break; + default: + prefix = "git-"; + break; + } + if (skip_prefix(name, prefix, &new_name)) return new_name; - return name; + return name; } static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask) @@ -83,8 +98,10 @@ static void print_command_list(const struct cmdname_help *cmds, for (i = 0; cmds[i].name; i++) { if (cmds[i].category & mask) { + size_t len = strlen(cmds[i].name); printf(" %s ", cmds[i].name); - mput_char(' ', longest - strlen(cmds[i].name)); + if (longest > len) + mput_char(' ', longest - len); puts(_(cmds[i].help)); } } @@ -98,7 +115,8 @@ static int cmd_name_cmp(const void *elem1, const void *elem2) return strcmp(e1->name, e2->name); } -static void print_cmd_by_category(const struct category_description *catdesc) +static void print_cmd_by_category(const struct category_description *catdesc, + int *longest_p) { struct cmdname_help *cmds; int longest = 0; @@ -120,10 +138,14 @@ static void print_cmd_by_category(const struct category_description *catdesc) uint32_t mask = catdesc[i].category; const char *desc = catdesc[i].desc; - printf("\n%s\n", _(desc)); + if (i) + putchar('\n'); + puts(_(desc)); print_command_list(cmds, mask, longest); } free(cmds); + if (longest_p) + *longest_p = longest; } void add_cmdname(struct cmdnames *cmds, const char *name, int len) @@ -258,6 +280,8 @@ void load_command_list(const char *prefix, const char *env_path = getenv("PATH"); const char *exec_path = git_exec_path(); + load_builtin_commands(prefix, main_cmds); + if (exec_path) { list_commands_in_dir(main_cmds, exec_path, prefix); QSORT(main_cmds->names, main_cmds->cnt, cmdname_compare); @@ -285,9 +309,21 @@ void load_command_list(const char *prefix, exclude_cmds(other_cmds, main_cmds); } -void list_commands(unsigned int colopts, - struct cmdnames *main_cmds, struct cmdnames *other_cmds) +static int get_colopts(const char *var, const char *value, void *data) +{ + unsigned int *colopts = data; + + if (starts_with(var, "column.")) + return git_column_config(var, value, "help", colopts); + + return 0; +} + +void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds) { + unsigned int colopts = 0; + git_config(get_colopts, &colopts); + if (main_cmds->cnt) { const char *exec_path = git_exec_path(); printf_ln(_("available git commands in '%s'"), exec_path); @@ -297,7 +333,7 @@ void list_commands(unsigned int colopts, } if (other_cmds->cnt) { - printf_ln(_("git commands available from elsewhere on your $PATH")); + puts(_("git commands available from elsewhere on your $PATH")); putchar('\n'); pretty_print_cmdnames(other_cmds, colopts); putchar('\n'); @@ -307,7 +343,8 @@ void list_commands(unsigned int colopts, void list_common_cmds_help(void) { puts(_("These are common Git commands used in various situations:")); - print_cmd_by_category(common_categories); + putchar('\n'); + print_cmd_by_category(common_categories, NULL); } void list_all_main_cmds(struct string_list *list) @@ -370,14 +407,7 @@ void list_cmds_by_config(struct string_list *list) { const char *cmd_list; - /* - * There's no actual repository setup at this point (and even - * if there is, we don't really care; only global config - * matters). If we accidentally set up a repository, it's ok - * too since the caller (git --list-cmds=) should exit shortly - * anyway. - */ - if (git_config_get_string_const("completion.commands", &cmd_list)) + if (git_config_get_string_tmp("completion.commands", &cmd_list)) return; string_list_sort(list); @@ -388,8 +418,8 @@ void list_cmds_by_config(struct string_list *list) const char *p = strchrnul(cmd_list, ' '); strbuf_add(&sb, cmd_list, p - cmd_list); - if (*cmd_list == '-') - string_list_remove(list, cmd_list + 1, 0); + if (sb.buf[0] == '-') + string_list_remove(list, sb.buf + 1, 0); else string_list_insert(list, sb.buf); strbuf_release(&sb); @@ -399,103 +429,101 @@ void list_cmds_by_config(struct string_list *list) } } -void list_common_guides_help(void) +void list_guides_help(void) { struct category_description catdesc[] = { - { CAT_guide, N_("The common Git guides are:") }, + { CAT_guide, N_("The Git concept guides are:") }, { 0, NULL } }; - print_cmd_by_category(catdesc); + print_cmd_by_category(catdesc, NULL); putchar('\n'); } -struct slot_expansion { - const char *prefix; - const char *placeholder; - void (*fn)(struct string_list *list, const char *prefix); - int found; -}; +void list_user_interfaces_help(void) +{ + struct category_description catdesc[] = { + { CAT_userinterfaces, N_("User-facing repository, command and file interfaces:") }, + { 0, NULL } + }; + print_cmd_by_category(catdesc, NULL); + putchar('\n'); +} -void list_config_help(int for_human) -{ - struct slot_expansion slot_expansions[] = { - { "advice", "*", list_config_advices }, - { "color.branch", "<slot>", list_config_color_branch_slots }, - { "color.decorate", "<slot>", list_config_color_decorate_slots }, - { "color.diff", "<slot>", list_config_color_diff_slots }, - { "color.grep", "<slot>", list_config_color_grep_slots }, - { "color.interactive", "<slot>", list_config_color_interactive_slots }, - { "color.status", "<slot>", list_config_color_status_slots }, - { "fsck", "<msg-id>", list_config_fsck_msg_ids }, - { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids }, - { NULL, NULL, NULL } +void list_developer_interfaces_help(void) +{ + struct category_description catdesc[] = { + { CAT_developerinterfaces, N_("File formats, protocols and other developer interfaces:") }, + { 0, NULL } }; - const char **p; - struct slot_expansion *e; - struct string_list keys = STRING_LIST_INIT_DUP; - int i; + print_cmd_by_category(catdesc, NULL); + putchar('\n'); +} - for (p = config_name_list; *p; p++) { - const char *var = *p; - struct strbuf sb = STRBUF_INIT; +static int get_alias(const char *var, const char *value, void *data) +{ + struct string_list *list = data; - for (e = slot_expansions; e->prefix; e++) { + if (skip_prefix(var, "alias.", &var)) + string_list_append(list, var)->util = xstrdup(value); - strbuf_reset(&sb); - strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); - if (!strcasecmp(var, sb.buf)) { - e->fn(&keys, e->prefix); - e->found++; - break; - } - } - strbuf_release(&sb); - if (!e->prefix) - string_list_append(&keys, var); - } + return 0; +} - for (e = slot_expansions; e->prefix; e++) - if (!e->found) - BUG("slot_expansion %s.%s is not used", - e->prefix, e->placeholder); +static void list_all_cmds_help_external_commands(void) +{ + struct string_list others = STRING_LIST_INIT_DUP; + int i; - string_list_sort(&keys); - for (i = 0; i < keys.nr; i++) { - const char *var = keys.items[i].string; - const char *wildcard, *tag, *cut; + list_all_other_cmds(&others); + if (others.nr) + printf("\n%s\n", _("External commands")); + for (i = 0; i < others.nr; i++) + printf(" %s\n", others.items[i].string); + string_list_clear(&others, 0); +} - if (for_human) { - puts(var); - continue; - } +static void list_all_cmds_help_aliases(int longest) +{ + struct string_list alias_list = STRING_LIST_INIT_DUP; + struct cmdname_help *aliases; + int i; - wildcard = strchr(var, '*'); - tag = strchr(var, '<'); + git_config(get_alias, &alias_list); + string_list_sort(&alias_list); - if (!wildcard && !tag) { - puts(var); - continue; - } - - if (wildcard && !tag) - cut = wildcard; - else if (!wildcard && tag) - cut = tag; - else - cut = wildcard < tag ? wildcard : tag; + for (i = 0; i < alias_list.nr; i++) { + size_t len = strlen(alias_list.items[i].string); + if (longest < len) + longest = len; + } - /* - * We may produce duplicates, but that's up to - * git-completion.bash to handle - */ - printf("%.*s\n", (int)(cut - var), var); + if (alias_list.nr) { + printf("\n%s\n", _("Command aliases")); + ALLOC_ARRAY(aliases, alias_list.nr + 1); + for (i = 0; i < alias_list.nr; i++) { + aliases[i].name = alias_list.items[i].string; + aliases[i].help = alias_list.items[i].util; + aliases[i].category = 1; + } + aliases[alias_list.nr].name = NULL; + print_command_list(aliases, 1, longest); + free(aliases); } - string_list_clear(&keys, 0); + string_list_clear(&alias_list, 1); } -void list_all_cmds_help(void) +void list_all_cmds_help(int show_external_commands, int show_aliases) { - print_cmd_by_category(main_categories); + int longest; + + puts(_("See 'git help <command>' to read about a specific subcommand")); + putchar('\n'); + print_cmd_by_category(main_categories, &longest); + + if (show_external_commands) + list_all_cmds_help_external_commands(); + if (show_aliases) + list_all_cmds_help_aliases(longest); } int is_in_cmdlist(struct cmdnames *c, const char *s) @@ -510,17 +538,35 @@ int is_in_cmdlist(struct cmdnames *c, const char *s) static int autocorrect; static struct cmdnames aliases; -static int git_unknown_cmd_config(const char *var, const char *value, void *cb) +#define AUTOCORRECT_PROMPT (-3) +#define AUTOCORRECT_NEVER (-2) +#define AUTOCORRECT_IMMEDIATELY (-1) + +static int git_unknown_cmd_config(const char *var, const char *value, + void *cb UNUSED) { const char *p; - if (!strcmp(var, "help.autocorrect")) - autocorrect = git_config_int(var,value); + if (!strcmp(var, "help.autocorrect")) { + if (!value) + return config_error_nonbool(var); + if (!strcmp(value, "never")) { + autocorrect = AUTOCORRECT_NEVER; + } else if (!strcmp(value, "immediate")) { + autocorrect = AUTOCORRECT_IMMEDIATELY; + } else if (!strcmp(value, "prompt")) { + autocorrect = AUTOCORRECT_PROMPT; + } else { + int v = git_config_int(var, value); + autocorrect = (v < 0) + ? AUTOCORRECT_IMMEDIATELY : v; + } + } /* Also use aliases for command lookup */ if (skip_prefix(var, "alias.", &p)) add_cmdname(&aliases, p, strlen(p)); - return git_default_config(var, value, cb); + return 0; } static int levenshtein_compare(const void *p1, const void *p2) @@ -563,6 +609,17 @@ const char *help_unknown_cmd(const char *cmd) read_early_config(git_unknown_cmd_config, NULL); + /* + * Disable autocorrection prompt in a non-interactive session + */ + if ((autocorrect == AUTOCORRECT_PROMPT) && (!isatty(0) || !isatty(2))) + autocorrect = AUTOCORRECT_NEVER; + + if (autocorrect == AUTOCORRECT_NEVER) { + fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd); + exit(1); + } + load_command_list("git-", &main_cmds, &other_cmds); add_cmd_list(&main_cmds, &aliases); @@ -632,12 +689,21 @@ const char *help_unknown_cmd(const char *cmd) _("WARNING: You called a Git command named '%s', " "which does not exist."), cmd); - if (autocorrect < 0) + if (autocorrect == AUTOCORRECT_IMMEDIATELY) fprintf_ln(stderr, _("Continuing under the assumption that " "you meant '%s'."), assumed); - else { + else if (autocorrect == AUTOCORRECT_PROMPT) { + char *answer; + struct strbuf msg = STRBUF_INIT; + strbuf_addf(&msg, _("Run '%s' instead [y/N]? "), assumed); + answer = git_prompt(msg.buf, PROMPT_ECHO); + strbuf_release(&msg); + if (!(starts_with(answer, "y") || + starts_with(answer, "Y"))) + exit(1); + } else { fprintf_ln(stderr, _("Continuing in %0.1f seconds, " "assuming that you meant '%s'."), @@ -662,11 +728,39 @@ const char *help_unknown_cmd(const char *cmd) exit(1); } +void get_version_info(struct strbuf *buf, int show_build_options) +{ + /* + * The format of this string should be kept stable for compatibility + * with external projects that rely on the output of "git version". + * + * Always show the version, even if other options are given. + */ + strbuf_addf(buf, "git version %s\n", git_version_string); + + if (show_build_options) { + strbuf_addf(buf, "cpu: %s\n", GIT_HOST_CPU); + if (git_built_from_commit_string[0]) + strbuf_addf(buf, "built from commit: %s\n", + git_built_from_commit_string); + else + strbuf_addstr(buf, "no commit associated with this build\n"); + strbuf_addf(buf, "sizeof-long: %d\n", (int)sizeof(long)); + strbuf_addf(buf, "sizeof-size_t: %d\n", (int)sizeof(size_t)); + strbuf_addf(buf, "shell-path: %s\n", SHELL_PATH); + /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ + + if (fsmonitor_ipc__is_supported()) + strbuf_addstr(buf, "feature: fsmonitor--daemon\n"); + } +} + int cmd_version(int argc, const char **argv, const char *prefix) { + struct strbuf buf = STRBUF_INIT; int build_options = 0; const char * const usage[] = { - N_("git version [<options>]"), + N_("git version [--build-options]"), NULL }; struct option options[] = { @@ -677,24 +771,11 @@ int cmd_version(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, usage, 0); - /* - * The format of this string should be kept stable for compatibility - * with external projects that rely on the output of "git version". - * - * Always show the version, even if other options are given. - */ - printf("git version %s\n", git_version_string); + get_version_info(&buf, build_options); + printf("%s", buf.buf); + + strbuf_release(&buf); - if (build_options) { - printf("cpu: %s\n", GIT_HOST_CPU); - if (git_built_from_commit_string[0]) - printf("built from commit: %s\n", - git_built_from_commit_string); - else - printf("no commit associated with this build\n"); - printf("sizeof-long: %d\n", (int)sizeof(long)); - /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ - } return 0; } @@ -703,24 +784,25 @@ struct similar_ref_cb { struct string_list *similar_refs; }; -static int append_similar_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) +static int append_similar_ref(const char *refname, + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data); char *branch = strrchr(refname, '/') + 1; - const char *remote; /* A remote branch of the same name is deemed similar */ - if (skip_prefix(refname, "refs/remotes/", &remote) && + if (starts_with(refname, "refs/remotes/") && !strcmp(branch, cb->base_ref)) - string_list_append(cb->similar_refs, remote); + string_list_append_nodup(cb->similar_refs, + shorten_unambiguous_ref(refname, 1)); return 0; } static struct string_list guess_refs(const char *ref) { struct similar_ref_cb ref_cb; - struct string_list similar_refs = STRING_LIST_INIT_NODUP; + struct string_list similar_refs = STRING_LIST_INIT_DUP; ref_cb.base_ref = ref; ref_cb.similar_refs = &similar_refs; @@ -728,7 +810,8 @@ static struct string_list guess_refs(const char *ref) return similar_refs; } -void help_unknown_ref(const char *ref, const char *cmd, const char *error) +NORETURN void help_unknown_ref(const char *ref, const char *cmd, + const char *error) { int i; struct string_list suggested_refs = guess_refs(ref); |
