2006-04-21 21:27:34 +04:00
|
|
|
#include "cache.h"
|
2017-06-14 21:07:36 +03:00
|
|
|
#include "config.h"
|
2006-04-21 21:27:34 +04:00
|
|
|
#include "builtin.h"
|
2018-04-11 00:26:18 +03:00
|
|
|
#include "exec-cmd.h"
|
2017-04-26 02:46:59 +03:00
|
|
|
#include "run-command.h"
|
2008-08-31 17:50:23 +04:00
|
|
|
#include "levenshtein.h"
|
2008-07-30 03:16:58 +04:00
|
|
|
#include "help.h"
|
2018-05-10 11:46:42 +03:00
|
|
|
#include "command-list.h"
|
2012-04-13 14:54:37 +04:00
|
|
|
#include "string-list.h"
|
|
|
|
#include "column.h"
|
2012-06-02 22:51:42 +04:00
|
|
|
#include "version.h"
|
2013-05-04 04:04:19 +04:00
|
|
|
#include "refs.h"
|
2017-05-30 08:17:42 +03:00
|
|
|
#include "parse-options.h"
|
2008-03-07 10:46:28 +03:00
|
|
|
|
2018-05-10 11:46:42 +03:00
|
|
|
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 }
|
|
|
|
};
|
2018-05-20 21:40:01 +03:00
|
|
|
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") },
|
2019-11-05 20:07:26 +03:00
|
|
|
{ CAT_synchingrepositories, N_("Low-level Commands / Syncing Repositories") },
|
2018-05-20 21:40:01 +03:00
|
|
|
{ CAT_purehelpers, N_("Low-level Commands / Internal Helpers") },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
2018-05-10 11:46:42 +03:00
|
|
|
|
2018-05-20 21:40:02 +03:00
|
|
|
static const char *drop_prefix(const char *name, uint32_t category)
|
2018-05-10 11:46:42 +03:00
|
|
|
{
|
|
|
|
const char *new_name;
|
|
|
|
|
|
|
|
if (skip_prefix(name, "git-", &new_name))
|
|
|
|
return new_name;
|
2018-05-20 21:40:02 +03:00
|
|
|
if (category == CAT_guide && skip_prefix(name, "git", &new_name))
|
|
|
|
return new_name;
|
2018-05-10 11:46:42 +03:00
|
|
|
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;
|
2018-05-20 21:40:02 +03:00
|
|
|
cmds[nr].name = drop_prefix(cmd->name, cmd->category);
|
2018-05-10 11:46:42 +03:00
|
|
|
|
|
|
|
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) {
|
2018-12-11 17:58:11 +03:00
|
|
|
size_t len = strlen(cmds[i].name);
|
2018-05-10 11:46:42 +03:00
|
|
|
printf(" %s ", cmds[i].name);
|
2019-01-31 12:23:49 +03:00
|
|
|
if (longest > len)
|
|
|
|
mput_char(' ', longest - len);
|
2018-05-10 11:46:42 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-09-29 09:08:14 +03:00
|
|
|
static void print_cmd_by_category(const struct category_description *catdesc,
|
|
|
|
int *longest_p)
|
2018-05-10 11:46:42 +03:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
printf("\n%s\n", _(desc));
|
|
|
|
print_command_list(cmds, mask, longest);
|
|
|
|
}
|
|
|
|
free(cmds);
|
2018-09-29 09:08:14 +03:00
|
|
|
if (longest_p)
|
|
|
|
*longest_p = longest;
|
2018-05-10 11:46:42 +03:00
|
|
|
}
|
|
|
|
|
2008-07-30 03:16:58 +04:00
|
|
|
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
|
2006-04-21 21:27:34 +04:00
|
|
|
{
|
2016-02-23 01:44:32 +03:00
|
|
|
struct cmdname *ent;
|
|
|
|
FLEX_ALLOC_MEM(ent, name, name, len);
|
2006-04-21 21:27:34 +04:00
|
|
|
ent->len = len;
|
2007-10-29 06:30:52 +03:00
|
|
|
|
|
|
|
ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
|
|
|
|
cmds->names[cmds->cnt++] = ent;
|
2006-04-21 21:27:34 +04:00
|
|
|
}
|
|
|
|
|
2008-08-31 17:50:23 +04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2006-04-21 21:27:34 +04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2007-10-29 06:30:52 +03:00
|
|
|
static void uniq(struct cmdnames *cmds)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
if (!cmds->cnt)
|
|
|
|
return;
|
|
|
|
|
2012-07-25 20:16:19 +04:00
|
|
|
for (i = j = 1; i < cmds->cnt; i++) {
|
|
|
|
if (!strcmp(cmds->names[i]->name, cmds->names[j-1]->name))
|
|
|
|
free(cmds->names[i]);
|
|
|
|
else
|
2007-10-29 06:30:52 +03:00
|
|
|
cmds->names[j++] = cmds->names[i];
|
2012-07-25 20:16:19 +04:00
|
|
|
}
|
2007-10-29 06:30:52 +03:00
|
|
|
|
|
|
|
cmds->cnt = j;
|
|
|
|
}
|
|
|
|
|
2008-07-30 03:16:58 +04:00
|
|
|
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
|
2007-11-09 02:35:32 +03:00
|
|
|
{
|
2007-10-29 06:30:52 +03:00
|
|
|
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++];
|
2012-07-25 22:01:12 +04:00
|
|
|
else if (cmp == 0) {
|
|
|
|
ei++;
|
|
|
|
free(cmds->names[ci++]);
|
|
|
|
} else if (cmp > 0)
|
2007-10-29 06:30:52 +03:00
|
|
|
ei++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (ci < cmds->cnt)
|
|
|
|
cmds->names[cj++] = cmds->names[ci++];
|
|
|
|
|
|
|
|
cmds->cnt = cj;
|
|
|
|
}
|
|
|
|
|
2014-02-28 23:27:29 +04:00
|
|
|
static void pretty_print_cmdnames(struct cmdnames *cmds, unsigned int colopts)
|
2006-04-21 21:27:34 +04:00
|
|
|
{
|
2012-04-13 14:54:37 +04:00
|
|
|
struct string_list list = STRING_LIST_INIT_NODUP;
|
|
|
|
struct column_options copts;
|
|
|
|
int i;
|
2006-04-21 21:27:34 +04:00
|
|
|
|
2012-04-13 14:54:37 +04:00
|
|
|
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);
|
2006-04-21 21:27:34 +04:00
|
|
|
}
|
|
|
|
|
2008-08-28 21:15:33 +04:00
|
|
|
static void list_commands_in_dir(struct cmdnames *cmds,
|
2008-07-30 03:16:58 +04:00
|
|
|
const char *path,
|
|
|
|
const char *prefix)
|
2006-04-21 21:27:34 +04:00
|
|
|
{
|
2007-10-29 06:30:52 +03:00
|
|
|
DIR *dir = opendir(path);
|
2006-04-21 21:27:34 +04:00
|
|
|
struct dirent *de;
|
2008-07-28 00:34:14 +04:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
int len;
|
2006-04-21 21:27:34 +04:00
|
|
|
|
2008-07-28 00:34:14 +04:00
|
|
|
if (!dir)
|
2008-08-28 21:15:33 +04:00
|
|
|
return;
|
2008-07-30 03:16:58 +04:00
|
|
|
if (!prefix)
|
|
|
|
prefix = "git-";
|
2006-04-21 21:27:34 +04:00
|
|
|
|
2008-07-28 00:34:14 +04:00
|
|
|
strbuf_addf(&buf, "%s/", path);
|
|
|
|
len = buf.len;
|
|
|
|
|
2006-04-21 21:27:34 +04:00
|
|
|
while ((de = readdir(dir)) != NULL) {
|
2014-06-18 23:57:17 +04:00
|
|
|
const char *ent;
|
2014-06-30 20:58:51 +04:00
|
|
|
size_t entlen;
|
2006-04-21 21:27:34 +04:00
|
|
|
|
2014-06-18 23:57:17 +04:00
|
|
|
if (!skip_prefix(de->d_name, prefix, &ent))
|
2006-04-21 21:27:34 +04:00
|
|
|
continue;
|
2007-10-27 12:36:52 +04:00
|
|
|
|
2008-07-28 00:34:14 +04:00
|
|
|
strbuf_setlen(&buf, len);
|
|
|
|
strbuf_addstr(&buf, de->d_name);
|
|
|
|
if (!is_executable(buf.buf))
|
2006-04-21 21:27:34 +04:00
|
|
|
continue;
|
|
|
|
|
2014-06-18 23:57:17 +04:00
|
|
|
entlen = strlen(ent);
|
2014-07-16 22:25:59 +04:00
|
|
|
strip_suffix(ent, ".exe", &entlen);
|
2006-04-21 21:27:34 +04:00
|
|
|
|
2014-06-18 23:57:17 +04:00
|
|
|
add_cmdname(cmds, ent, entlen);
|
2006-04-21 21:27:34 +04:00
|
|
|
}
|
|
|
|
closedir(dir);
|
2008-07-28 00:34:14 +04:00
|
|
|
strbuf_release(&buf);
|
2007-10-29 06:30:52 +03:00
|
|
|
}
|
|
|
|
|
2008-08-28 21:15:33 +04:00
|
|
|
void load_command_list(const char *prefix,
|
2008-07-30 03:16:58 +04:00
|
|
|
struct cmdnames *main_cmds,
|
|
|
|
struct cmdnames *other_cmds)
|
2007-10-29 06:30:52 +03:00
|
|
|
{
|
|
|
|
const char *env_path = getenv("PATH");
|
|
|
|
const char *exec_path = git_exec_path();
|
|
|
|
|
2008-08-28 21:19:07 +04:00
|
|
|
if (exec_path) {
|
2008-08-28 21:15:33 +04:00
|
|
|
list_commands_in_dir(main_cmds, exec_path, prefix);
|
2016-09-29 18:27:31 +03:00
|
|
|
QSORT(main_cmds->names, main_cmds->cnt, cmdname_compare);
|
2008-08-28 21:19:07 +04:00
|
|
|
uniq(main_cmds);
|
2007-10-29 06:30:52 +03:00
|
|
|
}
|
|
|
|
|
2008-08-28 21:19:07 +04:00
|
|
|
if (env_path) {
|
|
|
|
char *paths, *path, *colon;
|
|
|
|
path = paths = xstrdup(env_path);
|
|
|
|
while (1) {
|
|
|
|
if ((colon = strchr(path, PATH_SEP)))
|
|
|
|
*colon = 0;
|
2008-09-10 19:54:28 +04:00
|
|
|
if (!exec_path || strcmp(path, exec_path))
|
|
|
|
list_commands_in_dir(other_cmds, path, prefix);
|
2007-10-29 06:30:52 +03:00
|
|
|
|
2008-08-28 21:19:07 +04:00
|
|
|
if (!colon)
|
|
|
|
break;
|
|
|
|
path = colon + 1;
|
|
|
|
}
|
|
|
|
free(paths);
|
2007-10-29 06:30:52 +03:00
|
|
|
|
2016-09-29 18:27:31 +03:00
|
|
|
QSORT(other_cmds->names, other_cmds->cnt, cmdname_compare);
|
2008-08-28 21:19:07 +04:00
|
|
|
uniq(other_cmds);
|
|
|
|
}
|
2008-07-30 03:16:58 +04:00
|
|
|
exclude_cmds(other_cmds, main_cmds);
|
2008-02-25 01:17:37 +03:00
|
|
|
}
|
|
|
|
|
2012-05-04 02:13:31 +04:00
|
|
|
void list_commands(unsigned int colopts,
|
2012-04-13 14:54:37 +04:00
|
|
|
struct cmdnames *main_cmds, struct cmdnames *other_cmds)
|
2008-02-25 01:17:37 +03:00
|
|
|
{
|
2008-07-30 03:16:58 +04:00
|
|
|
if (main_cmds->cnt) {
|
2008-08-28 21:19:42 +04:00
|
|
|
const char *exec_path = git_exec_path();
|
2012-04-25 15:21:53 +04:00
|
|
|
printf_ln(_("available git commands in '%s'"), exec_path);
|
2007-10-29 06:30:52 +03:00
|
|
|
putchar('\n');
|
2014-02-28 23:27:29 +04:00
|
|
|
pretty_print_cmdnames(main_cmds, colopts);
|
2007-10-29 06:30:52 +03:00
|
|
|
putchar('\n');
|
|
|
|
}
|
|
|
|
|
2008-07-30 03:16:58 +04:00
|
|
|
if (other_cmds->cnt) {
|
2012-04-25 15:21:53 +04:00
|
|
|
printf_ln(_("git commands available from elsewhere on your $PATH"));
|
2008-07-30 03:16:58 +04:00
|
|
|
putchar('\n');
|
2014-02-28 23:27:29 +04:00
|
|
|
pretty_print_cmdnames(other_cmds, colopts);
|
2007-10-29 06:30:52 +03:00
|
|
|
putchar('\n');
|
|
|
|
}
|
2006-04-21 21:27:34 +04:00
|
|
|
}
|
|
|
|
|
2013-01-19 10:35:04 +04:00
|
|
|
void list_common_cmds_help(void)
|
2015-05-21 20:39:22 +03:00
|
|
|
{
|
|
|
|
puts(_("These are common Git commands used in various situations:"));
|
2018-09-29 09:08:14 +03:00
|
|
|
print_cmd_by_category(common_categories, NULL);
|
2013-01-19 10:35:04 +04:00
|
|
|
}
|
2015-05-21 20:39:22 +03:00
|
|
|
|
2018-05-20 21:39:59 +03:00
|
|
|
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);
|
2015-05-21 20:39:22 +03:00
|
|
|
}
|
|
|
|
|
2018-05-20 21:39:59 +03:00
|
|
|
void list_all_other_cmds(struct string_list *list)
|
2013-01-19 10:35:04 +04:00
|
|
|
{
|
2018-05-20 21:39:59 +03:00
|
|
|
struct cmdnames main_cmds, other_cmds;
|
|
|
|
int i;
|
2013-01-19 10:35:04 +04:00
|
|
|
|
2018-05-20 21:39:59 +03:00
|
|
|
memset(&main_cmds, 0, sizeof(main_cmds));
|
|
|
|
memset(&other_cmds, 0, sizeof(other_cmds));
|
|
|
|
load_command_list("git-", &main_cmds, &other_cmds);
|
2013-01-19 10:35:04 +04:00
|
|
|
|
2018-05-20 21:39:59 +03:00
|
|
|
for (i = 0; i < other_cmds.cnt; i++)
|
|
|
|
string_list_append(list, other_cmds.names[i]->name);
|
2015-05-21 20:39:22 +03:00
|
|
|
|
2018-05-20 21:39:59 +03:00
|
|
|
clean_cmdnames(&main_cmds);
|
|
|
|
clean_cmdnames(&other_cmds);
|
|
|
|
}
|
|
|
|
|
2018-05-20 21:40:00 +03:00
|
|
|
void list_cmds_by_category(struct string_list *list,
|
|
|
|
const char *cat)
|
|
|
|
{
|
|
|
|
int i, n = ARRAY_SIZE(command_list);
|
|
|
|
uint32_t cat_id = 0;
|
2015-05-21 20:39:22 +03:00
|
|
|
|
2018-05-20 21:40:00 +03:00
|
|
|
for (i = 0; category_names[i]; i++) {
|
|
|
|
if (!strcmp(cat, category_names[i])) {
|
|
|
|
cat_id = 1UL << i;
|
|
|
|
break;
|
2015-05-21 20:39:22 +03:00
|
|
|
}
|
2018-05-20 21:40:00 +03:00
|
|
|
}
|
|
|
|
if (!cat_id)
|
|
|
|
die(_("unsupported command listing type '%s'"), cat);
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
struct cmdname_help *cmd = command_list + i;
|
|
|
|
|
2018-05-20 21:40:02 +03:00
|
|
|
if (!(cmd->category & cat_id))
|
|
|
|
continue;
|
|
|
|
string_list_append(list, drop_prefix(cmd->name, cmd->category));
|
2018-05-20 21:40:00 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 21:40:09 +03:00
|
|
|
void list_cmds_by_config(struct string_list *list)
|
|
|
|
{
|
|
|
|
const char *cmd_list;
|
|
|
|
|
|
|
|
if (git_config_get_string_const("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, ' ');
|
2015-05-21 20:39:22 +03:00
|
|
|
|
2018-05-20 21:40:09 +03:00
|
|
|
strbuf_add(&sb, cmd_list, p - cmd_list);
|
2019-03-20 21:03:28 +03:00
|
|
|
if (sb.buf[0] == '-')
|
|
|
|
string_list_remove(list, sb.buf + 1, 0);
|
2018-05-20 21:40:09 +03:00
|
|
|
else
|
|
|
|
string_list_insert(list, sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
while (*p == ' ')
|
|
|
|
p++;
|
|
|
|
cmd_list = p;
|
2013-01-19 10:35:04 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-20 21:40:02 +03:00
|
|
|
void list_common_guides_help(void)
|
|
|
|
{
|
|
|
|
struct category_description catdesc[] = {
|
|
|
|
{ CAT_guide, N_("The common Git guides are:") },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
2018-09-29 09:08:14 +03:00
|
|
|
print_cmd_by_category(catdesc, NULL);
|
2018-05-20 21:40:02 +03:00
|
|
|
putchar('\n');
|
|
|
|
}
|
|
|
|
|
2018-05-26 16:55:24 +03:00
|
|
|
struct slot_expansion {
|
|
|
|
const char *prefix;
|
|
|
|
const char *placeholder;
|
|
|
|
void (*fn)(struct string_list *list, const char *prefix);
|
|
|
|
int found;
|
|
|
|
};
|
|
|
|
|
2018-05-26 16:55:28 +03:00
|
|
|
void list_config_help(int for_human)
|
2018-05-26 16:55:24 +03:00
|
|
|
{
|
|
|
|
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 },
|
sideband: highlight keywords in remote sideband output
The colorization is controlled with the config setting "color.remote".
Supported keywords are "error", "warning", "hint" and "success". They
are highlighted if they appear at the start of the line, which is
common in error messages, eg.
ERROR: commit is missing Change-Id
The Git push process itself prints lots of non-actionable messages
(eg. bandwidth statistics, object counters for different phases of the
process). This obscures actionable error messages that servers may
send back. Highlighting keywords in the sideband draws more attention
to those messages.
The background for this change is that Gerrit does server-side
processing to create or update code reviews, and actionable error
messages (eg. missing Change-Id) must be communicated back to the user
during the push. User research has shown that new users have trouble
seeing these messages.
The highlighting is done on the client rather than server side, so
servers don't have to grow capabilities to understand terminal escape
codes and terminal state. It also consistent with the current state
where Git is control of the local display (eg. prefixing messages with
"remote: ").
The highlighting can be configured using color.remote.<KEYWORD>
configuration settings. Since the keys are matched case insensitively,
we match the keywords case insensitively too.
Finally, this solution is backwards compatible: many servers already
prefix their messages with "error", and they will benefit from this
change without requiring a server update. By contrast, a server-side
solution would likely require plumbing the TERM variable through the
git protocol, so it would require changes to both server and client.
Helped-by: Duy Nguyen <pclouds@gmail.com>
Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-08-07 15:51:08 +03:00
|
|
|
{ "color.remote", "<slot>", list_config_color_sideband_slots },
|
2018-05-26 16:55:24 +03:00
|
|
|
{ "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 }
|
|
|
|
};
|
|
|
|
const char **p;
|
|
|
|
struct slot_expansion *e;
|
|
|
|
struct string_list keys = STRING_LIST_INIT_DUP;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (p = config_name_list; *p; p++) {
|
|
|
|
const char *var = *p;
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
|
|
|
|
for (e = slot_expansions; e->prefix; e++) {
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (e = slot_expansions; e->prefix; e++)
|
|
|
|
if (!e->found)
|
|
|
|
BUG("slot_expansion %s.%s is not used",
|
|
|
|
e->prefix, e->placeholder);
|
|
|
|
|
|
|
|
string_list_sort(&keys);
|
2018-05-26 16:55:28 +03:00
|
|
|
for (i = 0; i < keys.nr; i++) {
|
|
|
|
const char *var = keys.items[i].string;
|
|
|
|
const char *wildcard, *tag, *cut;
|
|
|
|
|
|
|
|
if (for_human) {
|
|
|
|
puts(var);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
wildcard = strchr(var, '*');
|
|
|
|
tag = strchr(var, '<');
|
|
|
|
|
|
|
|
if (!wildcard && !tag) {
|
|
|
|
puts(var);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wildcard && !tag)
|
|
|
|
cut = wildcard;
|
|
|
|
else if (!wildcard && tag)
|
|
|
|
cut = tag;
|
|
|
|
else
|
|
|
|
cut = wildcard < tag ? wildcard : tag;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We may produce duplicates, but that's up to
|
|
|
|
* git-completion.bash to handle
|
|
|
|
*/
|
|
|
|
printf("%.*s\n", (int)(cut - var), var);
|
|
|
|
}
|
2018-05-26 16:55:24 +03:00
|
|
|
string_list_clear(&keys, 0);
|
|
|
|
}
|
|
|
|
|
2018-09-29 09:08:14 +03:00
|
|
|
static int get_alias(const char *var, const char *value, void *data)
|
|
|
|
{
|
|
|
|
struct string_list *list = data;
|
|
|
|
|
|
|
|
if (skip_prefix(var, "alias.", &var))
|
|
|
|
string_list_append(list, var)->util = xstrdup(value);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-20 21:40:01 +03:00
|
|
|
void list_all_cmds_help(void)
|
|
|
|
{
|
2018-09-29 09:08:14 +03:00
|
|
|
struct string_list others = STRING_LIST_INIT_DUP;
|
|
|
|
struct string_list alias_list = STRING_LIST_INIT_DUP;
|
|
|
|
struct cmdname_help *aliases;
|
|
|
|
int i, longest;
|
|
|
|
|
|
|
|
printf_ln(_("See 'git help <command>' to read about a specific subcommand"));
|
|
|
|
print_cmd_by_category(main_categories, &longest);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
git_config(get_alias, &alias_list);
|
|
|
|
string_list_sort(&alias_list);
|
2018-12-11 17:58:11 +03:00
|
|
|
|
|
|
|
for (i = 0; i < alias_list.nr; i++) {
|
|
|
|
size_t len = strlen(alias_list.items[i].string);
|
|
|
|
if (longest < len)
|
|
|
|
longest = len;
|
|
|
|
}
|
|
|
|
|
2018-09-29 09:08:14 +03:00
|
|
|
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);
|
2018-05-20 21:40:01 +03:00
|
|
|
}
|
|
|
|
|
2008-07-30 03:16:58 +04:00
|
|
|
int is_in_cmdlist(struct cmdnames *c, const char *s)
|
2008-02-25 01:17:37 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < c->cnt; i++)
|
|
|
|
if (!strcmp(s, c->names[i]->name))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-08-31 17:54:58 +04:00
|
|
|
static int autocorrect;
|
2008-09-10 19:54:28 +04:00
|
|
|
static struct cmdnames aliases;
|
2008-08-31 17:54:58 +04:00
|
|
|
|
|
|
|
static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
|
|
|
|
{
|
use skip_prefix to avoid magic numbers
It's a common idiom to match a prefix and then skip past it
with a magic number, like:
if (starts_with(foo, "bar"))
foo += 3;
This is easy to get wrong, since you have to count the
prefix string yourself, and there's no compiler check if the
string changes. We can use skip_prefix to avoid the magic
numbers here.
Note that some of these conversions could be much shorter.
For example:
if (starts_with(arg, "--foo=")) {
bar = arg + 6;
continue;
}
could become:
if (skip_prefix(arg, "--foo=", &bar))
continue;
However, I have left it as:
if (skip_prefix(arg, "--foo=", &v)) {
bar = v;
continue;
}
to visually match nearby cases which need to actually
process the string. Like:
if (skip_prefix(arg, "--foo=", &v)) {
bar = atoi(v);
continue;
}
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 23:47:50 +04:00
|
|
|
const char *p;
|
|
|
|
|
2008-08-31 17:54:58 +04:00
|
|
|
if (!strcmp(var, "help.autocorrect"))
|
|
|
|
autocorrect = git_config_int(var,value);
|
2008-09-10 19:54:28 +04:00
|
|
|
/* Also use aliases for command lookup */
|
use skip_prefix to avoid magic numbers
It's a common idiom to match a prefix and then skip past it
with a magic number, like:
if (starts_with(foo, "bar"))
foo += 3;
This is easy to get wrong, since you have to count the
prefix string yourself, and there's no compiler check if the
string changes. We can use skip_prefix to avoid the magic
numbers here.
Note that some of these conversions could be much shorter.
For example:
if (starts_with(arg, "--foo=")) {
bar = arg + 6;
continue;
}
could become:
if (skip_prefix(arg, "--foo=", &bar))
continue;
However, I have left it as:
if (skip_prefix(arg, "--foo=", &v)) {
bar = v;
continue;
}
to visually match nearby cases which need to actually
process the string. Like:
if (skip_prefix(arg, "--foo=", &v)) {
bar = atoi(v);
continue;
}
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 23:47:50 +04:00
|
|
|
if (skip_prefix(var, "alias.", &p))
|
|
|
|
add_cmdname(&aliases, p, strlen(p));
|
2008-08-31 17:54:58 +04:00
|
|
|
|
|
|
|
return git_default_config(var, value, cb);
|
|
|
|
}
|
|
|
|
|
2008-08-31 17:50:23 +04:00
|
|
|
static int levenshtein_compare(const void *p1, const void *p2)
|
2006-07-31 01:42:25 +04:00
|
|
|
{
|
2008-08-31 17:50:23 +04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-09-10 19:54:28 +04:00
|
|
|
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];
|
2017-06-16 02:15:49 +03:00
|
|
|
FREE_AND_NULL(old->names);
|
2008-09-10 19:54:28 +04:00
|
|
|
old->cnt = 0;
|
|
|
|
}
|
|
|
|
|
2009-12-15 10:57:18 +03:00
|
|
|
/* An empirically derived magic number */
|
2010-11-26 19:00:39 +03:00
|
|
|
#define SIMILARITY_FLOOR 7
|
|
|
|
#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
|
2009-12-15 10:57:18 +03:00
|
|
|
|
2011-07-08 14:08:49 +04:00
|
|
|
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?");
|
|
|
|
|
2008-08-31 17:50:23 +04:00
|
|
|
const char *help_unknown_cmd(const char *cmd)
|
2006-07-31 01:42:25 +04:00
|
|
|
{
|
2008-08-31 17:50:23 +04:00
|
|
|
int i, n, best_similarity = 0;
|
|
|
|
struct cmdnames main_cmds, other_cmds;
|
2018-05-10 11:46:42 +03:00
|
|
|
struct cmdname_help *common_cmds;
|
2008-08-31 17:50:23 +04:00
|
|
|
|
|
|
|
memset(&main_cmds, 0, sizeof(main_cmds));
|
2009-08-11 14:10:21 +04:00
|
|
|
memset(&other_cmds, 0, sizeof(other_cmds));
|
2008-09-10 19:54:28 +04:00
|
|
|
memset(&aliases, 0, sizeof(aliases));
|
2008-08-31 17:50:23 +04:00
|
|
|
|
2017-06-14 14:35:50 +03:00
|
|
|
read_early_config(git_unknown_cmd_config, NULL);
|
2008-08-31 17:54:58 +04:00
|
|
|
|
2008-08-31 17:50:23 +04:00
|
|
|
load_command_list("git-", &main_cmds, &other_cmds);
|
|
|
|
|
2008-09-10 19:54:28 +04:00
|
|
|
add_cmd_list(&main_cmds, &aliases);
|
|
|
|
add_cmd_list(&main_cmds, &other_cmds);
|
2016-09-29 18:27:31 +03:00
|
|
|
QSORT(main_cmds.names, main_cmds.cnt, cmdname_compare);
|
2008-09-10 19:54:28 +04:00
|
|
|
uniq(&main_cmds);
|
2008-08-31 17:50:23 +04:00
|
|
|
|
2018-05-10 11:46:42 +03:00
|
|
|
extract_cmds(&common_cmds, common_mask);
|
|
|
|
|
2010-11-26 19:00:39 +03:00
|
|
|
/* 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;
|
|
|
|
|
2011-07-08 14:08:49 +04:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2010-11-26 19:00:39 +03:00
|
|
|
/* Does the candidate appear in common_cmds list? */
|
2018-05-10 11:46:42 +03:00
|
|
|
while (common_cmds[n].name &&
|
2010-11-26 19:00:39 +03:00
|
|
|
(cmp = strcmp(common_cmds[n].name, candidate)) < 0)
|
|
|
|
n++;
|
2018-05-10 11:46:42 +03:00
|
|
|
if (common_cmds[n].name && !cmp) {
|
2010-11-26 19:00:39 +03:00
|
|
|
/* Yes, this is one of the common commands */
|
|
|
|
n++; /* use the entry from common_cmds[] */
|
2013-12-01 00:55:40 +04:00
|
|
|
if (starts_with(candidate, cmd)) {
|
2010-11-26 19:00:39 +03:00
|
|
|
/* Give prefix match a very good score */
|
|
|
|
main_cmds.names[i]->len = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-31 17:50:23 +04:00
|
|
|
main_cmds.names[i]->len =
|
2012-05-27 20:02:58 +04:00
|
|
|
levenshtein(cmd, candidate, 0, 2, 1, 3) + 1;
|
2010-11-26 19:00:39 +03:00
|
|
|
}
|
2018-05-10 11:46:42 +03:00
|
|
|
FREE_AND_NULL(common_cmds);
|
2008-08-31 17:50:23 +04:00
|
|
|
|
2016-09-29 18:27:31 +03:00
|
|
|
QSORT(main_cmds.names, main_cmds.cnt, levenshtein_compare);
|
2008-08-31 17:50:23 +04:00
|
|
|
|
|
|
|
if (!main_cmds.cnt)
|
2012-04-23 16:30:24 +04:00
|
|
|
die(_("Uh oh. Your system reports no Git commands at all."));
|
2008-08-31 17:50:23 +04:00
|
|
|
|
2010-11-26 19:00:39 +03:00
|
|
|
/* 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 */
|
|
|
|
}
|
2009-12-15 10:57:18 +03:00
|
|
|
if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
|
2008-08-31 17:50:23 +04:00
|
|
|
const char *assumed = main_cmds.names[0]->name;
|
|
|
|
main_cmds.names[0] = NULL;
|
|
|
|
clean_cmdnames(&main_cmds);
|
2012-04-23 16:30:24 +04:00
|
|
|
fprintf_ln(stderr,
|
|
|
|
_("WARNING: You called a Git command named '%s', "
|
2017-06-21 16:57:38 +03:00
|
|
|
"which does not exist."),
|
|
|
|
cmd);
|
|
|
|
if (autocorrect < 0)
|
|
|
|
fprintf_ln(stderr,
|
|
|
|
_("Continuing under the assumption that "
|
|
|
|
"you meant '%s'."),
|
|
|
|
assumed);
|
|
|
|
else {
|
|
|
|
fprintf_ln(stderr,
|
|
|
|
_("Continuing in %0.1f seconds, "
|
|
|
|
"assuming that you meant '%s'."),
|
|
|
|
(float)autocorrect/10.0, assumed);
|
2015-06-05 22:45:05 +03:00
|
|
|
sleep_millisec(autocorrect * 100);
|
2008-08-31 17:54:58 +04:00
|
|
|
}
|
2008-08-31 17:50:23 +04:00
|
|
|
return assumed;
|
|
|
|
}
|
|
|
|
|
2012-04-23 16:30:24 +04:00
|
|
|
fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
|
2008-08-31 17:50:23 +04:00
|
|
|
|
2009-12-15 10:57:18 +03:00
|
|
|
if (SIMILAR_ENOUGH(best_similarity)) {
|
2012-04-23 16:30:24 +04:00
|
|
|
fprintf_ln(stderr,
|
2017-05-11 15:06:32 +03:00
|
|
|
Q_("\nThe most similar command is",
|
|
|
|
"\nThe most similar commands are",
|
2012-04-23 16:30:24 +04:00
|
|
|
n));
|
2008-08-31 17:50:23 +04:00
|
|
|
|
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
|
|
|
|
}
|
|
|
|
|
2006-07-31 01:42:25 +04:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2006-07-29 09:44:25 +04:00
|
|
|
int cmd_version(int argc, const char **argv, const char *prefix)
|
2006-04-21 21:27:34 +04:00
|
|
|
{
|
2017-05-30 08:17:42 +03:00
|
|
|
int build_options = 0;
|
|
|
|
const char * const usage[] = {
|
|
|
|
N_("git version [<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);
|
|
|
|
|
2013-04-17 00:33:25 +04:00
|
|
|
/*
|
|
|
|
* The format of this string should be kept stable for compatibility
|
|
|
|
* with external projects that rely on the output of "git version".
|
2017-05-30 08:17:42 +03:00
|
|
|
*
|
|
|
|
* Always show the version, even if other options are given.
|
2013-04-17 00:33:25 +04:00
|
|
|
*/
|
2006-04-21 21:27:34 +04:00
|
|
|
printf("git version %s\n", git_version_string);
|
2017-05-30 08:17:42 +03:00
|
|
|
|
|
|
|
if (build_options) {
|
2017-12-15 02:34:34 +03:00
|
|
|
printf("cpu: %s\n", GIT_HOST_CPU);
|
2017-12-15 02:34:38 +03:00
|
|
|
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");
|
2017-05-30 08:17:42 +03:00
|
|
|
printf("sizeof-long: %d\n", (int)sizeof(long));
|
2018-07-27 06:48:59 +03:00
|
|
|
printf("sizeof-size_t: %d\n", (int)sizeof(size_t));
|
2017-05-30 08:17:42 +03:00
|
|
|
/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
|
2016-07-12 02:54:18 +03:00
|
|
|
}
|
2006-04-21 21:27:34 +04:00
|
|
|
return 0;
|
|
|
|
}
|
2013-05-04 04:04:19 +04:00
|
|
|
|
|
|
|
struct similar_ref_cb {
|
|
|
|
const char *base_ref;
|
|
|
|
struct string_list *similar_refs;
|
|
|
|
};
|
|
|
|
|
2015-05-25 21:38:54 +03:00
|
|
|
static int append_similar_ref(const char *refname, const struct object_id *oid,
|
2013-05-04 04:04:19 +04:00
|
|
|
int flags, void *cb_data)
|
|
|
|
{
|
|
|
|
struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);
|
|
|
|
char *branch = strrchr(refname, '/') + 1;
|
2014-06-18 23:48:29 +04:00
|
|
|
|
2013-05-04 04:04:19 +04:00
|
|
|
/* A remote branch of the same name is deemed similar */
|
2019-05-14 15:05:05 +03:00
|
|
|
if (starts_with(refname, "refs/remotes/") &&
|
2013-05-04 04:04:19 +04:00
|
|
|
!strcmp(branch, cb->base_ref))
|
2019-05-14 15:05:05 +03:00
|
|
|
string_list_append_nodup(cb->similar_refs,
|
|
|
|
shorten_unambiguous_ref(refname, 1));
|
2013-05-04 04:04:19 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct string_list guess_refs(const char *ref)
|
|
|
|
{
|
|
|
|
struct similar_ref_cb ref_cb;
|
help_unknown_ref(): duplicate collected refnames
When "git merge" sees an unknown refname, we iterate through the refs to
try to suggest some possible alternates. We do so with for_each_ref(),
and in the callback we add some of the refnames we get to a
string_list that is declared with NODUP, directly adding a pointer into
the refname string our callback received.
But the for_each_ref() machinery does not promise that the refname
string will remain valid, and as a result we may print garbage memory.
The code in question dates back to its inception in e56181060e (help:
add help_unknown_ref(), 2013-05-04). But back then, the refname strings
generally did remain stable, at least immediately after the
for_each_ref() call. Later, in d1cf15516f (packed_ref_iterator_begin():
iterate using `mmapped_ref_iterator`, 2017-09-25), we started
consistently re-using a separate buffer for packed refs.
The fix is simple: duplicate the strings we intend to collect. We
already call string_list_clear(), so the memory is correctly freed.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-05-14 15:04:31 +03:00
|
|
|
struct string_list similar_refs = STRING_LIST_INIT_DUP;
|
2013-05-04 04:04:19 +04:00
|
|
|
|
|
|
|
ref_cb.base_ref = ref;
|
|
|
|
ref_cb.similar_refs = &similar_refs;
|
|
|
|
for_each_ref(append_similar_ref, &ref_cb);
|
|
|
|
return similar_refs;
|
|
|
|
}
|
|
|
|
|
2019-08-29 22:13:16 +03:00
|
|
|
NORETURN void help_unknown_ref(const char *ref, const char *cmd,
|
|
|
|
const char *error)
|
2013-05-04 04:04:19 +04:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|