git/help.c

838 lines
21 KiB
C
Raw Normal View History

#include "git-compat-util.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"
#include "column.h"
#include "version.h"
#include "refs.h"
#include "parse-options.h"
#include "prompt.h"
#include "fsmonitor-ipc.h"
struct category_description {
uint32_t category;
const char *desc;
};
static uint32_t common_mask =
CAT_init | CAT_worktree | CAT_info |
CAT_history | CAT_remote;
static struct category_description common_categories[] = {
{ CAT_init, N_("start a working area (see also: git help tutorial)") },
{ CAT_worktree, N_("work on the current change (see also: git help everyday)") },
{ CAT_info, N_("examine the history and state (see also: git help revisions)") },
{ CAT_history, N_("grow, mark and tweak your common history") },
{ CAT_remote, N_("collaborate (see also: git help workflows)") },
{ 0, NULL }
};
static struct category_description main_categories[] = {
{ CAT_mainporcelain, N_("Main Porcelain Commands") },
{ CAT_ancillarymanipulators, N_("Ancillary Commands / Manipulators") },
{ CAT_ancillaryinterrogators, N_("Ancillary Commands / Interrogators") },
{ 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 / 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;
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;
}
static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask)
{
int i, nr = 0;
struct cmdname_help *cmds;
if (ARRAY_SIZE(command_list) == 0)
BUG("empty command_list[] is a sign of broken generate-cmdlist.sh");
ALLOC_ARRAY(cmds, ARRAY_SIZE(command_list) + 1);
for (i = 0; i < ARRAY_SIZE(command_list); i++) {
const struct cmdname_help *cmd = command_list + i;
if (!(cmd->category & mask))
continue;
cmds[nr] = *cmd;
cmds[nr].name = drop_prefix(cmd->name, cmd->category);
nr++;
}
cmds[nr].name = NULL;
*p_cmds = cmds;
}
static void print_command_list(const struct cmdname_help *cmds,
uint32_t mask, int longest)
{
int i;
for (i = 0; cmds[i].name; i++) {
if (cmds[i].category & mask) {
size_t len = strlen(cmds[i].name);
printf(" %s ", cmds[i].name);
if (longest > len)
mput_char(' ', longest - len);
puts(_(cmds[i].help));
}
}
}
static int cmd_name_cmp(const void *elem1, const void *elem2)
{
const struct cmdname_help *e1 = elem1;
const struct cmdname_help *e2 = elem2;
return strcmp(e1->name, e2->name);
}
static void print_cmd_by_category(const struct category_description *catdesc,
int *longest_p)
{
struct cmdname_help *cmds;
int longest = 0;
int i, nr = 0;
uint32_t mask = 0;
for (i = 0; catdesc[i].desc; i++)
mask |= catdesc[i].category;
extract_cmds(&cmds, mask);
for (i = 0; cmds[i].name; i++, nr++) {
if (longest < strlen(cmds[i].name))
longest = strlen(cmds[i].name);
}
QSORT(cmds, nr, cmd_name_cmp);
for (i = 0; catdesc[i].desc; i++) {
uint32_t mask = catdesc[i].category;
const char *desc = catdesc[i].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)
{
struct cmdname *ent;
FLEX_ALLOC_MEM(ent, name, name, len);
ent->len = len;
ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
cmds->names[cmds->cnt++] = ent;
}
static void clean_cmdnames(struct cmdnames *cmds)
{
int i;
for (i = 0; i < cmds->cnt; ++i)
free(cmds->names[i]);
free(cmds->names);
cmds->cnt = 0;
cmds->alloc = 0;
}
static int cmdname_compare(const void *a_, const void *b_)
{
struct cmdname *a = *(struct cmdname **)a_;
struct cmdname *b = *(struct cmdname **)b_;
return strcmp(a->name, b->name);
}
static void uniq(struct cmdnames *cmds)
{
int i, j;
if (!cmds->cnt)
return;
for (i = j = 1; i < cmds->cnt; i++) {
if (!strcmp(cmds->names[i]->name, cmds->names[j-1]->name))
free(cmds->names[i]);
else
cmds->names[j++] = cmds->names[i];
}
cmds->cnt = j;
}
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
{
int ci, cj, ei;
int cmp;
ci = cj = ei = 0;
while (ci < cmds->cnt && ei < excludes->cnt) {
cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
if (cmp < 0)
cmds->names[cj++] = cmds->names[ci++];
else if (cmp == 0) {
ei++;
free(cmds->names[ci++]);
} else if (cmp > 0)
ei++;
}
while (ci < cmds->cnt)
cmds->names[cj++] = cmds->names[ci++];
cmds->cnt = cj;
}
static void pretty_print_cmdnames(struct cmdnames *cmds, unsigned int colopts)
{
struct string_list list = STRING_LIST_INIT_NODUP;
struct column_options copts;
int i;
for (i = 0; i < cmds->cnt; i++)
string_list_append(&list, cmds->names[i]->name);
/*
* always enable column display, we only consult column.*
* about layout strategy and stuff
*/
colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
memset(&copts, 0, sizeof(copts));
copts.indent = " ";
copts.padding = 2;
print_columns(&list, colopts, &copts);
string_list_clear(&list, 0);
}
static void list_commands_in_dir(struct cmdnames *cmds,
const char *path,
const char *prefix)
{
DIR *dir = opendir(path);
struct dirent *de;
struct strbuf buf = STRBUF_INIT;
int len;
if (!dir)
return;
if (!prefix)
prefix = "git-";
strbuf_addf(&buf, "%s/", path);
len = buf.len;
while ((de = readdir(dir)) != NULL) {
const char *ent;
size_t entlen;
if (!skip_prefix(de->d_name, prefix, &ent))
continue;
strbuf_setlen(&buf, len);
strbuf_addstr(&buf, de->d_name);
if (!is_executable(buf.buf))
continue;
entlen = strlen(ent);
strip_suffix(ent, ".exe", &entlen);
add_cmdname(cmds, ent, entlen);
}
closedir(dir);
strbuf_release(&buf);
}
void load_command_list(const char *prefix,
struct cmdnames *main_cmds,
struct cmdnames *other_cmds)
{
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);
uniq(main_cmds);
}
if (env_path) {
char *paths, *path, *colon;
path = paths = xstrdup(env_path);
while (1) {
if ((colon = strchr(path, PATH_SEP)))
*colon = 0;
if (!exec_path || strcmp(path, exec_path))
list_commands_in_dir(other_cmds, path, prefix);
if (!colon)
break;
path = colon + 1;
}
free(paths);
QSORT(other_cmds->names, other_cmds->cnt, cmdname_compare);
uniq(other_cmds);
}
exclude_cmds(other_cmds, main_cmds);
}
config: add ctx arg to config_fn_t Add a new "const struct config_context *ctx" arg to config_fn_t to hold additional information about the config iteration operation. config_context has a "struct key_value_info kvi" member that holds metadata about the config source being read (e.g. what kind of config source it is, the filename, etc). In this series, we're only interested in .kvi, so we could have just used "struct key_value_info" as an arg, but config_context makes it possible to add/adjust members in the future without changing the config_fn_t signature. We could also consider other ways of organizing the args (e.g. moving the config name and value into config_context or key_value_info), but in my experiments, the incremental benefit doesn't justify the added complexity (e.g. a config_fn_t will sometimes invoke another config_fn_t but with a different config value). In subsequent commits, the .kvi member will replace the global "struct config_reader" in config.c, making config iteration a global-free operation. It requires much more work for the machinery to provide meaningful values of .kvi, so for now, merely change the signature and call sites, pass NULL as a placeholder value, and don't rely on the arg in any meaningful way. Most of the changes are performed by contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every config_fn_t: - Modifies the signature to accept "const struct config_context *ctx" - Passes "ctx" to any inner config_fn_t, if needed - Adds UNUSED attributes to "ctx", if needed Most config_fn_t instances are easily identified by seeing if they are called by the various config functions. Most of the remaining ones are manually named in the .cocci patch. Manual cleanups are still needed, but the majority of it is trivial; it's either adjusting config_fn_t that the .cocci patch didn't catch, or adding forward declarations of "struct config_context ctx" to make the signatures make sense. The non-trivial changes are in cases where we are invoking a config_fn_t outside of config machinery, and we now need to decide what value of "ctx" to pass. These cases are: - trace2/tr2_cfg.c:tr2_cfg_set_fl() This is indirectly called by git_config_set() so that the trace2 machinery can notice the new config values and update its settings using the tr2 config parsing function, i.e. tr2_cfg_cb(). - builtin/checkout.c:checkout_main() This calls git_xmerge_config() as a shorthand for parsing a CLI arg. This might be worth refactoring away in the future, since git_xmerge_config() can call git_default_config(), which can do much more than just parsing. Handle them by creating a KVI_INIT macro that initializes "struct key_value_info" to a reasonable default, and use that to construct the "ctx" arg. Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-29 03:26:22 +08:00
static int get_colopts(const char *var, const char *value,
const struct config_context *ctx UNUSED, 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);
putchar('\n');
pretty_print_cmdnames(main_cmds, colopts);
putchar('\n');
}
if (other_cmds->cnt) {
puts(_("git commands available from elsewhere on your $PATH"));
putchar('\n');
pretty_print_cmdnames(other_cmds, colopts);
putchar('\n');
}
}
void list_common_cmds_help(void)
{
puts(_("These are common Git commands used in various situations:"));
putchar('\n');
print_cmd_by_category(common_categories, NULL);
}
void list_all_main_cmds(struct string_list *list)
{
struct cmdnames main_cmds, other_cmds;
int i;
memset(&main_cmds, 0, sizeof(main_cmds));
memset(&other_cmds, 0, sizeof(other_cmds));
load_command_list("git-", &main_cmds, &other_cmds);
for (i = 0; i < main_cmds.cnt; i++)
string_list_append(list, main_cmds.names[i]->name);
clean_cmdnames(&main_cmds);
clean_cmdnames(&other_cmds);
}
void list_all_other_cmds(struct string_list *list)
{
struct cmdnames main_cmds, other_cmds;
int i;
memset(&main_cmds, 0, sizeof(main_cmds));
memset(&other_cmds, 0, sizeof(other_cmds));
load_command_list("git-", &main_cmds, &other_cmds);
for (i = 0; i < other_cmds.cnt; i++)
string_list_append(list, other_cmds.names[i]->name);
clean_cmdnames(&main_cmds);
clean_cmdnames(&other_cmds);
}
void list_cmds_by_category(struct string_list *list,
const char *cat)
{
int i, n = ARRAY_SIZE(command_list);
uint32_t cat_id = 0;
for (i = 0; category_names[i]; i++) {
if (!strcmp(cat, category_names[i])) {
cat_id = 1UL << i;
break;
}
}
if (!cat_id)
die(_("unsupported command listing type '%s'"), cat);
for (i = 0; i < n; i++) {
struct cmdname_help *cmd = command_list + i;
if (!(cmd->category & cat_id))
continue;
string_list_append(list, drop_prefix(cmd->name, cmd->category));
}
}
void list_cmds_by_config(struct string_list *list)
{
const char *cmd_list;
config: fix leaks from git_config_get_string_const() There are two functions to get a single config string: - git_config_get_string() - git_config_get_string_const() One might naively think that the first one allocates a new string and the second one just points us to the internal configset storage. But in fact they both allocate a new copy; the second one exists only to avoid having to cast when using it with a const global which we never intend to free. The documentation for the function explains that clearly, but it seems I'm not alone in being surprised by this. Of 17 calls to the function, 13 of them leak the resulting value. We could obviously fix these by adding the appropriate free(). But it would be simpler still if we actually had a non-allocating way to get the string. There's git_config_get_value() but that doesn't quite do what we want. If the config key is present but is a boolean with no value (e.g., "[foo]bar" in the file), then we'll get NULL (whereas the string versions will print an error and die). So let's introduce a new variant, git_config_get_string_tmp(), that behaves as these callers expect. We need a new name because we have new semantics but the same function signature (so even if we converted the four remaining callers, topics in flight might be surprised). The "tmp" is because this value should only be held onto for a short time. In practice it's rare for us to clear and refresh the configset, invalidating the pointer, but hopefully the "tmp" makes callers think about the lifetime. In each of the converted cases here the value only needs to last within the local function or its immediate caller. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-08-15 00:17:36 +08:00
if (git_config_get_string_tmp("completion.commands", &cmd_list))
return;
string_list_sort(list);
string_list_remove_duplicates(list, 0);
while (*cmd_list) {
struct strbuf sb = STRBUF_INIT;
const char *p = strchrnul(cmd_list, ' ');
strbuf_add(&sb, cmd_list, p - cmd_list);
if (sb.buf[0] == '-')
string_list_remove(list, sb.buf + 1, 0);
else
string_list_insert(list, sb.buf);
strbuf_release(&sb);
while (*p == ' ')
p++;
cmd_list = p;
}
}
void list_guides_help(void)
{
struct category_description catdesc[] = {
{ CAT_guide, N_("The Git concept guides are:") },
{ 0, NULL }
};
print_cmd_by_category(catdesc, NULL);
putchar('\n');
}
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_developer_interfaces_help(void)
{
struct category_description catdesc[] = {
{ CAT_developerinterfaces, N_("File formats, protocols and other developer interfaces:") },
{ 0, NULL }
};
print_cmd_by_category(catdesc, NULL);
putchar('\n');
}
config: add ctx arg to config_fn_t Add a new "const struct config_context *ctx" arg to config_fn_t to hold additional information about the config iteration operation. config_context has a "struct key_value_info kvi" member that holds metadata about the config source being read (e.g. what kind of config source it is, the filename, etc). In this series, we're only interested in .kvi, so we could have just used "struct key_value_info" as an arg, but config_context makes it possible to add/adjust members in the future without changing the config_fn_t signature. We could also consider other ways of organizing the args (e.g. moving the config name and value into config_context or key_value_info), but in my experiments, the incremental benefit doesn't justify the added complexity (e.g. a config_fn_t will sometimes invoke another config_fn_t but with a different config value). In subsequent commits, the .kvi member will replace the global "struct config_reader" in config.c, making config iteration a global-free operation. It requires much more work for the machinery to provide meaningful values of .kvi, so for now, merely change the signature and call sites, pass NULL as a placeholder value, and don't rely on the arg in any meaningful way. Most of the changes are performed by contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every config_fn_t: - Modifies the signature to accept "const struct config_context *ctx" - Passes "ctx" to any inner config_fn_t, if needed - Adds UNUSED attributes to "ctx", if needed Most config_fn_t instances are easily identified by seeing if they are called by the various config functions. Most of the remaining ones are manually named in the .cocci patch. Manual cleanups are still needed, but the majority of it is trivial; it's either adjusting config_fn_t that the .cocci patch didn't catch, or adding forward declarations of "struct config_context ctx" to make the signatures make sense. The non-trivial changes are in cases where we are invoking a config_fn_t outside of config machinery, and we now need to decide what value of "ctx" to pass. These cases are: - trace2/tr2_cfg.c:tr2_cfg_set_fl() This is indirectly called by git_config_set() so that the trace2 machinery can notice the new config values and update its settings using the tr2 config parsing function, i.e. tr2_cfg_cb(). - builtin/checkout.c:checkout_main() This calls git_xmerge_config() as a shorthand for parsing a CLI arg. This might be worth refactoring away in the future, since git_xmerge_config() can call git_default_config(), which can do much more than just parsing. Handle them by creating a KVI_INIT macro that initializes "struct key_value_info" to a reasonable default, and use that to construct the "ctx" arg. Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-29 03:26:22 +08:00
static int get_alias(const char *var, const char *value,
const struct config_context *ctx UNUSED, void *data)
{
struct string_list *list = data;
if (skip_prefix(var, "alias.", &var)) {
if (!value)
return config_error_nonbool(var);
string_list_append(list, var)->util = xstrdup(value);
}
return 0;
}
static void list_all_cmds_help_external_commands(void)
{
struct string_list others = STRING_LIST_INIT_DUP;
int i;
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);
}
static void list_all_cmds_help_aliases(int longest)
{
struct string_list alias_list = STRING_LIST_INIT_DUP;
struct cmdname_help *aliases;
int i;
git_config(get_alias, &alias_list);
string_list_sort(&alias_list);
for (i = 0; i < alias_list.nr; i++) {
size_t len = strlen(alias_list.items[i].string);
if (longest < len)
longest = len;
}
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(&alias_list, 1);
}
void list_all_cmds_help(int show_external_commands, int show_aliases)
{
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)
{
int i;
for (i = 0; i < c->cnt; i++)
if (!strcmp(s, c->names[i]->name))
return 1;
return 0;
}
static int autocorrect;
static struct cmdnames aliases;
#define AUTOCORRECT_PROMPT (-3)
#define AUTOCORRECT_NEVER (-2)
#define AUTOCORRECT_IMMEDIATELY (-1)
static int git_unknown_cmd_config(const char *var, const char *value,
const struct config_context *ctx,
void *cb UNUSED)
{
const char *p;
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, ctx->kvi);
autocorrect = (v < 0)
? AUTOCORRECT_IMMEDIATELY : v;
}
}
/* Also use aliases for command lookup */
if (skip_prefix(var, "alias.", &p))
add_cmdname(&aliases, p, strlen(p));
help.c: fix autocorrect in work tree for bare repository Currently, auto correction doesn't work reliably for commands which must run in a work tree (e.g. `git status`) in Git work trees which are created from a bare repository. As far as I'm able to determine, this has been broken since commit 659fef199f (help: use early config when autocorrecting aliases, 2017-06-14), where the call to `git_config()` in `help_unknown_cmd()` was replaced with a call to `read_early_config()`. From what I can tell, the actual cause for the unexpected error is that we call `git_default_config()` in the `git_unknown_cmd_config` callback instead of simply returning `0` for config entries which we aren't interested in. Calling `git_default_config()` in this callback to `read_early_config()` seems like a bad idea since those calls will initialize a bunch of state in `environment.c` (among other things `is_bare_repository_cfg`) before we've properly detected that we're running in a work tree. All other callbacks provided to `read_early_config()` appear to only extract their configurations while simply returning `0` for all other config keys. This commit changes the `git_unknown_cmd_config` callback to not call `git_default_config()`. Instead we also simply return `0` for config keys which we're not interested in. Additionally the commit adds a new test case covering `help.autocorrect` in a work tree created from a bare clone. Signed-off-by: Simon Gerber <gesimu@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-10-30 03:56:14 +08:00
return 0;
}
static int levenshtein_compare(const void *p1, const void *p2)
{
const struct cmdname *const *c1 = p1, *const *c2 = p2;
const char *s1 = (*c1)->name, *s2 = (*c2)->name;
int l1 = (*c1)->len;
int l2 = (*c2)->len;
return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
}
static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
{
int i;
ALLOC_GROW(cmds->names, cmds->cnt + old->cnt, cmds->alloc);
for (i = 0; i < old->cnt; i++)
cmds->names[cmds->cnt++] = old->names[i];
FREE_AND_NULL(old->names);
old->cnt = 0;
}
/* An empirically derived magic number */
#define SIMILARITY_FLOOR 7
#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
static const char bad_interpreter_advice[] =
N_("'%s' appears to be a git command, but we were not\n"
"able to execute it. Maybe git-%s is broken?");
const char *help_unknown_cmd(const char *cmd)
{
int i, n, best_similarity = 0;
struct cmdnames main_cmds, other_cmds;
struct cmdname_help *common_cmds;
memset(&main_cmds, 0, sizeof(main_cmds));
memset(&other_cmds, 0, sizeof(other_cmds));
memset(&aliases, 0, sizeof(aliases));
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);
add_cmd_list(&main_cmds, &other_cmds);
QSORT(main_cmds.names, main_cmds.cnt, cmdname_compare);
uniq(&main_cmds);
extract_cmds(&common_cmds, common_mask);
/* This abuses cmdname->len for levenshtein distance */
for (i = 0, n = 0; i < main_cmds.cnt; i++) {
int cmp = 0; /* avoid compiler stupidity */
const char *candidate = main_cmds.names[i]->name;
/*
* An exact match means we have the command, but
* for some reason exec'ing it gave us ENOENT; probably
* it's a bad interpreter in the #! line.
*/
if (!strcmp(candidate, cmd))
die(_(bad_interpreter_advice), cmd, cmd);
/* Does the candidate appear in common_cmds list? */
while (common_cmds[n].name &&
(cmp = strcmp(common_cmds[n].name, candidate)) < 0)
n++;
if (common_cmds[n].name && !cmp) {
/* Yes, this is one of the common commands */
n++; /* use the entry from common_cmds[] */
if (starts_with(candidate, cmd)) {
/* Give prefix match a very good score */
main_cmds.names[i]->len = 0;
continue;
}
}
main_cmds.names[i]->len =
levenshtein(cmd, candidate, 0, 2, 1, 3) + 1;
}
FREE_AND_NULL(common_cmds);
QSORT(main_cmds.names, main_cmds.cnt, levenshtein_compare);
if (!main_cmds.cnt)
die(_("Uh oh. Your system reports no Git commands at all."));
/* skip and count prefix matches */
for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
; /* still counting */
if (main_cmds.cnt <= n) {
/* prefix matches with everything? that is too ambiguous */
best_similarity = SIMILARITY_FLOOR + 1;
} else {
/* count all the most similar ones */
for (best_similarity = main_cmds.names[n++]->len;
(n < main_cmds.cnt &&
best_similarity == main_cmds.names[n]->len);
n++)
; /* still counting */
}
if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
clean_cmdnames(&main_cmds);
fprintf_ln(stderr,
_("WARNING: You called a Git command named '%s', "
"which does not exist."),
cmd);
if (autocorrect == AUTOCORRECT_IMMEDIATELY)
fprintf_ln(stderr,
_("Continuing under the assumption that "
"you meant '%s'."),
assumed);
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'."),
(float)autocorrect/10.0, assumed);
sleep_millisec(autocorrect * 100);
}
return assumed;
}
fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
if (SIMILAR_ENOUGH(best_similarity)) {
fprintf_ln(stderr,
usability: don't ask questions if no reply is required There has been a bug report by a corporate user that stated that "spelling mistake of stash followed by a yes prints character 'y' infinite times." This analysis was false. When the spelling of a command contains errors, the git program tries to help the user by providing candidates which are close to the unexisting command. E.g Git prints the following: git: 'stahs' is not a git command. See 'git --help'. Did you mean this? stash and then exits. The problem with this hint is that it is not formally indicated as an hint and the user is in fact encouraged to reply to the question, whereas the Git command is already finished. The user was unlucky enough that it was the command he was looking for, and replied "yes" on the command line, effectively launching the `yes` program. The initial error is that the Git programs, when launched in command-line mode (without interaction) must not ask questions, because these questions would normally require a user input as a reply that they won't handle indeed. That's a source of confusion on UX level. To improve the general usability of the Git suite, the following rule was applied: if the sentence * appears in a non-interactive session * is printed last before exit * is a question addressing the user ("you") the sentence is turned into affirmative and proposes the option. The basic rewording of the question sentences has been extended to other spots found in the source. Requested at https://github.com/git/git-scm.com/issues/999 by rpai1 Signed-off-by: Jean-Noel Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-05-11 20:06:32 +08:00
Q_("\nThe most similar command is",
"\nThe most similar commands are",
n));
for (i = 0; i < n; i++)
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
}
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 [--build-options]"),
NULL
};
struct option options[] = {
OPT_BOOL(0, "build-options", &build_options,
"also print build options"),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, usage, 0);
get_version_info(&buf, build_options);
printf("%s", buf.buf);
strbuf_release(&buf);
return 0;
}
struct similar_ref_cb {
const char *base_ref;
struct string_list *similar_refs;
};
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;
/* A remote branch of the same name is deemed similar */
if (starts_with(refname, "refs/remotes/") &&
!strcmp(branch, cb->base_ref))
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_DUP;
ref_cb.base_ref = ref;
ref_cb.similar_refs = &similar_refs;
for_each_ref(append_similar_ref, &ref_cb);
return similar_refs;
}
NORETURN void help_unknown_ref(const char *ref, const char *cmd,
const char *error)
{
int i;
struct string_list suggested_refs = guess_refs(ref);
fprintf_ln(stderr, _("%s: %s - %s"), cmd, ref, error);
if (suggested_refs.nr > 0) {
fprintf_ln(stderr,
Q_("\nDid you mean this?",
"\nDid you mean one of these?",
suggested_refs.nr));
for (i = 0; i < suggested_refs.nr; i++)
fprintf(stderr, "\t%s\n", suggested_refs.items[i].string);
}
string_list_clear(&suggested_refs, 0);
exit(1);
}