git/parse-options.c

1129 lines
29 KiB
C
Raw Normal View History

#include "git-compat-util.h"
#include "parse-options.h"
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "color.h"
#include "utf8.h"
static int disallow_abbreviated_options;
enum opt_parsed {
OPT_LONG = 0,
OPT_SHORT = 1<<0,
OPT_UNSET = 1<<1,
};
static int optbug(const struct option *opt, const char *reason)
{
if (opt->long_name) {
if (opt->short_name)
return error("BUG: switch '%c' (--%s) %s",
opt->short_name, opt->long_name, reason);
return error("BUG: option '%s' %s", opt->long_name, reason);
}
return error("BUG: switch '%c' %s", opt->short_name, reason);
}
static const char *optname(const struct option *opt, enum opt_parsed flags)
{
static struct strbuf sb = STRBUF_INIT;
strbuf_reset(&sb);
if (flags & OPT_SHORT)
strbuf_addf(&sb, "switch `%c'", opt->short_name);
else if (flags & OPT_UNSET)
strbuf_addf(&sb, "option `no-%s'", opt->long_name);
else if (flags == OPT_LONG)
strbuf_addf(&sb, "option `%s'", opt->long_name);
else
BUG("optname() got unknown flags %d", flags);
return sb.buf;
}
static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
const struct option *opt,
enum opt_parsed flags, const char **arg)
{
if (p->opt) {
*arg = p->opt;
p->opt = NULL;
} else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
*arg = (const char *)opt->defval;
} else if (p->argc > 1) {
p->argc--;
*arg = *++p->argv;
} else
return error(_("%s requires a value"), optname(opt, flags));
return 0;
}
static void fix_filename(const char *prefix, const char **file)
{
if (!file || !*file || !prefix || is_absolute_path(*file)
|| !strcmp("-", *file))
return;
*file = prefix_filename(prefix, *file);
}
static enum parse_opt_result opt_command_mode_error(
const struct option *opt,
const struct option *all_opts,
enum opt_parsed flags)
{
const struct option *that;
struct strbuf that_name = STRBUF_INIT;
/*
* Find the other option that was used to set the variable
* already, and report that this is not compatible with it.
*/
for (that = all_opts; that->type != OPTION_END; that++) {
if (that == opt ||
!(that->flags & PARSE_OPT_CMDMODE) ||
that->value != opt->value ||
that->defval != *(int *)opt->value)
continue;
if (that->long_name)
strbuf_addf(&that_name, "--%s", that->long_name);
else
strbuf_addf(&that_name, "-%c", that->short_name);
error(_("%s is incompatible with %s"),
optname(opt, flags), that_name.buf);
strbuf_release(&that_name);
return PARSE_OPT_ERROR;
}
return error(_("%s : incompatible with something else"),
optname(opt, flags));
}
static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
const struct option *opt,
const struct option *all_opts,
enum opt_parsed flags)
{
const char *s, *arg;
const int unset = flags & OPT_UNSET;
int err;
if (unset && p->opt)
return error(_("%s takes no value"), optname(opt, flags));
if (unset && (opt->flags & PARSE_OPT_NONEG))
return error(_("%s isn't available"), optname(opt, flags));
if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
return error(_("%s takes no value"), optname(opt, flags));
/*
* Giving the same mode option twice, although unnecessary,
* is not a grave error, so let it pass.
*/
if ((opt->flags & PARSE_OPT_CMDMODE) &&
*(int *)opt->value && *(int *)opt->value != opt->defval)
return opt_command_mode_error(opt, all_opts, flags);
switch (opt->type) {
case OPTION_LOWLEVEL_CALLBACK:
return opt->ll_callback(p, opt, NULL, unset);
case OPTION_BIT:
if (unset)
*(int *)opt->value &= ~opt->defval;
else
*(int *)opt->value |= opt->defval;
return 0;
case OPTION_NEGBIT:
if (unset)
*(int *)opt->value |= opt->defval;
else
*(int *)opt->value &= ~opt->defval;
return 0;
case OPTION_BITOP:
if (unset)
BUG("BITOP can't have unset form");
*(int *)opt->value &= ~opt->extra;
*(int *)opt->value |= opt->defval;
return 0;
parse-options: deprecate OPT_BOOLEAN It is natural to expect that an option defined with OPT_BOOLEAN() could be used in this way: int option = -1; /* unspecified */ struct option options[] = { OPT_BOOLEAN(0, "option", &option, "set option"), OPT_END() }; parse_options(ac, av, prefix, options, usage, 0); if (option < 0) ... do the default thing ... else if (!option) ... --no-option was given ... else ... --option was given ... to easily tell three cases apart: - There is no mention of the `--option` on the command line; - The variable is positively set with `--option`; or - The variable is explicitly negated with `--no-option`. Unfortunately, this is not the case. OPT_BOOLEAN() increments the variable every time `--option` is given, and resets it to zero when `--no-option` is given. As a first step to remedy this, introduce a true boolean OPT_BOOL(), and rename OPT_BOOLEAN() to OPT_COUNTUP(). To help transitioning, OPT_BOOLEAN and OPTION_BOOLEAN are defined as deprecated synonyms to OPT_COUNTUP and OPTION_COUNTUP respectively. This is what db7244b (parse-options new features., 2007-11-07) from four years ago started by marking OPTION_BOOLEAN as "INCR would have been a better name". Some existing users do depend on the count-up semantics; for example, users of OPT__VERBOSE() could use it to raise the verbosity level with repeated use of `-v` on the command line, but they probably should be rewritten to use OPT__VERBOSITY() instead these days. I suspect that some users of OPT__FORCE() may also use it to implement different level of forcibleness but I didn't check. On top of this patch, here are the remaining clean-up tasks that other people can help: - Look at each hit in "git grep -e OPT_BOOLEAN"; trace all uses of the value that is set to the underlying variable, and if it can proven that the variable is only used as a boolean, replace it with OPT_BOOL(). If the caller does depend on the count-up semantics, replace it with OPT_COUNTUP() instead. - Same for OPTION_BOOLEAN; replace it with OPTION_SET_INT and arrange to set 1 to the variable for a true boolean, and otherwise replace it with OPTION_COUNTUP. - Look at each hit in "git grep -e OPT__VERBOSE -e OPT__QUIET" and see if they can be replaced with OPT__VERBOSITY(). I'll follow this message up with a separate patch as an example. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-09-28 07:56:49 +08:00
case OPTION_COUNTUP:
parse-options.c: make OPTION_COUNTUP respect "unspecified" values OPT_COUNTUP() merely increments the counter upon --option, and resets it to 0 upon --no-option, which means that there is no "unspecified" value with which a client can initialize the counter to determine whether or not --[no]-option was seen at all. Make OPT_COUNTUP() treat any negative number as an "unspecified" value to address this shortcoming. In particular, if a client initializes the counter to -1, then if it is still -1 after parse_options(), then neither --option nor --no-option was seen; if it is 0, then --no-option was seen last, and if it is 1 or greater, than --option was seen last. This change does not affect the behavior of existing clients because they all use the initial value of 0 (or more). Note that builtin/clean.c initializes the variable used with OPT__FORCE (which uses OPT_COUNTUP()) to a negative value, but it is set to either 0 or 1 by reading the configuration before the code calls parse_options(), i.e. as far as parse_options() is concerned, the initial value of the variable is not negative. To test this behavior, in test-parse-options.c, "verbose" is set to "unspecified" while quiet is set to 0 which will test the new behavior with all sets of values. Helped-by: Jeff King <peff@peff.net> Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Pranit Bauva <pranit.bauva@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-05-05 17:50:00 +08:00
if (*(int *)opt->value < 0)
*(int *)opt->value = 0;
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
return 0;
case OPTION_SET_INT:
*(int *)opt->value = unset ? 0 : opt->defval;
return 0;
case OPTION_STRING:
if (unset)
*(const char **)opt->value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
else
return get_arg(p, opt, flags, (const char **)opt->value);
return 0;
case OPTION_FILENAME:
err = 0;
if (unset)
*(const char **)opt->value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
*(const char **)opt->value = (const char *)opt->defval;
else
err = get_arg(p, opt, flags, (const char **)opt->value);
if (!err)
fix_filename(p->prefix, (const char **)opt->value);
return err;
case OPTION_CALLBACK:
{
const char *p_arg = NULL;
int p_unset;
if (unset)
p_unset = 1;
else if (opt->flags & PARSE_OPT_NOARG)
p_unset = 0;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
p_unset = 0;
else if (get_arg(p, opt, flags, &arg))
return -1;
else {
p_unset = 0;
p_arg = arg;
}
if (opt->callback)
return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0;
else
return (*opt->ll_callback)(p, opt, p_arg, p_unset);
}
case OPTION_INTEGER:
if (unset) {
*(int *)opt->value = 0;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
*(int *)opt->value = opt->defval;
return 0;
}
if (get_arg(p, opt, flags, &arg))
return -1;
if (!*arg)
return error(_("%s expects a numerical value"),
optname(opt, flags));
*(int *)opt->value = strtol(arg, (char **)&s, 10);
if (*s)
return error(_("%s expects a numerical value"),
optname(opt, flags));
return 0;
case OPTION_MAGNITUDE:
if (unset) {
*(unsigned long *)opt->value = 0;
return 0;
}
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
*(unsigned long *)opt->value = opt->defval;
return 0;
}
if (get_arg(p, opt, flags, &arg))
return -1;
if (!git_parse_ulong(arg, opt->value))
return error(_("%s expects a non-negative integer value"
" with an optional k/m/g suffix"),
optname(opt, flags));
return 0;
default:
BUG("opt->type %d should not happen", opt->type);
}
}
static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
const struct option *options)
{
const struct option *all_opts = options;
const struct option *numopt = NULL;
for (; options->type != OPTION_END; options++) {
if (options->short_name == *p->opt) {
p->opt = p->opt[1] ? p->opt + 1 : NULL;
return get_value(p, options, all_opts, OPT_SHORT);
}
/*
* Handle the numerical option later, explicit one-digit
* options take precedence over it.
*/
if (options->type == OPTION_NUMBER)
numopt = options;
}
if (numopt && isdigit(*p->opt)) {
size_t len = 1;
char *arg;
int rc;
while (isdigit(p->opt[len]))
len++;
arg = xmemdupz(p->opt, len);
p->opt = p->opt[len] ? p->opt + len : NULL;
if (numopt->callback)
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
else
rc = (*numopt->ll_callback)(p, numopt, arg, 0);
free(arg);
return rc;
}
return PARSE_OPT_UNKNOWN;
}
static int has_string(const char *it, const char **array)
{
while (*array)
if (!strcmp(it, *(array++)))
return 1;
return 0;
}
static int is_alias(struct parse_opt_ctx_t *ctx,
const struct option *one_opt,
const struct option *another_opt)
{
const char **group;
if (!ctx->alias_groups)
return 0;
if (!one_opt->long_name || !another_opt->long_name)
return 0;
for (group = ctx->alias_groups; *group; group += 3) {
/* it and other are from the same family? */
if (has_string(one_opt->long_name, group) &&
has_string(another_opt->long_name, group))
return 1;
}
return 0;
}
static enum parse_opt_result parse_long_opt(
struct parse_opt_ctx_t *p, const char *arg,
const struct option *options)
{
const struct option *all_opts = options;
const char *arg_end = strchrnul(arg, '=');
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG;
for (; options->type != OPTION_END; options++) {
const char *rest, *long_name = options->long_name;
enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
if (!long_name)
continue;
again:
if (!skip_prefix(arg, long_name, &rest))
rest = NULL;
if (!rest) {
/* abbreviated? */
if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option &&
!is_alias(p, abbrev_option, options)) {
/*
* If this is abbreviated, it is
* ambiguous. So when there is no
* exact match later, we need to
* error out.
*/
ambiguous_option = abbrev_option;
ambiguous_flags = abbrev_flags;
}
if (!(flags & OPT_UNSET) && *arg_end)
p->opt = arg_end + 1;
abbrev_option = options;
abbrev_flags = flags ^ opt_flags;
continue;
}
/* negation allowed? */
if (options->flags & PARSE_OPT_NONEG)
continue;
/* negated and abbreviated very much? */
if (starts_with("no-", arg)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
if (!starts_with(arg, "no-")) {
if (skip_prefix(long_name, "no-", &long_name)) {
opt_flags |= OPT_UNSET;
goto again;
}
continue;
}
flags |= OPT_UNSET;
if (!skip_prefix(arg + 3, long_name, &rest)) {
/* abbreviated and negated? */
if (starts_with(long_name, arg + 3))
goto is_abbreviated;
else
continue;
}
}
if (*rest) {
if (*rest != '=')
continue;
p->opt = rest + 1;
}
return get_value(p, options, all_opts, flags ^ opt_flags);
}
if (disallow_abbreviated_options && (ambiguous_option || abbrev_option))
die("disallowed abbreviated or ambiguous option '%.*s'",
(int)(arg_end - arg), arg);
if (ambiguous_option) {
error(_("ambiguous option: %s "
"(could be --%s%s or --%s%s)"),
arg,
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
ambiguous_option->long_name,
(abbrev_flags & OPT_UNSET) ? "no-" : "",
abbrev_option->long_name);
return PARSE_OPT_HELP;
}
if (abbrev_option)
return get_value(p, abbrev_option, all_opts, abbrev_flags);
return PARSE_OPT_UNKNOWN;
}
static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
const char *arg,
const struct option *options)
{
const struct option *all_opts = options;
for (; options->type != OPTION_END; options++) {
if (!(options->flags & PARSE_OPT_NODASH))
continue;
if (options->short_name == arg[0] && arg[1] == '\0')
return get_value(p, options, all_opts, OPT_SHORT);
}
return PARSE_OPT_ERROR;
}
static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
return;
if (starts_with(arg, "no-")) {
error(_("did you mean `--%s` (with two dashes)?"), arg);
exit(129);
}
for (; options->type != OPTION_END; options++) {
if (!options->long_name)
continue;
if (starts_with(options->long_name, arg)) {
error(_("did you mean `--%s` (with two dashes)?"), arg);
exit(129);
}
}
}
static void parse_options_check(const struct option *opts)
{
int err = 0;
char short_opts[128];
memset(short_opts, '\0', sizeof(short_opts));
for (; opts->type != OPTION_END; opts++) {
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
(opts->flags & PARSE_OPT_OPTARG))
err |= optbug(opts, "uses incompatible flags "
"LASTARG_DEFAULT and OPTARG");
if (opts->short_name) {
if (0x7F <= opts->short_name)
err |= optbug(opts, "invalid short name");
else if (short_opts[opts->short_name]++)
err |= optbug(opts, "short name already used");
}
if (opts->flags & PARSE_OPT_NODASH &&
((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG) ||
!(opts->flags & PARSE_OPT_NONEG) ||
opts->long_name))
err |= optbug(opts, "uses feature "
"not supported for dashless options");
switch (opts->type) {
parse-options: deprecate OPT_BOOLEAN It is natural to expect that an option defined with OPT_BOOLEAN() could be used in this way: int option = -1; /* unspecified */ struct option options[] = { OPT_BOOLEAN(0, "option", &option, "set option"), OPT_END() }; parse_options(ac, av, prefix, options, usage, 0); if (option < 0) ... do the default thing ... else if (!option) ... --no-option was given ... else ... --option was given ... to easily tell three cases apart: - There is no mention of the `--option` on the command line; - The variable is positively set with `--option`; or - The variable is explicitly negated with `--no-option`. Unfortunately, this is not the case. OPT_BOOLEAN() increments the variable every time `--option` is given, and resets it to zero when `--no-option` is given. As a first step to remedy this, introduce a true boolean OPT_BOOL(), and rename OPT_BOOLEAN() to OPT_COUNTUP(). To help transitioning, OPT_BOOLEAN and OPTION_BOOLEAN are defined as deprecated synonyms to OPT_COUNTUP and OPTION_COUNTUP respectively. This is what db7244b (parse-options new features., 2007-11-07) from four years ago started by marking OPTION_BOOLEAN as "INCR would have been a better name". Some existing users do depend on the count-up semantics; for example, users of OPT__VERBOSE() could use it to raise the verbosity level with repeated use of `-v` on the command line, but they probably should be rewritten to use OPT__VERBOSITY() instead these days. I suspect that some users of OPT__FORCE() may also use it to implement different level of forcibleness but I didn't check. On top of this patch, here are the remaining clean-up tasks that other people can help: - Look at each hit in "git grep -e OPT_BOOLEAN"; trace all uses of the value that is set to the underlying variable, and if it can proven that the variable is only used as a boolean, replace it with OPT_BOOL(). If the caller does depend on the count-up semantics, replace it with OPT_COUNTUP() instead. - Same for OPTION_BOOLEAN; replace it with OPTION_SET_INT and arrange to set 1 to the variable for a true boolean, and otherwise replace it with OPTION_COUNTUP. - Look at each hit in "git grep -e OPT__VERBOSE -e OPT__QUIET" and see if they can be replaced with OPT__VERBOSITY(). I'll follow this message up with a separate patch as an example. Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-09-28 07:56:49 +08:00
case OPTION_COUNTUP:
case OPTION_BIT:
case OPTION_NEGBIT:
case OPTION_SET_INT:
case OPTION_NUMBER:
if ((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG))
err |= optbug(opts, "should not accept an argument");
break;
case OPTION_CALLBACK:
if (!opts->callback && !opts->ll_callback)
BUG("OPTION_CALLBACK needs one callback");
if (opts->callback && opts->ll_callback)
BUG("OPTION_CALLBACK can't have two callbacks");
break;
case OPTION_LOWLEVEL_CALLBACK:
if (!opts->ll_callback)
BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
if (opts->callback)
BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
break;
case OPTION_ALIAS:
BUG("OPT_ALIAS() should not remain at this point. "
"Are you using parse_options_step() directly?\n"
"That case is not supported yet.");
default:
; /* ok. (usually accepts an argument) */
}
if (opts->argh &&
strcspn(opts->argh, " _") != strlen(opts->argh))
err |= optbug(opts, "multi-word argh should use dash to separate words");
}
if (err)
exit(128);
}
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
const struct option *options,
enum parse_opt_flags flags)
{
ctx->argc = argc;
ctx->argv = argv;
if (!(flags & PARSE_OPT_ONE_SHOT)) {
ctx->argc--;
ctx->argv++;
}
ctx->total = ctx->argc;
ctx->out = argv;
ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
!(flags & PARSE_OPT_ONE_SHOT))
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
if ((flags & PARSE_OPT_ONE_SHOT) &&
(flags & PARSE_OPT_KEEP_ARGV0))
BUG("Can't keep argv0 if you don't have it");
parse_options_check(options);
}
void parse_options_start(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
const struct option *options,
enum parse_opt_flags flags)
{
memset(ctx, 0, sizeof(*ctx));
parse_options_start_1(ctx, argc, argv, prefix, options, flags);
}
static void show_negated_gitcomp(const struct option *opts, int show_all,
int nr_noopts)
completion: collapse extra --no-.. options The commands that make use of --git-completion-helper feature could now produce a lot of --no-xxx options that a command can take. This in many case could nearly double the amount of completable options, using more screen estate and also harder to search for the wanted option. This patch attempts to mitigate that by collapsing extra --no- options, the ones that are added by --git-completion-helper and not in original struct option arrays. The "--no-..." option will be displayed in this case to hint about more options, e.g. > ~/w/git $ git clone -- --bare --origin= --branch= --progress --checkout --quiet --config= --recurse-submodules --depth= --reference= --dissociate --reference-if-able= --filter= --separate-git-dir= --hardlinks --shallow-exclude= --ipv4 --shallow-since= --ipv6 --shallow-submodules --jobs= --shared --local --single-branch --mirror --tags --no-... --template= --no-checkout --upload-pack= --no-hardlinks --verbose --no-tags and when you complete it with --no-<tab>, all negative options will be presented: > ~/w/git $ git clone --no- --no-bare --no-quiet --no-branch --no-recurse-submodules --no-checkout --no-reference --no-config --no-reference-if-able --no-depth --no-separate-git-dir --no-dissociate --no-shallow-exclude --no-filter --no-shallow-since --no-hardlinks --no-shallow-submodules --no-ipv4 --no-shared --no-ipv6 --no-single-branch --no-jobs --no-tags --no-local --no-template --no-mirror --no-upload-pack --no-origin --no-verbose --no-progress Corner case: to make sure that people will never accidentally complete the fake option "--no-..." there must be one real --no- in the first complete listing even if it's not from the original struct option. PS. This could could be made simpler with ";&" to fall through from "--no-*" block and share the code but ";&" is not available on bash-3 (i.e. Mac) Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-06-06 17:41:39 +08:00
{
int printed_dashdash = 0;
for (; opts->type != OPTION_END; opts++) {
int has_unset_form = 0;
const char *name;
if (!opts->long_name)
continue;
if (!show_all &&
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)))
completion: collapse extra --no-.. options The commands that make use of --git-completion-helper feature could now produce a lot of --no-xxx options that a command can take. This in many case could nearly double the amount of completable options, using more screen estate and also harder to search for the wanted option. This patch attempts to mitigate that by collapsing extra --no- options, the ones that are added by --git-completion-helper and not in original struct option arrays. The "--no-..." option will be displayed in this case to hint about more options, e.g. > ~/w/git $ git clone -- --bare --origin= --branch= --progress --checkout --quiet --config= --recurse-submodules --depth= --reference= --dissociate --reference-if-able= --filter= --separate-git-dir= --hardlinks --shallow-exclude= --ipv4 --shallow-since= --ipv6 --shallow-submodules --jobs= --shared --local --single-branch --mirror --tags --no-... --template= --no-checkout --upload-pack= --no-hardlinks --verbose --no-tags and when you complete it with --no-<tab>, all negative options will be presented: > ~/w/git $ git clone --no- --no-bare --no-quiet --no-branch --no-recurse-submodules --no-checkout --no-reference --no-config --no-reference-if-able --no-depth --no-separate-git-dir --no-dissociate --no-shallow-exclude --no-filter --no-shallow-since --no-hardlinks --no-shallow-submodules --no-ipv4 --no-shared --no-ipv6 --no-single-branch --no-jobs --no-tags --no-local --no-template --no-mirror --no-upload-pack --no-origin --no-verbose --no-progress Corner case: to make sure that people will never accidentally complete the fake option "--no-..." there must be one real --no- in the first complete listing even if it's not from the original struct option. PS. This could could be made simpler with ";&" to fall through from "--no-*" block and share the code but ";&" is not available on bash-3 (i.e. Mac) Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-06-06 17:41:39 +08:00
continue;
if (opts->flags & PARSE_OPT_NONEG)
continue;
switch (opts->type) {
case OPTION_STRING:
case OPTION_FILENAME:
case OPTION_INTEGER:
case OPTION_MAGNITUDE:
case OPTION_CALLBACK:
case OPTION_BIT:
case OPTION_NEGBIT:
case OPTION_COUNTUP:
case OPTION_SET_INT:
has_unset_form = 1;
break;
default:
break;
}
if (!has_unset_form)
continue;
if (skip_prefix(opts->long_name, "no-", &name)) {
if (nr_noopts < 0)
printf(" --%s", name);
} else if (nr_noopts >= 0) {
if (nr_noopts && !printed_dashdash) {
printf(" --");
printed_dashdash = 1;
}
printf(" --no-%s", opts->long_name);
nr_noopts++;
}
}
}
static int show_gitcomp(const struct option *opts, int show_all)
{
completion: collapse extra --no-.. options The commands that make use of --git-completion-helper feature could now produce a lot of --no-xxx options that a command can take. This in many case could nearly double the amount of completable options, using more screen estate and also harder to search for the wanted option. This patch attempts to mitigate that by collapsing extra --no- options, the ones that are added by --git-completion-helper and not in original struct option arrays. The "--no-..." option will be displayed in this case to hint about more options, e.g. > ~/w/git $ git clone -- --bare --origin= --branch= --progress --checkout --quiet --config= --recurse-submodules --depth= --reference= --dissociate --reference-if-able= --filter= --separate-git-dir= --hardlinks --shallow-exclude= --ipv4 --shallow-since= --ipv6 --shallow-submodules --jobs= --shared --local --single-branch --mirror --tags --no-... --template= --no-checkout --upload-pack= --no-hardlinks --verbose --no-tags and when you complete it with --no-<tab>, all negative options will be presented: > ~/w/git $ git clone --no- --no-bare --no-quiet --no-branch --no-recurse-submodules --no-checkout --no-reference --no-config --no-reference-if-able --no-depth --no-separate-git-dir --no-dissociate --no-shallow-exclude --no-filter --no-shallow-since --no-hardlinks --no-shallow-submodules --no-ipv4 --no-shared --no-ipv6 --no-single-branch --no-jobs --no-tags --no-local --no-template --no-mirror --no-upload-pack --no-origin --no-verbose --no-progress Corner case: to make sure that people will never accidentally complete the fake option "--no-..." there must be one real --no- in the first complete listing even if it's not from the original struct option. PS. This could could be made simpler with ";&" to fall through from "--no-*" block and share the code but ";&" is not available on bash-3 (i.e. Mac) Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-06-06 17:41:39 +08:00
const struct option *original_opts = opts;
int nr_noopts = 0;
for (; opts->type != OPTION_END; opts++) {
const char *suffix = "";
if (!opts->long_name)
continue;
if (!show_all &&
parse-options: don't complete option aliases by default Since 'OPT_ALIAS' was created in 5c387428f1 (parse-options: don't emit "ambiguous option" for aliases, 2019-04-29), 'git clone --git-completion-helper', which is used by the Bash completion script to list options accepted by clone (via '__gitcomp_builtin'), lists both '--recurse-submodules' and its alias '--recursive', which was not the case before since '--recursive' had the PARSE_OPT_HIDDEN flag set, and options with this flag are skipped by 'parse-options.c::show_gitcomp', which implements 'git <cmd> --git-completion-helper'. This means that typing 'git clone --recurs<TAB>' will yield both '--recurse-submodules' and '--recursive', which is not ideal since both do the same thing, and so the completion should directly complete the canonical option. At the point where 'show_gitcomp' is called in 'parse_options_step', 'preprocess_options' was already called in 'parse_options', so any aliases are now copies of the original options with a modified help text indicating they are aliases. Helpfully, since 64cc539fd2 (parse-options: don't leak alias help messages, 2021-03-21) these copies have the PARSE_OPT_FROM_ALIAS flag set, so check that flag early in 'show_gitcomp' and do not print them, unless the user explicitely requested that *all* completion be shown (by setting 'GIT_COMPLETION_SHOW_ALL'). After all, if we want to encourage the use of '--recurse-submodules' over '--recursive', we'd better just suggest the former. The only other options alias is 'log' and friends' '--mailmap', which is an alias for '--use-mailmap', but the Bash completion helpers for these commands do not use '__gitcomp_builtin', and thus are unnaffected by this change. Test the new behaviour in t9902-completion.sh. As a side effect, this also tests the correct behaviour of GIT_COMPLETION_SHOW_ALL, which was not tested before. Note that since '__gitcomp_builtin' caches the options it shows, we need to re-source the completion script to clear that cache for the second test. Signed-off-by: Philippe Blain <levraiphilippeblain@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-07-16 09:55:43 +08:00
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE | PARSE_OPT_FROM_ALIAS)))
continue;
switch (opts->type) {
case OPTION_GROUP:
continue;
case OPTION_STRING:
case OPTION_FILENAME:
case OPTION_INTEGER:
case OPTION_MAGNITUDE:
case OPTION_CALLBACK:
if (opts->flags & PARSE_OPT_NOARG)
break;
if (opts->flags & PARSE_OPT_OPTARG)
break;
if (opts->flags & PARSE_OPT_LASTARG_DEFAULT)
break;
suffix = "=";
break;
default:
break;
}
if (opts->flags & PARSE_OPT_COMP_ARG)
suffix = "=";
completion: collapse extra --no-.. options The commands that make use of --git-completion-helper feature could now produce a lot of --no-xxx options that a command can take. This in many case could nearly double the amount of completable options, using more screen estate and also harder to search for the wanted option. This patch attempts to mitigate that by collapsing extra --no- options, the ones that are added by --git-completion-helper and not in original struct option arrays. The "--no-..." option will be displayed in this case to hint about more options, e.g. > ~/w/git $ git clone -- --bare --origin= --branch= --progress --checkout --quiet --config= --recurse-submodules --depth= --reference= --dissociate --reference-if-able= --filter= --separate-git-dir= --hardlinks --shallow-exclude= --ipv4 --shallow-since= --ipv6 --shallow-submodules --jobs= --shared --local --single-branch --mirror --tags --no-... --template= --no-checkout --upload-pack= --no-hardlinks --verbose --no-tags and when you complete it with --no-<tab>, all negative options will be presented: > ~/w/git $ git clone --no- --no-bare --no-quiet --no-branch --no-recurse-submodules --no-checkout --no-reference --no-config --no-reference-if-able --no-depth --no-separate-git-dir --no-dissociate --no-shallow-exclude --no-filter --no-shallow-since --no-hardlinks --no-shallow-submodules --no-ipv4 --no-shared --no-ipv6 --no-single-branch --no-jobs --no-tags --no-local --no-template --no-mirror --no-upload-pack --no-origin --no-verbose --no-progress Corner case: to make sure that people will never accidentally complete the fake option "--no-..." there must be one real --no- in the first complete listing even if it's not from the original struct option. PS. This could could be made simpler with ";&" to fall through from "--no-*" block and share the code but ";&" is not available on bash-3 (i.e. Mac) Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-06-06 17:41:39 +08:00
if (starts_with(opts->long_name, "no-"))
nr_noopts++;
printf(" --%s%s", opts->long_name, suffix);
}
show_negated_gitcomp(original_opts, show_all, -1);
show_negated_gitcomp(original_opts, show_all, nr_noopts);
fputc('\n', stdout);
return PARSE_OPT_COMPLETE;
}
/*
* Scan and may produce a new option[] array, which should be used
* instead of the original 'options'.
*
* Right now this is only used to preprocess and substitute
* OPTION_ALIAS.
parse-options: don't leak alias help messages preprocess_options() allocates new strings for help messages for OPTION_ALIAS. Therefore we also need to clean those help messages up when freeing the returned options. First introduced in: 7c280589cf (parse-options: teach "git cmd -h" to show alias as alias, 2020-03-16) The preprocessed options themselves no longer contain any indication that a given option is/was an alias - therefore we add a new flag to indicate former aliases. (An alternative approach would be to look back at the original options to determine which options are aliases - but that seems like a fragile approach. Or we could even look at the alias_groups list - which might be less fragile, but would be slower as it requires nested looping.) As far as I can tell, parse_options() is only ever used once per command, and the help messages are small - hence this leak has very little impact. This leak was found while running t0001. LSAN output can be found below: Direct leak of 65 byte(s) in 1 object(s) allocated from: #0 0x49a859 in realloc /home/abuild/rpmbuild/BUILD/llvm-11.0.0.src/build/../projects/compiler-rt/lib/asan/asan_malloc_linux.cpp:164:3 #1 0x9aae36 in xrealloc /home/ahunt/oss-fuzz/git/wrapper.c:126:8 #2 0x939d8d in strbuf_grow /home/ahunt/oss-fuzz/git/strbuf.c:98:2 #3 0x93b936 in strbuf_vaddf /home/ahunt/oss-fuzz/git/strbuf.c:392:3 #4 0x93b7ff in strbuf_addf /home/ahunt/oss-fuzz/git/strbuf.c:333:2 #5 0x86747e in preprocess_options /home/ahunt/oss-fuzz/git/parse-options.c:666:3 #6 0x866ed2 in parse_options /home/ahunt/oss-fuzz/git/parse-options.c:847:17 #7 0x51c4a7 in cmd_clone /home/ahunt/oss-fuzz/git/builtin/clone.c:989:9 #8 0x4cd60d in run_builtin /home/ahunt/oss-fuzz/git/git.c:453:11 #9 0x4cb2da in handle_builtin /home/ahunt/oss-fuzz/git/git.c:704:3 #10 0x4ccc37 in run_argv /home/ahunt/oss-fuzz/git/git.c:771:4 #11 0x4cac29 in cmd_main /home/ahunt/oss-fuzz/git/git.c:902:19 #12 0x69c9fe in main /home/ahunt/oss-fuzz/git/common-main.c:52:11 #13 0x7fdac42d4349 in __libc_start_main (/lib64/libc.so.6+0x24349) Signed-off-by: Andrzej Hunt <ajrhunt@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-22 00:58:36 +08:00
*
* The returned options should be freed using free_preprocessed_options.
*/
static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
const struct option *options)
{
struct option *newopt;
int i, nr, alias;
int nr_aliases = 0;
for (nr = 0; options[nr].type != OPTION_END; nr++) {
if (options[nr].type == OPTION_ALIAS)
nr_aliases++;
}
if (!nr_aliases)
return NULL;
ALLOC_ARRAY(newopt, nr + 1);
COPY_ARRAY(newopt, options, nr + 1);
/* each alias has two string pointers and NULL */
CALLOC_ARRAY(ctx->alias_groups, 3 * (nr_aliases + 1));
for (alias = 0, i = 0; i < nr; i++) {
int short_name;
const char *long_name;
const char *source;
struct strbuf help = STRBUF_INIT;
int j;
if (newopt[i].type != OPTION_ALIAS)
continue;
short_name = newopt[i].short_name;
long_name = newopt[i].long_name;
source = newopt[i].value;
if (!long_name)
BUG("An alias must have long option name");
strbuf_addf(&help, _("alias of --%s"), source);
for (j = 0; j < nr; j++) {
const char *name = options[j].long_name;
if (!name || strcmp(name, source))
continue;
if (options[j].type == OPTION_ALIAS)
BUG("No please. Nested aliases are not supported.");
memcpy(newopt + i, options + j, sizeof(*newopt));
newopt[i].short_name = short_name;
newopt[i].long_name = long_name;
newopt[i].help = strbuf_detach(&help, NULL);
parse-options: don't leak alias help messages preprocess_options() allocates new strings for help messages for OPTION_ALIAS. Therefore we also need to clean those help messages up when freeing the returned options. First introduced in: 7c280589cf (parse-options: teach "git cmd -h" to show alias as alias, 2020-03-16) The preprocessed options themselves no longer contain any indication that a given option is/was an alias - therefore we add a new flag to indicate former aliases. (An alternative approach would be to look back at the original options to determine which options are aliases - but that seems like a fragile approach. Or we could even look at the alias_groups list - which might be less fragile, but would be slower as it requires nested looping.) As far as I can tell, parse_options() is only ever used once per command, and the help messages are small - hence this leak has very little impact. This leak was found while running t0001. LSAN output can be found below: Direct leak of 65 byte(s) in 1 object(s) allocated from: #0 0x49a859 in realloc /home/abuild/rpmbuild/BUILD/llvm-11.0.0.src/build/../projects/compiler-rt/lib/asan/asan_malloc_linux.cpp:164:3 #1 0x9aae36 in xrealloc /home/ahunt/oss-fuzz/git/wrapper.c:126:8 #2 0x939d8d in strbuf_grow /home/ahunt/oss-fuzz/git/strbuf.c:98:2 #3 0x93b936 in strbuf_vaddf /home/ahunt/oss-fuzz/git/strbuf.c:392:3 #4 0x93b7ff in strbuf_addf /home/ahunt/oss-fuzz/git/strbuf.c:333:2 #5 0x86747e in preprocess_options /home/ahunt/oss-fuzz/git/parse-options.c:666:3 #6 0x866ed2 in parse_options /home/ahunt/oss-fuzz/git/parse-options.c:847:17 #7 0x51c4a7 in cmd_clone /home/ahunt/oss-fuzz/git/builtin/clone.c:989:9 #8 0x4cd60d in run_builtin /home/ahunt/oss-fuzz/git/git.c:453:11 #9 0x4cb2da in handle_builtin /home/ahunt/oss-fuzz/git/git.c:704:3 #10 0x4ccc37 in run_argv /home/ahunt/oss-fuzz/git/git.c:771:4 #11 0x4cac29 in cmd_main /home/ahunt/oss-fuzz/git/git.c:902:19 #12 0x69c9fe in main /home/ahunt/oss-fuzz/git/common-main.c:52:11 #13 0x7fdac42d4349 in __libc_start_main (/lib64/libc.so.6+0x24349) Signed-off-by: Andrzej Hunt <ajrhunt@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-22 00:58:36 +08:00
newopt[i].flags |= PARSE_OPT_FROM_ALIAS;
break;
}
if (j == nr)
BUG("could not find source option '%s' of alias '%s'",
source, newopt[i].long_name);
ctx->alias_groups[alias * 3 + 0] = newopt[i].long_name;
ctx->alias_groups[alias * 3 + 1] = options[j].long_name;
ctx->alias_groups[alias * 3 + 2] = NULL;
alias++;
}
return newopt;
}
parse-options: don't leak alias help messages preprocess_options() allocates new strings for help messages for OPTION_ALIAS. Therefore we also need to clean those help messages up when freeing the returned options. First introduced in: 7c280589cf (parse-options: teach "git cmd -h" to show alias as alias, 2020-03-16) The preprocessed options themselves no longer contain any indication that a given option is/was an alias - therefore we add a new flag to indicate former aliases. (An alternative approach would be to look back at the original options to determine which options are aliases - but that seems like a fragile approach. Or we could even look at the alias_groups list - which might be less fragile, but would be slower as it requires nested looping.) As far as I can tell, parse_options() is only ever used once per command, and the help messages are small - hence this leak has very little impact. This leak was found while running t0001. LSAN output can be found below: Direct leak of 65 byte(s) in 1 object(s) allocated from: #0 0x49a859 in realloc /home/abuild/rpmbuild/BUILD/llvm-11.0.0.src/build/../projects/compiler-rt/lib/asan/asan_malloc_linux.cpp:164:3 #1 0x9aae36 in xrealloc /home/ahunt/oss-fuzz/git/wrapper.c:126:8 #2 0x939d8d in strbuf_grow /home/ahunt/oss-fuzz/git/strbuf.c:98:2 #3 0x93b936 in strbuf_vaddf /home/ahunt/oss-fuzz/git/strbuf.c:392:3 #4 0x93b7ff in strbuf_addf /home/ahunt/oss-fuzz/git/strbuf.c:333:2 #5 0x86747e in preprocess_options /home/ahunt/oss-fuzz/git/parse-options.c:666:3 #6 0x866ed2 in parse_options /home/ahunt/oss-fuzz/git/parse-options.c:847:17 #7 0x51c4a7 in cmd_clone /home/ahunt/oss-fuzz/git/builtin/clone.c:989:9 #8 0x4cd60d in run_builtin /home/ahunt/oss-fuzz/git/git.c:453:11 #9 0x4cb2da in handle_builtin /home/ahunt/oss-fuzz/git/git.c:704:3 #10 0x4ccc37 in run_argv /home/ahunt/oss-fuzz/git/git.c:771:4 #11 0x4cac29 in cmd_main /home/ahunt/oss-fuzz/git/git.c:902:19 #12 0x69c9fe in main /home/ahunt/oss-fuzz/git/common-main.c:52:11 #13 0x7fdac42d4349 in __libc_start_main (/lib64/libc.so.6+0x24349) Signed-off-by: Andrzej Hunt <ajrhunt@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-22 00:58:36 +08:00
static void free_preprocessed_options(struct option *options)
{
int i;
if (!options)
return;
for (i = 0; options[i].type != OPTION_END; i++) {
if (options[i].flags & PARSE_OPT_FROM_ALIAS)
free((void *)options[i].help);
}
free(options);
}
static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *,
const char * const *,
const struct option *,
int, int);
enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
const struct option *options,
const char * const usagestr[])
{
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
/* we must reset ->opt, unknown short option leave it dangling */
ctx->opt = NULL;
for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0];
if (ctx->flags & PARSE_OPT_ONE_SHOT &&
ctx->argc != ctx->total)
break;
if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
return PARSE_OPT_NON_OPTION;
ctx->out[ctx->cpidx++] = ctx->argv[0];
continue;
}
/* lone -h asks for help */
if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h"))
goto show_usage;
/*
* lone --git-completion-helper and --git-completion-helper-all
* are asked by git-completion.bash
*/
if (ctx->total == 1 && !strcmp(arg, "--git-completion-helper"))
return show_gitcomp(options, 0);
if (ctx->total == 1 && !strcmp(arg, "--git-completion-helper-all"))
return show_gitcomp(options, 1);
if (arg[1] != '-') {
ctx->opt = arg + 1;
switch (parse_short_opt(ctx, options)) {
case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
case PARSE_OPT_UNKNOWN:
if (ctx->opt)
check_typos(arg + 1, options);
if (internal_help && *ctx->opt == 'h')
goto show_usage;
goto unknown;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_HELP:
case PARSE_OPT_COMPLETE:
BUG("parse_short_opt() cannot return these");
case PARSE_OPT_DONE:
break;
}
if (ctx->opt)
check_typos(arg + 1, options);
while (ctx->opt) {
switch (parse_short_opt(ctx, options)) {
case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
case PARSE_OPT_UNKNOWN:
if (internal_help && *ctx->opt == 'h')
goto show_usage;
/* fake a short option thing to hide the fact that we may have
* started to parse aggregated stuff
*
* This is leaky, too bad.
*/
ctx->argv[0] = xstrdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-';
goto unknown;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_COMPLETE:
case PARSE_OPT_HELP:
BUG("parse_short_opt() cannot return these");
case PARSE_OPT_DONE:
break;
}
}
continue;
}
if (!arg[2] /* "--" */ ||
!strcmp(arg + 2, "end-of-options")) {
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
ctx->argc--;
ctx->argv++;
}
break;
}
if (internal_help && !strcmp(arg + 2, "help-all"))
return usage_with_options_internal(ctx, usagestr, options, 1, 0);
if (internal_help && !strcmp(arg + 2, "help"))
goto show_usage;
switch (parse_long_opt(ctx, arg + 2, options)) {
case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
case PARSE_OPT_UNKNOWN:
goto unknown;
case PARSE_OPT_HELP:
goto show_usage;
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_COMPLETE:
BUG("parse_long_opt() cannot return these");
case PARSE_OPT_DONE:
break;
}
continue;
unknown:
if (ctx->flags & PARSE_OPT_ONE_SHOT)
break;
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
ctx->opt = NULL;
}
return PARSE_OPT_DONE;
show_usage:
return usage_with_options_internal(ctx, usagestr, options, 0, 0);
}
int parse_options_end(struct parse_opt_ctx_t *ctx)
{
if (ctx->flags & PARSE_OPT_ONE_SHOT)
return ctx->total - ctx->argc;
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
ctx->out[ctx->cpidx + ctx->argc] = NULL;
return ctx->cpidx + ctx->argc;
}
int parse_options(int argc, const char **argv,
const char *prefix,
const struct option *options,
const char * const usagestr[],
enum parse_opt_flags flags)
{
struct parse_opt_ctx_t ctx;
struct option *real_options;
disallow_abbreviated_options =
git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0);
memset(&ctx, 0, sizeof(ctx));
real_options = preprocess_options(&ctx, options);
if (real_options)
options = real_options;
parse_options_start_1(&ctx, argc, argv, prefix, options, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP:
case PARSE_OPT_ERROR:
exit(129);
case PARSE_OPT_COMPLETE:
exit(0);
case PARSE_OPT_NON_OPTION:
case PARSE_OPT_DONE:
break;
case PARSE_OPT_UNKNOWN:
if (ctx.argv[0][1] == '-') {
error(_("unknown option `%s'"), ctx.argv[0] + 2);
} else if (isascii(*ctx.opt)) {
error(_("unknown switch `%c'"), *ctx.opt);
} else {
error(_("unknown non-ascii option in string: `%s'"),
ctx.argv[0]);
}
usage_with_options(usagestr, options);
}
MacOS: precompose_argv_prefix() The following sequence leads to a "BUG" assertion running under MacOS: DIR=git-test-restore-p Adiarnfd=$(printf 'A\314\210') DIRNAME=xx${Adiarnfd}yy mkdir $DIR && cd $DIR && git init && mkdir $DIRNAME && cd $DIRNAME && echo "Initial" >file && git add file && echo "One more line" >>file && echo y | git restore -p . Initialized empty Git repository in /tmp/git-test-restore-p/.git/ BUG: pathspec.c:495: error initializing pathspec_item Cannot close git diff-index --cached --numstat [snip] The command `git restore` is run from a directory inside a Git repo. Git needs to split the $CWD into 2 parts: The path to the repo and "the rest", if any. "The rest" becomes a "prefix" later used inside the pathspec code. As an example, "/path/to/repo/dir-inside-repå" would determine "/path/to/repo" as the root of the repo, the place where the configuration file .git/config is found. The rest becomes the prefix ("dir-inside-repå"), from where the pathspec machinery expands the ".", more about this later. If there is a decomposed form, (making the decomposing visible like this), "dir-inside-rep°a" doesn't match "dir-inside-repå". Git commands need to: (a) read the configuration variable "core.precomposeunicode" (b) precocompose argv[] (c) precompose the prefix, if there was any The first commit, 76759c7dff53 "git on Mac OS and precomposed unicode" addressed (a) and (b). The call to precompose_argv() was added into parse-options.c, because that seemed to be a good place when the patch was written. Commands that don't use parse-options need to do (a) and (b) themselfs. The commands `diff-files`, `diff-index`, `diff-tree` and `diff` learned (a) and (b) in commit 90a78b83e0b8 "diff: run arguments through precompose_argv" Branch names (or refs in general) using decomposed code points resulting in decomposed file names had been fixed in commit 8e712ef6fc97 "Honor core.precomposeUnicode in more places" The bug report from above shows 2 things: - more commands need to handle precomposed unicode - (c) should be implemented for all commands using pathspecs Solution: precompose_argv() now handles the prefix (if needed), and is renamed into precompose_argv_prefix(). Inside this function the config variable core.precomposeunicode is read into the global variable precomposed_unicode, as before. This reading is skipped if precomposed_unicode had been read before. The original patch for preocomposed unicode, 76759c7dff53, placed precompose_argv() into parse-options.c Now add it into git.c::run_builtin() as well. Existing precompose calls in diff-files.c and others may become redundant, and if we audit the callflows that reach these places to make sure that they can never be reached without going through the new call added to run_builtin(), we might be able to remove these existing ones. But in this commit, we do not bother to do so and leave these precompose callsites as they are. Because precompose() is idempotent and can be called on an already precomposed string safely, this is safer than removing existing calls without fully vetting the callflows. There is certainly room for cleanups - this change intends to be a bug fix. Cleanups needs more tests in e.g. t/t3910-mac-os-precompose.sh, and should be done in future commits. [1] git-bugreport-2021-01-06-1209.txt (git can't deal with special characters) [2] https://lore.kernel.org/git/A102844A-9501-4A86-854D-E3B387D378AA@icloud.com/ Reported-by: Daniel Troger <random_n0body@icloud.com> Helped-By: Philippe Blain <levraiphilippeblain@gmail.com> Signed-off-by: Torsten Bögershausen <tboegi@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-02-04 00:28:23 +08:00
precompose_argv_prefix(argc, argv, NULL);
parse-options: don't leak alias help messages preprocess_options() allocates new strings for help messages for OPTION_ALIAS. Therefore we also need to clean those help messages up when freeing the returned options. First introduced in: 7c280589cf (parse-options: teach "git cmd -h" to show alias as alias, 2020-03-16) The preprocessed options themselves no longer contain any indication that a given option is/was an alias - therefore we add a new flag to indicate former aliases. (An alternative approach would be to look back at the original options to determine which options are aliases - but that seems like a fragile approach. Or we could even look at the alias_groups list - which might be less fragile, but would be slower as it requires nested looping.) As far as I can tell, parse_options() is only ever used once per command, and the help messages are small - hence this leak has very little impact. This leak was found while running t0001. LSAN output can be found below: Direct leak of 65 byte(s) in 1 object(s) allocated from: #0 0x49a859 in realloc /home/abuild/rpmbuild/BUILD/llvm-11.0.0.src/build/../projects/compiler-rt/lib/asan/asan_malloc_linux.cpp:164:3 #1 0x9aae36 in xrealloc /home/ahunt/oss-fuzz/git/wrapper.c:126:8 #2 0x939d8d in strbuf_grow /home/ahunt/oss-fuzz/git/strbuf.c:98:2 #3 0x93b936 in strbuf_vaddf /home/ahunt/oss-fuzz/git/strbuf.c:392:3 #4 0x93b7ff in strbuf_addf /home/ahunt/oss-fuzz/git/strbuf.c:333:2 #5 0x86747e in preprocess_options /home/ahunt/oss-fuzz/git/parse-options.c:666:3 #6 0x866ed2 in parse_options /home/ahunt/oss-fuzz/git/parse-options.c:847:17 #7 0x51c4a7 in cmd_clone /home/ahunt/oss-fuzz/git/builtin/clone.c:989:9 #8 0x4cd60d in run_builtin /home/ahunt/oss-fuzz/git/git.c:453:11 #9 0x4cb2da in handle_builtin /home/ahunt/oss-fuzz/git/git.c:704:3 #10 0x4ccc37 in run_argv /home/ahunt/oss-fuzz/git/git.c:771:4 #11 0x4cac29 in cmd_main /home/ahunt/oss-fuzz/git/git.c:902:19 #12 0x69c9fe in main /home/ahunt/oss-fuzz/git/common-main.c:52:11 #13 0x7fdac42d4349 in __libc_start_main (/lib64/libc.so.6+0x24349) Signed-off-by: Andrzej Hunt <ajrhunt@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-03-22 00:58:36 +08:00
free_preprocessed_options(real_options);
free(ctx.alias_groups);
return parse_options_end(&ctx);
}
static int usage_argh(const struct option *opts, FILE *outfile)
{
const char *s;
int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
!opts->argh || !!strpbrk(opts->argh, "()<>[]|");
if (opts->flags & PARSE_OPT_OPTARG)
if (opts->long_name)
s = literal ? "[=%s]" : "[=<%s>]";
else
s = literal ? "[%s]" : "[<%s>]";
else
s = literal ? " %s" : " <%s>";
return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
}
#define USAGE_OPTS_WIDTH 24
#define USAGE_GAP 2
static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx,
const char * const *usagestr,
const struct option *opts,
int full, int err)
{
FILE *outfile = err ? stderr : stdout;
int need_newline;
parse-options: properly align continued usage output Some commands such as "git stash" emit continued options output with e.g. "git stash -h", because usage_with_options_internal() prefixes with its own whitespace the resulting output wasn't properly aligned. Let's account for the added whitespace, which properly aligns the output. The "git stash" command has usage output with a N_() translation that legitimately stretches across multiple lines; N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] We'd like to have that output aligned with the length of the initial "git stash " output, but since usage_with_options_internal() adds its own whitespace prefixing we fell short, before this change we'd emit: $ git stash -h usage: git stash list [<options>] or: git stash show [<options>] [<stash>] [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] Now we'll properly emit aligned output. I.e. the last four lines above will instead be (a whitespace-only change to the above): [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] We could also go for an approach where we have the caller support no padding of their own, i.e. (same as the first example, except for the padding on the second line): N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" "[-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] But to do that we'll need to find the length of "git stash". We can discover that from the "cmd" in the "struct cmd_struct", but there might be cases with sub-commands or "git" itself taking arguments that would make that non-trivial. Even if it were I still think this approach is better, because this way we'll get the same legible alignment in the C code. The fact that usage_with_options_internal() is adding its own prefix padding is an implementation detail that callers shouldn't need to worry about. Implementation notes: We could skip the string_list_split() with a strchr(str, '\n') check, but we'd then need to duplicate our state machine for strings that do and don't contain a "\n". It's simpler to just always split into a "struct string_list", even though the common case is that that "struct string_list" will contain only one element. This is not performance-sensitive code. This change is relatively more complex since I've accounted for making it future-proof for RTL translation support. Later in usage_with_options_internal() we have some existing padding code dating back to d7a38c54a6c (parse-options: be able to generate usages automatically, 2007-10-15) which isn't RTL-safe, but that code would be easy to fix. Let's not introduce new RTL translation problems here. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-21 21:30:11 +08:00
const char *usage_prefix = _("usage: %s");
/*
* The translation could be anything, but we can count on
* msgfmt(1)'s --check option to have asserted that "%s" is in
* the translation. So compute the length of the "usage: "
* part. We are assuming that the translator wasn't overly
* clever and used e.g. "%1$s" instead of "%s", there's only
* one "%s" in "usage_prefix" above, so there's no reason to
* do so even with a RTL language.
*/
size_t usage_len = strlen(usage_prefix) - strlen("%s");
/*
* TRANSLATORS: the colon here should align with the
* one in "usage: %s" translation.
*/
const char *or_prefix = _(" or: %s");
/*
* TRANSLATORS: You should only need to translate this format
* string if your language is a RTL language (e.g. Arabic,
* Hebrew etc.), not if it's a LTR language (e.g. German,
* Russian, Chinese etc.).
*
* When a translated usage string has an embedded "\n" it's
* because options have wrapped to the next line. The line
* after the "\n" will then be padded to align with the
* command name, such as N_("git cmd [opt]\n<8
* spaces>[opt2]"), where the 8 spaces are the same length as
* "git cmd ".
*
* This format string prints out that already-translated
* line. The "%*s" is whitespace padding to account for the
* padding at the start of the line that we add in this
* function. The "%s" is a line in the (hopefully already
* translated) N_() usage string, which contained embedded
* newlines before we split it up.
*/
const char *usage_continued = _("%*s%s");
const char *prefix = usage_prefix;
int saw_empty_line = 0;
if (!usagestr)
return PARSE_OPT_HELP;
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fprintf(outfile, "cat <<\\EOF\n");
while (*usagestr) {
parse-options: properly align continued usage output Some commands such as "git stash" emit continued options output with e.g. "git stash -h", because usage_with_options_internal() prefixes with its own whitespace the resulting output wasn't properly aligned. Let's account for the added whitespace, which properly aligns the output. The "git stash" command has usage output with a N_() translation that legitimately stretches across multiple lines; N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] We'd like to have that output aligned with the length of the initial "git stash " output, but since usage_with_options_internal() adds its own whitespace prefixing we fell short, before this change we'd emit: $ git stash -h usage: git stash list [<options>] or: git stash show [<options>] [<stash>] [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] Now we'll properly emit aligned output. I.e. the last four lines above will instead be (a whitespace-only change to the above): [...] or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all] [-m|--message <message>] [...] We could also go for an approach where we have the caller support no padding of their own, i.e. (same as the first example, except for the padding on the second line): N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" "[-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" [...] But to do that we'll need to find the length of "git stash". We can discover that from the "cmd" in the "struct cmd_struct", but there might be cases with sub-commands or "git" itself taking arguments that would make that non-trivial. Even if it were I still think this approach is better, because this way we'll get the same legible alignment in the C code. The fact that usage_with_options_internal() is adding its own prefix padding is an implementation detail that callers shouldn't need to worry about. Implementation notes: We could skip the string_list_split() with a strchr(str, '\n') check, but we'd then need to duplicate our state machine for strings that do and don't contain a "\n". It's simpler to just always split into a "struct string_list", even though the common case is that that "struct string_list" will contain only one element. This is not performance-sensitive code. This change is relatively more complex since I've accounted for making it future-proof for RTL translation support. Later in usage_with_options_internal() we have some existing padding code dating back to d7a38c54a6c (parse-options: be able to generate usages automatically, 2007-10-15) which isn't RTL-safe, but that code would be easy to fix. Let's not introduce new RTL translation problems here. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-21 21:30:11 +08:00
const char *str = _(*usagestr++);
struct string_list list = STRING_LIST_INIT_DUP;
unsigned int j;
if (!saw_empty_line && !*str)
saw_empty_line = 1;
string_list_split(&list, str, '\n', -1);
for (j = 0; j < list.nr; j++) {
const char *line = list.items[j].string;
if (saw_empty_line && *line)
fprintf_ln(outfile, _(" %s"), line);
else if (saw_empty_line)
fputc('\n', outfile);
else if (!j)
fprintf_ln(outfile, prefix, line);
else
fprintf_ln(outfile, usage_continued,
(int)usage_len, "", line);
}
string_list_clear(&list, 0);
prefix = or_prefix;
}
need_newline = 1;
for (; opts->type != OPTION_END; opts++) {
size_t pos;
int pad;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
need_newline = 0;
if (*opts->help)
fprintf(outfile, "%s\n", _(opts->help));
continue;
}
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
continue;
if (need_newline) {
fputc('\n', outfile);
need_newline = 0;
}
pos = fprintf(outfile, " ");
if (opts->short_name) {
if (opts->flags & PARSE_OPT_NODASH)
pos += fprintf(outfile, "%c", opts->short_name);
else
pos += fprintf(outfile, "-%c", opts->short_name);
}
if (opts->long_name && opts->short_name)
pos += fprintf(outfile, ", ");
if (opts->long_name)
pos += fprintf(outfile, "--%s", opts->long_name);
if (opts->type == OPTION_NUMBER)
pos += utf8_fprintf(outfile, _("-NUM"));
if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
!(opts->flags & PARSE_OPT_NOARG))
pos += usage_argh(opts, outfile);
if (pos <= USAGE_OPTS_WIDTH)
pad = USAGE_OPTS_WIDTH - pos;
else {
fputc('\n', outfile);
pad = USAGE_OPTS_WIDTH;
}
if (opts->type == OPTION_ALIAS) {
fprintf(outfile, "%*s", pad + USAGE_GAP, "");
fprintf_ln(outfile, _("alias of --%s"),
(const char *)opts->value);
continue;
}
fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help));
}
fputc('\n', outfile);
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
fputs("EOF\n", outfile);
return PARSE_OPT_HELP;
}
Fix sparse warnings Fix warnings from 'make check'. - These files don't include 'builtin.h' causing sparse to complain that cmd_* isn't declared: builtin/clone.c:364, builtin/fetch-pack.c:797, builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78, builtin/merge-index.c:69, builtin/merge-recursive.c:22 builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426 builtin/notes.c:822, builtin/pack-redundant.c:596, builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149, builtin/remote.c:1512, builtin/remote-ext.c:240, builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384, builtin/unpack-file.c:25, builtin/var.c:75 - These files have symbols which should be marked static since they're only file scope: submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13, submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79, unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123, url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48 - These files redeclare symbols to be different types: builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571, usage.c:49, usage.c:58, usage.c:63, usage.c:72 - These files use a literal integer 0 when they really should use a NULL pointer: daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362 While we're in the area, clean up some unused #includes in builtin files (mostly exec_cmd.h). Signed-off-by: Stephen Boyd <bebarino@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 15:51:05 +08:00
void NORETURN usage_with_options(const char * const *usagestr,
const struct option *opts)
{
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
exit(129);
}
Fix sparse warnings Fix warnings from 'make check'. - These files don't include 'builtin.h' causing sparse to complain that cmd_* isn't declared: builtin/clone.c:364, builtin/fetch-pack.c:797, builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78, builtin/merge-index.c:69, builtin/merge-recursive.c:22 builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426 builtin/notes.c:822, builtin/pack-redundant.c:596, builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149, builtin/remote.c:1512, builtin/remote-ext.c:240, builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384, builtin/unpack-file.c:25, builtin/var.c:75 - These files have symbols which should be marked static since they're only file scope: submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13, submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79, unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123, url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48 - These files redeclare symbols to be different types: builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571, usage.c:49, usage.c:58, usage.c:63, usage.c:72 - These files use a literal integer 0 when they really should use a NULL pointer: daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362 While we're in the area, clean up some unused #includes in builtin files (mostly exec_cmd.h). Signed-off-by: Stephen Boyd <bebarino@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 15:51:05 +08:00
void NORETURN usage_msg_opt(const char *msg,
const char * const *usagestr,
const struct option *options)
{
die_message("%s\n", msg); /* The extra \n is intentional */
usage_with_options(usagestr, options);
}
void NORETURN usage_msg_optf(const char * const fmt,
const char * const *usagestr,
const struct option *options, ...)
{
struct strbuf msg = STRBUF_INIT;
va_list ap;
va_start(ap, options);
strbuf_vaddf(&msg, fmt, ap);
va_end(ap);
usage_msg_opt(msg.buf, usagestr, options);
}
void die_for_incompatible_opt4(int opt1, const char *opt1_name,
int opt2, const char *opt2_name,
int opt3, const char *opt3_name,
int opt4, const char *opt4_name)
{
int count = 0;
const char *options[4];
if (opt1)
options[count++] = opt1_name;
if (opt2)
options[count++] = opt2_name;
if (opt3)
options[count++] = opt3_name;
if (opt4)
options[count++] = opt4_name;
switch (count) {
case 4:
die(_("options '%s', '%s', '%s', and '%s' cannot be used together"),
opt1_name, opt2_name, opt3_name, opt4_name);
break;
case 3:
die(_("options '%s', '%s', and '%s' cannot be used together"),
options[0], options[1], options[2]);
break;
case 2:
die(_("options '%s' and '%s' cannot be used together"),
options[0], options[1]);
break;
default:
break;
}
}