2007-10-14 23:35:37 +00:00
|
|
|
#include "git-compat-util.h"
|
|
|
|
#include "parse-options.h"
|
2008-06-23 20:55:11 +00:00
|
|
|
#include "cache.h"
|
2017-06-14 18:07:36 +00:00
|
|
|
#include "config.h"
|
2009-01-26 14:13:23 +00:00
|
|
|
#include "commit.h"
|
Add an optional argument for --color options
Make git-branch, git-show-branch, git-grep, and all the diff-based
programs accept an optional argument <when> for --color. The argument
is a colorbool: "always", "never", or "auto". If no argument is given,
"always" is used; --no-color is an alias for --color=never. This makes
the command-line interface consistent with other GNU tools, such as `ls'
and `grep', and with the git-config color options. Note that, without
an argument, --color and --no-color work exactly as before.
To implement this, two internal changes were made:
1. Allow the first argument of git_config_colorbool() to be NULL,
in which case it returns -1 if the argument isn't "always", "never",
or "auto".
2. Add OPT_COLOR_FLAG(), OPT__COLOR(), and parse_opt_color_flag_cb()
to the option parsing library. The callback uses
git_config_colorbool(), so color.h is now a dependency
of parse-options.c.
Signed-off-by: Mark Lodato <lodatom@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-02-17 04:55:58 +00:00
|
|
|
#include "color.h"
|
2013-02-09 06:31:09 +00:00
|
|
|
#include "utf8.h"
|
2007-10-14 23:35:37 +00:00
|
|
|
|
tests: disallow the use of abbreviated options (by default)
Git's command-line parsers support uniquely abbreviated options, e.g.
`git init --ba` would automatically expand `--ba` to `--bare`.
This is a very convenient feature in every day life for Git users, in
particular when tab completion is not available.
However, it is not a good idea to rely on that in Git's test suite, as
something that is a unique abbreviation of a command line option today
might no longer be a unique abbreviation tomorrow.
For example, if a future contribution added a new mode
`git init --babyproofing` and a previously-introduced test case used the
fact that `git init --ba` expanded to `git init --bare`, that future
contribution would now have to touch seemingly unrelated tests just to
keep the test suite from failing.
So let's disallow abbreviated options in the test suite by default.
Note: for ease of implementation, this patch really only touches the
`parse-options` machinery: more and more hand-rolled option parsers are
converted to use that internal API, and more and more scripts are
converted to built-ins (naturally using the parse-options API, too), so
in practice this catches most issues, and is definitely the biggest bang
for the buck.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-12 09:37:24 +00:00
|
|
|
static int disallow_abbreviated_options;
|
|
|
|
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed {
|
|
|
|
OPT_LONG = 0,
|
|
|
|
OPT_SHORT = 1<<0,
|
|
|
|
OPT_UNSET = 1<<1,
|
|
|
|
};
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2022-06-02 12:25:34 +00:00
|
|
|
static void optbug(const struct option *opt, const char *reason)
|
2010-12-02 06:01:18 +00:00
|
|
|
{
|
2022-06-02 12:25:34 +00:00
|
|
|
if (opt->long_name && opt->short_name)
|
|
|
|
bug("switch '%c' (--%s) %s", opt->short_name,
|
|
|
|
opt->long_name, reason);
|
|
|
|
else if (opt->long_name)
|
|
|
|
bug("option '%s' %s", opt->long_name, reason);
|
|
|
|
else
|
|
|
|
bug("switch '%c' %s", opt->short_name, reason);
|
2010-12-02 06:01:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 19:07:46 +00:00
|
|
|
static const char *optname(const struct option *opt, enum opt_parsed flags)
|
2021-10-08 19:07:42 +00:00
|
|
|
{
|
|
|
|
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);
|
2021-10-08 19:07:46 +00:00
|
|
|
else if (flags == OPT_LONG)
|
2021-10-08 19:07:42 +00:00
|
|
|
strbuf_addf(&sb, "option `%s'", opt->long_name);
|
2021-10-08 19:07:46 +00:00
|
|
|
else
|
|
|
|
BUG("optname() got unknown flags %d", flags);
|
2021-10-08 19:07:42 +00:00
|
|
|
|
|
|
|
return sb.buf;
|
|
|
|
}
|
|
|
|
|
2019-01-27 00:35:27 +00:00
|
|
|
static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *opt,
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed flags, const char **arg)
|
2008-07-08 10:34:08 +00:00
|
|
|
{
|
|
|
|
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;
|
2008-07-21 18:30:36 +00:00
|
|
|
} else if (p->argc > 1) {
|
2008-07-08 10:34:08 +00:00
|
|
|
p->argc--;
|
|
|
|
*arg = *++p->argv;
|
|
|
|
} else
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s requires a value"), optname(opt, flags));
|
2008-07-08 10:34:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-23 18:53:13 +00:00
|
|
|
static void fix_filename(const char *prefix, const char **file)
|
|
|
|
{
|
|
|
|
if (!file || !*file || !prefix || is_absolute_path(*file)
|
|
|
|
|| !strcmp("-", *file))
|
|
|
|
return;
|
2017-03-21 01:28:49 +00:00
|
|
|
*file = prefix_filename(prefix, *file);
|
2009-05-23 18:53:13 +00:00
|
|
|
}
|
|
|
|
|
2019-01-27 00:35:27 +00:00
|
|
|
static enum parse_opt_result opt_command_mode_error(
|
|
|
|
const struct option *opt,
|
|
|
|
const struct option *all_opts,
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed flags)
|
2013-07-30 19:06:01 +00:00
|
|
|
{
|
|
|
|
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 ||
|
2020-02-20 14:15:16 +00:00
|
|
|
!(that->flags & PARSE_OPT_CMDMODE) ||
|
2013-07-30 19:06:01 +00:00
|
|
|
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);
|
2018-11-10 05:16:11 +00:00
|
|
|
error(_("%s is incompatible with %s"),
|
|
|
|
optname(opt, flags), that_name.buf);
|
2013-07-30 19:06:01 +00:00
|
|
|
strbuf_release(&that_name);
|
2019-01-27 00:35:27 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2013-07-30 19:06:01 +00:00
|
|
|
}
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s : incompatible with something else"),
|
|
|
|
optname(opt, flags));
|
2013-07-30 19:06:01 +00:00
|
|
|
}
|
|
|
|
|
2019-01-27 00:35:27 +00:00
|
|
|
static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *opt,
|
|
|
|
const struct option *all_opts,
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed flags)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2007-10-14 23:45:45 +00:00
|
|
|
const char *s, *arg;
|
2007-11-07 10:20:27 +00:00
|
|
|
const int unset = flags & OPT_UNSET;
|
2009-05-23 18:53:13 +00:00
|
|
|
int err;
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset && p->opt)
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset && (opt->flags & PARSE_OPT_NONEG))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s isn't available"), optname(opt, flags));
|
2010-12-01 23:30:40 +00:00
|
|
|
if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 10:20:27 +00:00
|
|
|
|
2020-02-20 14:15:16 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
switch (opt->type) {
|
parse-options: allow git commands to invent new option types
parse-options provides a variety of option behaviors, including
OPTION_CALLBACK, which should take care of just about any sane
behavior. All supported behaviors obey the following constraint:
A --foo option can only accept (and base its behavior on)
one argument, which would be the following command-line
argument in the "unsticked" form.
Alas, some existing git commands have options that do not obey that
constraint. For example, update-index --cacheinfo takes three
arguments, and update-index --resolve takes all later parameters as
arguments.
Introduces an OPTION_LOWLEVEL_CALLBACK backdoor to parse-options so
such option types can be supported without tempting inventors of other
commands through mention in the public API. Commands can set the
callback field to a function accepting three arguments: the option
parsing context, the option itself, and a flag indicating whether the
the option was negated. When the option is encountered, that function
is called to take over from get_value(). The return value should be
zero for success, -1 for usage errors.
Thanks to Stephen Boyd for API guidance.
Improved-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-12-01 23:32:16 +00:00
|
|
|
case OPTION_LOWLEVEL_CALLBACK:
|
2019-01-27 00:35:28 +00:00
|
|
|
return opt->ll_callback(p, opt, NULL, unset);
|
parse-options: allow git commands to invent new option types
parse-options provides a variety of option behaviors, including
OPTION_CALLBACK, which should take care of just about any sane
behavior. All supported behaviors obey the following constraint:
A --foo option can only accept (and base its behavior on)
one argument, which would be the following command-line
argument in the "unsticked" form.
Alas, some existing git commands have options that do not obey that
constraint. For example, update-index --cacheinfo takes three
arguments, and update-index --resolve takes all later parameters as
arguments.
Introduces an OPTION_LOWLEVEL_CALLBACK backdoor to parse-options so
such option types can be supported without tempting inventors of other
commands through mention in the public API. Commands can set the
callback field to a function accepting three arguments: the option
parsing context, the option itself, and a flag indicating whether the
the option was negated. When the option is encountered, that function
is called to take over from get_value(). The return value should be
zero for success, -1 for usage errors.
Thanks to Stephen Boyd for API guidance.
Improved-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2010-12-01 23:32:16 +00:00
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
case OPTION_BIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
2007-10-14 23:35:37 +00:00
|
|
|
else
|
2007-11-07 10:20:27 +00:00
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2009-05-07 19:44:17 +00:00
|
|
|
case OPTION_NEGBIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
else
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2019-01-27 00:35:25 +00:00
|
|
|
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-27 23:56:49 +00: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 09:50:00 +00:00
|
|
|
if (*(int *)opt->value < 0)
|
|
|
|
*(int *)opt->value = 0;
|
2007-11-07 10:20:27 +00:00
|
|
|
*(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
*(int *)opt->value = unset ? 0 : opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
case OPTION_STRING:
|
2008-07-08 10:34:08 +00:00
|
|
|
if (unset)
|
2007-11-07 10:20:27 +00:00
|
|
|
*(const char **)opt->value = NULL;
|
2008-07-08 10:34:08 +00:00
|
|
|
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
2007-10-14 23:45:45 +00:00
|
|
|
*(const char **)opt->value = (const char *)opt->defval;
|
2008-07-08 10:34:08 +00:00
|
|
|
else
|
|
|
|
return get_arg(p, opt, flags, (const char **)opt->value);
|
2007-10-14 23:35:37 +00:00
|
|
|
return 0;
|
|
|
|
|
2009-05-23 18:53:13 +00:00
|
|
|
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;
|
|
|
|
|
2007-10-14 23:45:45 +00:00
|
|
|
case OPTION_CALLBACK:
|
2019-01-27 00:35:28 +00:00
|
|
|
{
|
|
|
|
const char *p_arg = NULL;
|
|
|
|
int p_unset;
|
|
|
|
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset)
|
2019-01-27 00:35:28 +00:00
|
|
|
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))
|
2008-07-08 10:34:08 +00:00
|
|
|
return -1;
|
2019-01-27 00:35:28 +00:00
|
|
|
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);
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
case OPTION_INTEGER:
|
2007-11-07 10:20:27 +00:00
|
|
|
if (unset) {
|
2007-10-14 23:35:37 +00:00
|
|
|
*(int *)opt->value = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2007-12-21 10:41:41 +00:00
|
|
|
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
|
2007-10-14 23:45:45 +00:00
|
|
|
*(int *)opt->value = opt->defval;
|
|
|
|
return 0;
|
|
|
|
}
|
2008-07-08 10:34:08 +00:00
|
|
|
if (get_arg(p, opt, flags, &arg))
|
|
|
|
return -1;
|
2019-05-29 09:11:16 +00:00
|
|
|
if (!*arg)
|
|
|
|
return error(_("%s expects a numerical value"),
|
|
|
|
optname(opt, flags));
|
2008-07-08 10:34:08 +00:00
|
|
|
*(int *)opt->value = strtol(arg, (char **)&s, 10);
|
2007-10-14 23:35:37 +00:00
|
|
|
if (*s)
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s expects a numerical value"),
|
|
|
|
optname(opt, flags));
|
2007-10-14 23:35:37 +00:00
|
|
|
return 0;
|
|
|
|
|
2015-06-21 18:25:44 +00:00
|
|
|
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))
|
2018-11-10 05:16:11 +00:00
|
|
|
return error(_("%s expects a non-negative integer value"
|
|
|
|
" with an optional k/m/g suffix"),
|
|
|
|
optname(opt, flags));
|
2015-06-21 18:25:44 +00:00
|
|
|
return 0;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
default:
|
2018-11-10 05:16:12 +00:00
|
|
|
BUG("opt->type %d should not happen", opt->type);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-27 00:35:27 +00:00
|
|
|
static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *options)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
2009-05-07 19:45:08 +00:00
|
|
|
const struct option *numopt = NULL;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (options->short_name == *p->opt) {
|
|
|
|
p->opt = p->opt[1] ? p->opt + 1 : NULL;
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, OPT_SHORT);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2009-05-07 19:45:08 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2019-01-27 00:35:28 +00:00
|
|
|
if (numopt->callback)
|
|
|
|
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
|
|
|
|
else
|
|
|
|
rc = (*numopt->ll_callback)(p, numopt, arg, 0);
|
2009-05-07 19:45:08 +00:00
|
|
|
free(arg);
|
|
|
|
return rc;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2019-01-27 00:35:27 +00:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 10:05:25 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-01-27 00:35:27 +00:00
|
|
|
static enum parse_opt_result parse_long_opt(
|
|
|
|
struct parse_opt_ctx_t *p, const char *arg,
|
|
|
|
const struct option *options)
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
2014-03-08 06:48:31 +00:00
|
|
|
const char *arg_end = strchrnul(arg, '=');
|
2007-11-05 13:15:21 +00:00
|
|
|
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG;
|
2007-10-14 16:54:06 +00:00
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
2012-02-25 19:14:54 +00:00
|
|
|
const char *rest, *long_name = options->long_name;
|
2021-10-08 19:07:46 +00:00
|
|
|
enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
|
2007-10-14 23:35:37 +00:00
|
|
|
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
if (options->type == OPTION_SUBCOMMAND)
|
|
|
|
continue;
|
2012-02-25 19:14:54 +00:00
|
|
|
if (!long_name)
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
|
|
|
|
2012-02-25 19:14:54 +00:00
|
|
|
again:
|
refactor skip_prefix to return a boolean
The skip_prefix() function returns a pointer to the content
past the prefix, or NULL if the prefix was not found. While
this is nice and simple, in practice it makes it hard to use
for two reasons:
1. When you want to conditionally skip or keep the string
as-is, you have to introduce a temporary variable.
For example:
tmp = skip_prefix(buf, "foo");
if (tmp)
buf = tmp;
2. It is verbose to check the outcome in a conditional, as
you need extra parentheses to silence compiler
warnings. For example:
if ((cp = skip_prefix(buf, "foo"))
/* do something with cp */
Both of these make it harder to use for long if-chains, and
we tend to use starts_with() instead. However, the first line
of "do something" is often to then skip forward in buf past
the prefix, either using a magic constant or with an extra
strlen(3) (which is generally computed at compile time, but
means we are repeating ourselves).
This patch refactors skip_prefix() to return a simple boolean,
and to provide the pointer value as an out-parameter. If the
prefix is not found, the out-parameter is untouched. This
lets you write:
if (skip_prefix(arg, "foo ", &arg))
do_foo(arg);
else if (skip_prefix(arg, "bar ", &arg))
do_bar(arg);
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 19:44:19 +00:00
|
|
|
if (!skip_prefix(arg, long_name, &rest))
|
|
|
|
rest = NULL;
|
2007-10-14 23:35:37 +00:00
|
|
|
if (!rest) {
|
2007-10-14 16:54:06 +00:00
|
|
|
/* abbreviated? */
|
2022-08-19 16:03:57 +00:00
|
|
|
if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
|
2019-01-27 00:35:24 +00:00
|
|
|
!strncmp(long_name, arg, arg_end - arg)) {
|
2007-10-14 16:54:06 +00:00
|
|
|
is_abbreviated:
|
2019-04-29 10:05:25 +00:00
|
|
|
if (abbrev_option &&
|
|
|
|
!is_alias(p, abbrev_option, options)) {
|
2007-11-05 13:15:21 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
2007-10-14 16:54:06 +00:00
|
|
|
if (!(flags & OPT_UNSET) && *arg_end)
|
|
|
|
p->opt = arg_end + 1;
|
|
|
|
abbrev_option = options;
|
2012-02-25 19:14:54 +00:00
|
|
|
abbrev_flags = flags ^ opt_flags;
|
2007-10-14 16:54:06 +00:00
|
|
|
continue;
|
|
|
|
}
|
2009-09-25 18:44:44 +00:00
|
|
|
/* negation allowed? */
|
|
|
|
if (options->flags & PARSE_OPT_NONEG)
|
|
|
|
continue;
|
2007-10-14 16:54:06 +00:00
|
|
|
/* negated and abbreviated very much? */
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with("no-", arg)) {
|
2007-10-14 16:54:06 +00:00
|
|
|
flags |= OPT_UNSET;
|
|
|
|
goto is_abbreviated;
|
|
|
|
}
|
|
|
|
/* negated? */
|
2013-11-30 20:55:40 +00:00
|
|
|
if (!starts_with(arg, "no-")) {
|
2020-01-30 19:35:46 +00:00
|
|
|
if (skip_prefix(long_name, "no-", &long_name)) {
|
2012-02-25 19:14:54 +00:00
|
|
|
opt_flags |= OPT_UNSET;
|
|
|
|
goto again;
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
2012-02-25 19:14:54 +00:00
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
flags |= OPT_UNSET;
|
refactor skip_prefix to return a boolean
The skip_prefix() function returns a pointer to the content
past the prefix, or NULL if the prefix was not found. While
this is nice and simple, in practice it makes it hard to use
for two reasons:
1. When you want to conditionally skip or keep the string
as-is, you have to introduce a temporary variable.
For example:
tmp = skip_prefix(buf, "foo");
if (tmp)
buf = tmp;
2. It is verbose to check the outcome in a conditional, as
you need extra parentheses to silence compiler
warnings. For example:
if ((cp = skip_prefix(buf, "foo"))
/* do something with cp */
Both of these make it harder to use for long if-chains, and
we tend to use starts_with() instead. However, the first line
of "do something" is often to then skip forward in buf past
the prefix, either using a magic constant or with an extra
strlen(3) (which is generally computed at compile time, but
means we are repeating ourselves).
This patch refactors skip_prefix() to return a simple boolean,
and to provide the pointer value as an out-parameter. If the
prefix is not found, the out-parameter is untouched. This
lets you write:
if (skip_prefix(arg, "foo ", &arg))
do_foo(arg);
else if (skip_prefix(arg, "bar ", &arg))
do_bar(arg);
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-06-18 19:44:19 +00:00
|
|
|
if (!skip_prefix(arg + 3, long_name, &rest)) {
|
|
|
|
/* abbreviated and negated? */
|
|
|
|
if (starts_with(long_name, arg + 3))
|
|
|
|
goto is_abbreviated;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
if (*rest) {
|
|
|
|
if (*rest != '=')
|
|
|
|
continue;
|
|
|
|
p->opt = rest + 1;
|
|
|
|
}
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, flags ^ opt_flags);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2007-11-05 13:15:21 +00:00
|
|
|
|
tests: disallow the use of abbreviated options (by default)
Git's command-line parsers support uniquely abbreviated options, e.g.
`git init --ba` would automatically expand `--ba` to `--bare`.
This is a very convenient feature in every day life for Git users, in
particular when tab completion is not available.
However, it is not a good idea to rely on that in Git's test suite, as
something that is a unique abbreviation of a command line option today
might no longer be a unique abbreviation tomorrow.
For example, if a future contribution added a new mode
`git init --babyproofing` and a previously-introduced test case used the
fact that `git init --ba` expanded to `git init --bare`, that future
contribution would now have to touch seemingly unrelated tests just to
keep the test suite from failing.
So let's disallow abbreviated options in the test suite by default.
Note: for ease of implementation, this patch really only touches the
`parse-options` machinery: more and more hand-rolled option parsers are
converted to use that internal API, and more and more scripts are
converted to built-ins (naturally using the parse-options API, too), so
in practice this catches most issues, and is definitely the biggest bang
for the buck.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-12 09:37:24 +00:00
|
|
|
if (disallow_abbreviated_options && (ambiguous_option || abbrev_option))
|
|
|
|
die("disallowed abbreviated or ambiguous option '%.*s'",
|
|
|
|
(int)(arg_end - arg), arg);
|
|
|
|
|
2018-03-22 18:43:51 +00:00
|
|
|
if (ambiguous_option) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("ambiguous option: %s "
|
|
|
|
"(could be --%s%s or --%s%s)"),
|
2007-11-05 13:15:21 +00:00
|
|
|
arg,
|
|
|
|
(ambiguous_flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
ambiguous_option->long_name,
|
|
|
|
(abbrev_flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
abbrev_option->long_name);
|
2019-01-27 00:35:27 +00:00
|
|
|
return PARSE_OPT_HELP;
|
2018-03-22 18:43:51 +00:00
|
|
|
}
|
2007-10-14 16:54:06 +00:00
|
|
|
if (abbrev_option)
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, abbrev_option, all_opts, abbrev_flags);
|
2019-01-27 00:35:27 +00:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
2021-11-10 01:27:04 +00:00
|
|
|
static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
|
|
|
|
const char *arg,
|
|
|
|
const struct option *options)
|
2009-05-07 19:45:42 +00:00
|
|
|
{
|
2013-07-30 19:06:01 +00:00
|
|
|
const struct option *all_opts = options;
|
|
|
|
|
2009-05-07 19:45:42 +00:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!(options->flags & PARSE_OPT_NODASH))
|
|
|
|
continue;
|
|
|
|
if (options->short_name == arg[0] && arg[1] == '\0')
|
2013-07-30 19:06:01 +00:00
|
|
|
return get_value(p, options, all_opts, OPT_SHORT);
|
2009-05-07 19:45:42 +00:00
|
|
|
}
|
2021-11-10 01:27:04 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2009-05-07 19:45:42 +00:00
|
|
|
}
|
|
|
|
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
static enum parse_opt_result parse_subcommand(const char *arg,
|
|
|
|
const struct option *options)
|
|
|
|
{
|
|
|
|
for (; options->type != OPTION_END; options++)
|
|
|
|
if (options->type == OPTION_SUBCOMMAND &&
|
|
|
|
!strcmp(options->long_name, arg)) {
|
|
|
|
*(parse_opt_subcommand_fn **)options->value = options->subcommand_fn;
|
|
|
|
return PARSE_OPT_SUBCOMMAND;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PARSE_OPT_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2008-07-16 10:42:18 +00:00
|
|
|
static void check_typos(const char *arg, const struct option *options)
|
2008-01-26 11:26:57 +00:00
|
|
|
{
|
|
|
|
if (strlen(arg) < 3)
|
|
|
|
return;
|
|
|
|
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(arg, "no-")) {
|
2020-02-05 13:07:23 +00:00
|
|
|
error(_("did you mean `--%s` (with two dashes)?"), arg);
|
2008-01-26 11:26:57 +00:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!options->long_name)
|
|
|
|
continue;
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(options->long_name, arg)) {
|
2020-02-05 13:07:23 +00:00
|
|
|
error(_("did you mean `--%s` (with two dashes)?"), arg);
|
2008-01-26 11:26:57 +00:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-09 08:23:44 +00:00
|
|
|
static void parse_options_check(const struct option *opts)
|
|
|
|
{
|
2014-09-03 19:42:37 +00:00
|
|
|
char short_opts[128];
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
void *subcommand_value = NULL;
|
2009-06-09 08:23:44 +00:00
|
|
|
|
2014-09-03 19:42:37 +00:00
|
|
|
memset(short_opts, '\0', sizeof(short_opts));
|
2009-06-09 08:23:44 +00:00
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
|
2010-12-02 06:01:18 +00:00
|
|
|
(opts->flags & PARSE_OPT_OPTARG))
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "uses incompatible flags "
|
|
|
|
"LASTARG_DEFAULT and OPTARG");
|
2014-09-03 19:42:37 +00:00
|
|
|
if (opts->short_name) {
|
|
|
|
if (0x7F <= opts->short_name)
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "invalid short name");
|
2014-09-03 19:42:37 +00:00
|
|
|
else if (short_opts[opts->short_name]++)
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "short name already used");
|
2014-09-03 19:42:37 +00:00
|
|
|
}
|
2010-12-02 06:05:05 +00:00
|
|
|
if (opts->flags & PARSE_OPT_NODASH &&
|
|
|
|
((opts->flags & PARSE_OPT_OPTARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NONEG) ||
|
|
|
|
opts->long_name))
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "uses feature "
|
|
|
|
"not supported for dashless options");
|
2010-12-02 06:08:57 +00:00
|
|
|
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-27 23:56:49 +00:00
|
|
|
case OPTION_COUNTUP:
|
2010-12-02 06:08:57 +00:00
|
|
|
case OPTION_BIT:
|
|
|
|
case OPTION_NEGBIT:
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
case OPTION_NUMBER:
|
|
|
|
if ((opts->flags & PARSE_OPT_OPTARG) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG))
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "should not accept an argument");
|
2019-01-27 00:35:26 +00:00
|
|
|
break;
|
|
|
|
case OPTION_CALLBACK:
|
2019-01-27 00:35:28 +00:00
|
|
|
if (!opts->callback && !opts->ll_callback)
|
2022-06-02 12:25:35 +00:00
|
|
|
optbug(opts, "OPTION_CALLBACK needs one callback");
|
|
|
|
else if (opts->callback && opts->ll_callback)
|
|
|
|
optbug(opts, "OPTION_CALLBACK can't have two callbacks");
|
2019-01-27 00:35:26 +00:00
|
|
|
break;
|
|
|
|
case OPTION_LOWLEVEL_CALLBACK:
|
|
|
|
if (!opts->ll_callback)
|
2022-06-02 12:25:35 +00:00
|
|
|
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback");
|
2019-01-27 00:35:26 +00:00
|
|
|
if (opts->callback)
|
2022-06-02 12:25:35 +00:00
|
|
|
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback");
|
2019-01-27 00:35:26 +00:00
|
|
|
break;
|
2019-04-29 10:05:25 +00:00
|
|
|
case OPTION_ALIAS:
|
2022-06-02 12:25:35 +00:00
|
|
|
optbug(opts, "OPT_ALIAS() should not remain at this point. "
|
|
|
|
"Are you using parse_options_step() directly?\n"
|
|
|
|
"That case is not supported yet.");
|
|
|
|
break;
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case OPTION_SUBCOMMAND:
|
|
|
|
if (!opts->value || !opts->subcommand_fn)
|
|
|
|
optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function");
|
|
|
|
if (!subcommand_value)
|
|
|
|
subcommand_value = opts->value;
|
|
|
|
else if (subcommand_value != opts->value)
|
|
|
|
optbug(opts, "all OPTION_SUBCOMMANDs need the same value");
|
|
|
|
break;
|
2010-12-02 06:08:57 +00:00
|
|
|
default:
|
|
|
|
; /* ok. (usually accepts an argument) */
|
|
|
|
}
|
2014-03-23 23:04:36 +00:00
|
|
|
if (opts->argh &&
|
|
|
|
strcspn(opts->argh, " _") != strlen(opts->argh))
|
2022-06-02 12:25:34 +00:00
|
|
|
optbug(opts, "multi-word argh should use dash to separate words");
|
2009-06-09 08:23:44 +00:00
|
|
|
}
|
2022-06-02 12:25:34 +00:00
|
|
|
BUG_if_bug("invalid 'struct option'");
|
2009-06-09 08:23:44 +00:00
|
|
|
}
|
|
|
|
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
static int has_subcommands(const struct option *options)
|
|
|
|
{
|
|
|
|
for (; options->type != OPTION_END; options++)
|
|
|
|
if (options->type == OPTION_SUBCOMMAND)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-29 10:05:25 +00:00
|
|
|
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
|
|
|
|
int argc, const char **argv, const char *prefix,
|
2021-10-08 19:07:38 +00:00
|
|
|
const struct option *options,
|
|
|
|
enum parse_opt_flags flags)
|
2008-06-23 19:59:37 +00:00
|
|
|
{
|
2019-01-27 00:35:23 +00:00
|
|
|
ctx->argc = argc;
|
|
|
|
ctx->argv = argv;
|
|
|
|
if (!(flags & PARSE_OPT_ONE_SHOT)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
|
|
|
}
|
|
|
|
ctx->total = ctx->argc;
|
|
|
|
ctx->out = argv;
|
2009-05-23 18:53:12 +00:00
|
|
|
ctx->prefix = prefix;
|
2008-06-23 22:31:31 +00:00
|
|
|
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
|
2008-06-23 19:59:37 +00:00
|
|
|
ctx->flags = flags;
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
ctx->has_subcommands = has_subcommands(options);
|
|
|
|
if (!ctx->has_subcommands && (flags & PARSE_OPT_SUBCOMMAND_OPTIONAL))
|
|
|
|
BUG("Using PARSE_OPT_SUBCOMMAND_OPTIONAL without subcommands");
|
|
|
|
if (ctx->has_subcommands) {
|
|
|
|
if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
|
|
|
|
BUG("subcommands are incompatible with PARSE_OPT_STOP_AT_NON_OPTION");
|
|
|
|
if (!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
|
|
|
|
if (flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
|
|
|
|
BUG("subcommands are incompatible with PARSE_OPT_KEEP_UNKNOWN_OPT unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
|
|
|
|
if (flags & PARSE_OPT_KEEP_DASHDASH)
|
|
|
|
BUG("subcommands are incompatible with PARSE_OPT_KEEP_DASHDASH unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 16:03:57 +00:00
|
|
|
if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
|
2019-01-27 00:35:23 +00:00
|
|
|
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
|
|
|
|
!(flags & PARSE_OPT_ONE_SHOT))
|
2018-11-10 05:16:12 +00:00
|
|
|
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
|
2019-01-27 00:35:23 +00:00
|
|
|
if ((flags & PARSE_OPT_ONE_SHOT) &&
|
|
|
|
(flags & PARSE_OPT_KEEP_ARGV0))
|
|
|
|
BUG("Can't keep argv0 if you don't have it");
|
2010-12-06 07:57:42 +00:00
|
|
|
parse_options_check(options);
|
2008-06-23 19:59:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 10:05:25 +00:00
|
|
|
void parse_options_start(struct parse_opt_ctx_t *ctx,
|
|
|
|
int argc, const char **argv, const char *prefix,
|
2021-10-08 19:07:38 +00:00
|
|
|
const struct option *options,
|
|
|
|
enum parse_opt_flags flags)
|
2019-04-29 10:05:25 +00:00
|
|
|
{
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
parse_options_start_1(ctx, argc, argv, prefix, options, flags);
|
|
|
|
}
|
|
|
|
|
2020-08-19 23:06:08 +00:00
|
|
|
static void show_negated_gitcomp(const struct option *opts, int show_all,
|
|
|
|
int nr_noopts)
|
2018-06-06 09:41:39 +00:00
|
|
|
{
|
|
|
|
int printed_dashdash = 0;
|
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
int has_unset_form = 0;
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
2020-08-19 23:06:08 +00:00
|
|
|
if (!show_all &&
|
|
|
|
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)))
|
2018-06-06 09:41:39 +00: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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 23:06:08 +00:00
|
|
|
static int show_gitcomp(const struct option *opts, int show_all)
|
2018-02-09 11:01:40 +00:00
|
|
|
{
|
2018-06-06 09:41:39 +00:00
|
|
|
const struct option *original_opts = opts;
|
|
|
|
int nr_noopts = 0;
|
|
|
|
|
2018-02-09 11:01:40 +00:00
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
const char *prefix = "--";
|
2018-02-09 11:01:40 +00:00
|
|
|
const char *suffix = "";
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
2020-08-19 23:06:08 +00:00
|
|
|
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 01:55:43 +00:00
|
|
|
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE | PARSE_OPT_FROM_ALIAS)))
|
2018-02-09 11:01:40 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (opts->type) {
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case OPTION_SUBCOMMAND:
|
|
|
|
prefix = "";
|
|
|
|
break;
|
2018-02-09 11:01:40 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-02-09 11:02:12 +00:00
|
|
|
if (opts->flags & PARSE_OPT_COMP_ARG)
|
|
|
|
suffix = "=";
|
2018-06-06 09:41:39 +00:00
|
|
|
if (starts_with(opts->long_name, "no-"))
|
|
|
|
nr_noopts++;
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
printf("%s%s%s%s", opts == original_opts ? "" : " ",
|
|
|
|
prefix, opts->long_name, suffix);
|
2018-02-09 11:01:40 +00:00
|
|
|
}
|
2020-08-19 23:06:08 +00:00
|
|
|
show_negated_gitcomp(original_opts, show_all, -1);
|
|
|
|
show_negated_gitcomp(original_opts, show_all, nr_noopts);
|
2018-02-09 11:01:40 +00:00
|
|
|
fputc('\n', stdout);
|
2018-12-11 15:35:01 +00:00
|
|
|
return PARSE_OPT_COMPLETE;
|
2018-02-09 11:01:40 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 10:05:25 +00:00
|
|
|
/*
|
|
|
|
* Scan and may produce a new option[] array, which should be used
|
|
|
|
* instead of the original 'options'.
|
|
|
|
*
|
2019-11-05 17:07:23 +00:00
|
|
|
* Right now this is only used to preprocess and substitute
|
2019-04-29 10:05:25 +00:00
|
|
|
* OPTION_ALIAS.
|
2021-03-21 16:58:36 +00:00
|
|
|
*
|
|
|
|
* The returned options should be freed using free_preprocessed_options.
|
2019-04-29 10:05:25 +00:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
|
2023-01-01 21:16:48 +00:00
|
|
|
DUP_ARRAY(newopt, options, nr + 1);
|
2019-04-29 10:05:25 +00:00
|
|
|
|
|
|
|
/* 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;
|
2020-03-16 20:22:54 +00:00
|
|
|
struct strbuf help = STRBUF_INIT;
|
2019-04-29 10:05:25 +00:00
|
|
|
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");
|
2020-03-16 20:22:54 +00:00
|
|
|
strbuf_addf(&help, _("alias of --%s"), source);
|
2019-04-29 10:05:25 +00:00
|
|
|
|
|
|
|
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;
|
2020-03-16 20:22:54 +00:00
|
|
|
newopt[i].help = strbuf_detach(&help, NULL);
|
2021-03-21 16:58:36 +00:00
|
|
|
newopt[i].flags |= PARSE_OPT_FROM_ALIAS;
|
2019-04-29 10:05:25 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-03-21 16:58:36 +00: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);
|
|
|
|
}
|
|
|
|
|
2021-10-08 19:07:39 +00:00
|
|
|
static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *,
|
|
|
|
const char * const *,
|
|
|
|
const struct option *,
|
|
|
|
int, int);
|
2007-11-19 09:21:44 +00:00
|
|
|
|
2021-10-08 19:07:39 +00:00
|
|
|
enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
|
|
|
|
const struct option *options,
|
|
|
|
const char * const usagestr[])
|
2007-10-14 23:35:37 +00:00
|
|
|
{
|
2009-03-08 18:15:08 +00:00
|
|
|
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
|
|
|
|
|
2008-06-23 20:55:11 +00:00
|
|
|
/* we must reset ->opt, unknown short option leave it dangling */
|
|
|
|
ctx->opt = NULL;
|
|
|
|
|
2008-06-23 20:38:58 +00:00
|
|
|
for (; ctx->argc; ctx->argc--, ctx->argv++) {
|
|
|
|
const char *arg = ctx->argv[0];
|
2007-10-14 23:35:37 +00:00
|
|
|
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT &&
|
|
|
|
ctx->argc != ctx->total)
|
|
|
|
break;
|
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
if (*arg != '-' || !arg[1]) {
|
2009-05-07 19:45:42 +00:00
|
|
|
if (parse_nodash_opt(ctx, arg, options) == 0)
|
|
|
|
continue;
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
if (!ctx->has_subcommands) {
|
|
|
|
if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
|
|
|
|
return PARSE_OPT_NON_OPTION;
|
|
|
|
ctx->out[ctx->cpidx++] = ctx->argv[0];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (parse_subcommand(arg, options)) {
|
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
|
|
|
return PARSE_OPT_SUBCOMMAND;
|
|
|
|
case PARSE_OPT_UNKNOWN:
|
|
|
|
if (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)
|
|
|
|
/*
|
|
|
|
* arg is neither a short or long
|
|
|
|
* option nor a subcommand. Since
|
|
|
|
* this command has a default
|
|
|
|
* operation mode, we have to treat
|
|
|
|
* this arg and all remaining args
|
|
|
|
* as args meant to that default
|
|
|
|
* operation mode.
|
|
|
|
* So we are done parsing.
|
|
|
|
*/
|
|
|
|
return PARSE_OPT_DONE;
|
|
|
|
error(_("unknown subcommand: `%s'"), arg);
|
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
case PARSE_OPT_ERROR:
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
case PARSE_OPT_NON_OPTION:
|
|
|
|
/* Impossible. */
|
|
|
|
BUG("parse_subcommand() cannot return these");
|
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
2015-11-17 10:25:38 +00:00
|
|
|
/* lone -h asks for help */
|
|
|
|
if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h"))
|
|
|
|
goto show_usage;
|
|
|
|
|
2020-08-19 23:06:08 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
2018-02-09 11:01:40 +00:00
|
|
|
|
2007-10-14 23:35:37 +00:00
|
|
|
if (arg[1] != '-') {
|
2008-06-23 20:38:58 +00:00
|
|
|
ctx->opt = arg + 1;
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2012-03-03 11:00:29 +00:00
|
|
|
if (ctx->opt)
|
|
|
|
check_typos(arg + 1, options);
|
2015-11-17 10:25:38 +00:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_NON_OPTION:
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
BUG("parse_short_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2008-06-23 20:38:58 +00:00
|
|
|
if (ctx->opt)
|
2008-01-26 11:26:57 +00:00
|
|
|
check_typos(arg + 1, options);
|
2008-06-23 20:38:58 +00:00
|
|
|
while (ctx->opt) {
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2015-11-17 10:25:38 +00:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
|
|
|
|
2008-06-23 20:55:11 +00:00
|
|
|
/* 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] = '-';
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_NON_OPTION:
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
BUG("parse_short_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2008-01-26 11:26:57 +00:00
|
|
|
}
|
2007-10-14 23:35:37 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-08-06 14:40:16 +00:00
|
|
|
if (!arg[2] /* "--" */ ||
|
|
|
|
!strcmp(arg + 2, "end-of-options")) {
|
2008-06-23 20:38:58 +00:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-03-08 18:15:08 +00:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help-all"))
|
2010-06-12 12:57:39 +00:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 1, 0);
|
2009-03-08 18:15:08 +00:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help"))
|
2015-11-17 10:25:14 +00:00
|
|
|
goto show_usage;
|
2008-06-23 20:46:36 +00:00
|
|
|
switch (parse_long_opt(ctx, arg + 2, options)) {
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-22 18:43:51 +00:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2009-03-08 18:12:47 +00:00
|
|
|
goto unknown;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_HELP:
|
2018-03-22 18:43:51 +00:00
|
|
|
goto show_usage;
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_NON_OPTION:
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 00:35:27 +00:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
BUG("parse_long_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
2009-03-08 18:12:47 +00:00
|
|
|
continue;
|
|
|
|
unknown:
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT)
|
|
|
|
break;
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
if (ctx->has_subcommands &&
|
|
|
|
(ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) &&
|
|
|
|
(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) {
|
|
|
|
/*
|
|
|
|
* Found an unknown option given to a command with
|
|
|
|
* subcommands that has a default operation mode:
|
|
|
|
* we treat this option and all remaining args as
|
|
|
|
* arguments meant to that default operation mode.
|
|
|
|
* So we are done parsing.
|
|
|
|
*/
|
|
|
|
return PARSE_OPT_DONE;
|
|
|
|
}
|
2022-08-19 16:03:57 +00:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT))
|
2009-03-08 18:12:47 +00:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
|
|
|
ctx->out[ctx->cpidx++] = ctx->argv[0];
|
|
|
|
ctx->opt = NULL;
|
2008-06-23 20:38:58 +00:00
|
|
|
}
|
|
|
|
return PARSE_OPT_DONE;
|
2015-11-17 10:25:14 +00:00
|
|
|
|
|
|
|
show_usage:
|
2018-03-22 18:43:51 +00:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 0, 0);
|
2008-06-23 20:38:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int parse_options_end(struct parse_opt_ctx_t *ctx)
|
|
|
|
{
|
2019-01-27 00:35:23 +00:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT)
|
|
|
|
return ctx->total - ctx->argc;
|
|
|
|
|
2018-01-22 17:50:09 +00:00
|
|
|
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
|
2008-06-23 20:38:58 +00:00
|
|
|
ctx->out[ctx->cpidx + ctx->argc] = NULL;
|
|
|
|
return ctx->cpidx + ctx->argc;
|
|
|
|
}
|
|
|
|
|
2021-11-09 11:04:43 +00:00
|
|
|
int parse_options(int argc, const char **argv,
|
|
|
|
const char *prefix,
|
|
|
|
const struct option *options,
|
|
|
|
const char * const usagestr[],
|
|
|
|
enum parse_opt_flags flags)
|
2008-06-23 20:38:58 +00:00
|
|
|
{
|
|
|
|
struct parse_opt_ctx_t ctx;
|
2019-04-29 10:05:25 +00:00
|
|
|
struct option *real_options;
|
2008-06-23 20:38:58 +00:00
|
|
|
|
tests: disallow the use of abbreviated options (by default)
Git's command-line parsers support uniquely abbreviated options, e.g.
`git init --ba` would automatically expand `--ba` to `--bare`.
This is a very convenient feature in every day life for Git users, in
particular when tab completion is not available.
However, it is not a good idea to rely on that in Git's test suite, as
something that is a unique abbreviation of a command line option today
might no longer be a unique abbreviation tomorrow.
For example, if a future contribution added a new mode
`git init --babyproofing` and a previously-introduced test case used the
fact that `git init --ba` expanded to `git init --bare`, that future
contribution would now have to touch seemingly unrelated tests just to
keep the test suite from failing.
So let's disallow abbreviated options in the test suite by default.
Note: for ease of implementation, this patch really only touches the
`parse-options` machinery: more and more hand-rolled option parsers are
converted to use that internal API, and more and more scripts are
converted to built-ins (naturally using the parse-options API, too), so
in practice this catches most issues, and is definitely the biggest bang
for the buck.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-04-12 09:37:24 +00:00
|
|
|
disallow_abbreviated_options =
|
|
|
|
git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0);
|
|
|
|
|
2019-04-29 10:05:25 +00:00
|
|
|
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);
|
2008-06-23 20:38:58 +00:00
|
|
|
switch (parse_options_step(&ctx, options, usagestr)) {
|
|
|
|
case PARSE_OPT_HELP:
|
2018-03-22 18:43:51 +00:00
|
|
|
case PARSE_OPT_ERROR:
|
2008-06-23 20:38:58 +00:00
|
|
|
exit(129);
|
2018-12-11 15:35:01 +00:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
exit(0);
|
2010-12-01 23:32:55 +00:00
|
|
|
case PARSE_OPT_NON_OPTION:
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
|
|
|
break;
|
2008-06-23 20:38:58 +00:00
|
|
|
case PARSE_OPT_DONE:
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
if (ctx.has_subcommands &&
|
|
|
|
!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
|
|
|
|
error(_("need a subcommand"));
|
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
}
|
2008-06-23 20:38:58 +00:00
|
|
|
break;
|
2021-10-08 19:07:40 +00:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2008-06-23 20:46:36 +00:00
|
|
|
if (ctx.argv[0][1] == '-') {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown option `%s'"), ctx.argv[0] + 2);
|
2013-02-11 23:13:48 +00:00
|
|
|
} else if (isascii(*ctx.opt)) {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown switch `%c'"), *ctx.opt);
|
2013-02-11 23:13:48 +00:00
|
|
|
} else {
|
2018-11-10 05:16:13 +00:00
|
|
|
error(_("unknown non-ascii option in string: `%s'"),
|
2013-02-11 23:13:48 +00:00
|
|
|
ctx.argv[0]);
|
2008-06-23 20:46:36 +00:00
|
|
|
}
|
|
|
|
usage_with_options(usagestr, options);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
|
|
|
|
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-03 16:28:23 +00:00
|
|
|
precompose_argv_prefix(argc, argv, NULL);
|
2021-03-21 16:58:36 +00:00
|
|
|
free_preprocessed_options(real_options);
|
2019-04-29 10:05:25 +00:00
|
|
|
free(ctx.alias_groups);
|
2008-06-23 19:59:37 +00:00
|
|
|
return parse_options_end(&ctx);
|
2007-10-14 23:35:37 +00:00
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2010-05-17 15:34:41 +00:00
|
|
|
static int usage_argh(const struct option *opts, FILE *outfile)
|
2009-05-21 07:33:17 +00:00
|
|
|
{
|
|
|
|
const char *s;
|
2018-08-02 19:18:14 +00:00
|
|
|
int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!opts->argh || !!strpbrk(opts->argh, "()<>[]|");
|
2009-05-21 07:33:17 +00:00
|
|
|
if (opts->flags & PARSE_OPT_OPTARG)
|
|
|
|
if (opts->long_name)
|
|
|
|
s = literal ? "[=%s]" : "[=<%s>]";
|
|
|
|
else
|
|
|
|
s = literal ? "[%s]" : "[<%s>]";
|
|
|
|
else
|
|
|
|
s = literal ? " %s" : " <%s>";
|
2013-02-09 06:31:09 +00:00
|
|
|
return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
|
2009-05-21 07:33:17 +00:00
|
|
|
}
|
|
|
|
|
2007-10-14 23:38:30 +00:00
|
|
|
#define USAGE_OPTS_WIDTH 24
|
|
|
|
#define USAGE_GAP 2
|
|
|
|
|
2021-10-08 19:07:39 +00:00
|
|
|
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)
|
2007-10-14 23:38:30 +00:00
|
|
|
{
|
2010-05-17 15:34:41 +00:00
|
|
|
FILE *outfile = err ? stderr : stdout;
|
2017-09-25 04:08:05 +00:00
|
|
|
int need_newline;
|
2010-05-17 15:34:41 +00:00
|
|
|
|
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 13:30:11 +00: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;
|
|
|
|
|
2009-03-08 18:16:58 +00:00
|
|
|
if (!usagestr)
|
|
|
|
return PARSE_OPT_HELP;
|
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fprintf(outfile, "cat <<\\EOF\n");
|
|
|
|
|
2008-06-14 07:27:21 +00:00
|
|
|
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 13:30:11 +00: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;
|
2008-06-14 07:27:21 +00:00
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2017-09-25 04:08:05 +00:00
|
|
|
need_newline = 1;
|
2007-10-14 23:38:30 +00:00
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
size_t pos;
|
|
|
|
int pad;
|
|
|
|
|
parse-options: add support for parsing subcommands
Several Git commands have subcommands to implement mutually exclusive
"operation modes", and they usually parse their subcommand argument
with a bunch of if-else if statements.
Teach parse-options to handle subcommands as well, which will result
in shorter and simpler code with consistent error handling and error
messages on unknown or missing subcommand, and it will also make
possible for our Bash completion script to handle subcommands
programmatically.
The approach is guided by the following observations:
- Most subcommands [1] are implemented in dedicated functions, and
most of those functions [2] either have a signature matching the
'int cmd_foo(int argc, const char **argc, const char *prefix)'
signature of builtin commands or can be trivially converted to
that signature, because they miss only that last prefix parameter
or have no parameters at all.
- Subcommand arguments only have long form, and they have no double
dash prefix, no negated form, and no description, and they don't
take any arguments, and can't be abbreviated.
- There must be exactly one subcommand among the arguments, or zero
if the command has a default operation mode.
- All arguments following the subcommand are considered to be
arguments of the subcommand, and, conversely, arguments meant for
the subcommand may not preceed the subcommand.
So in the end subcommand declaration and parsing would look something
like this:
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"),
N_("the object directory to store the graph")),
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
return fn(argc, argv, prefix);
Here each OPT_SUBCOMMAND specifies the name of the subcommand and the
function implementing it, and the address of the same 'fn' subcommand
function pointer. parse_options() then processes the arguments until
it finds the first argument matching one of the subcommands, sets 'fn'
to the function associated with that subcommand, and returns, leaving
the rest of the arguments unprocessed. If none of the listed
subcommands is found among the arguments, parse_options() will show
usage and abort.
If a command has a default operation mode, 'fn' should be initialized
to the function implementing that mode, and parse_options() should be
invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case
parse_options() won't error out when not finding any subcommands, but
will return leaving 'fn' unchanged. Note that if that default
operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT
flag is necessary as well (otherwise parse_options() would error out
upon seeing the unknown option meant to the default operation mode).
Some thoughts about the implementation:
- The same pointer to 'fn' must be specified as 'value' for each
OPT_SUBCOMMAND, because there can be only one set of mutually
exclusive subcommands; parse_options() will BUG() otherwise.
There are other ways to tell parse_options() where to put the
function associated with the subcommand given on the command line,
but I didn't like them:
- Change parse_options()'s signature by adding a pointer to
subcommand function to be set to the function associated with
the given subcommand, affecting all callsites, even those that
don't have subcommands.
- Introduce a specific parse_options_and_subcommand() variant
with that extra funcion parameter.
- I decided against automatically calling the subcommand function
from within parse_options(), because:
- There are commands that have to perform additional actions
after option parsing but before calling the function
implementing the specified subcommand.
- The return code of the subcommand is usually the return code
of the git command, but preserving the return code of the
automatically called subcommand function would have made the
API awkward.
- Also add a OPT_SUBCOMMAND_F() variant to allow specifying an
option flag: we have two subcommands that are purposefully
excluded from completion ('git remote rm' and 'git stash save'),
so they'll have to be specified with the PARSE_OPT_NOCOMPLETE
flag.
- Some of the 'parse_opt_flags' don't make sense with subcommands,
and using them is probably just an oversight or misunderstanding.
Therefore parse_options() will BUG() when invoked with any of the
following flags while the options array contains at least one
OPT_SUBCOMMAND:
- PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing
arguments when encountering a "--" argument, so it doesn't
make sense to expect and keep one before a subcommand, because
it would prevent the parsing of the subcommand.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash
might be meaningful for the command's default operation mode,
e.g. to disambiguate refs and pathspecs.
- PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag
tells parse_options() to stop as soon as it encouners a
non-option argument, but subcommands are by definition not
options... so how could they be parsed, then?!
- PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any
unknown --options and then pass them to a different command or
subsystem. Surely if a command has subcommands, then this
functionality should rather be delegated to one of those
subcommands, and not performed by the command itself.
However, this flag is allowed in combination with the
PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass
--options to the default operation mode.
- If the command with subcommands has a default operation mode, then
all arguments to the command must preceed the arguments of the
subcommand.
AFAICT we don't have any commands where this makes a difference,
because in those commands either only the command accepts any
arguments ('notes' and 'remote'), or only the default subcommand
('reflog' and 'stash'), but never both.
- The 'argv' array passed to subcommand functions currently starts
with the name of the subcommand. Keep this behavior. AFAICT no
subcommand functions depend on the actual content of 'argv[0]',
but the parse_options() call handling their options expects that
the options start at argv[1].
- To support handling subcommands programmatically in our Bash
completion script, 'git cmd --git-completion-helper' will now list
both subcommands and regular --options, if any. This means that
the completion script will have to separate subcommands (i.e.
words without a double dash prefix) from --options on its own, but
that's rather easy to do, and it's not much work either, because
the number of subcommands a command might have is rather low, and
those commands accept only a single --option or none at all. An
alternative would be to introduce a separate option that lists
only subcommands, but then the completion script would need not
one but two git invocations and command substitutions for commands
with subcommands.
Note that this change doesn't affect the behavior of our Bash
completion script, because when completing the --option of a
command with subcommands, e.g. for 'git notes --<TAB>', then all
subcommands will be filtered out anyway, as none of them will
match the word to be completed starting with that double dash
prefix.
[1] Except 'git rerere', because many of its subcommands are
implemented in the bodies of the if-else if statements parsing the
command's subcommand argument.
[2] Except 'credential', 'credential-store' and 'fsmonitor--daemon',
because some of the functions implementing their subcommands take
special parameters.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-08-19 16:04:00 +00:00
|
|
|
if (opts->type == OPTION_SUBCOMMAND)
|
|
|
|
continue;
|
2007-10-14 23:38:30 +00:00
|
|
|
if (opts->type == OPTION_GROUP) {
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2017-09-25 04:08:05 +00:00
|
|
|
need_newline = 0;
|
2007-10-14 23:38:30 +00:00
|
|
|
if (*opts->help)
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf(outfile, "%s\n", _(opts->help));
|
2007-10-14 23:38:30 +00:00
|
|
|
continue;
|
|
|
|
}
|
2007-11-19 09:21:44 +00:00
|
|
|
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
|
|
|
|
continue;
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2017-09-25 04:08:05 +00:00
|
|
|
if (need_newline) {
|
|
|
|
fputc('\n', outfile);
|
|
|
|
need_newline = 0;
|
|
|
|
}
|
|
|
|
|
2010-05-17 15:34:41 +00:00
|
|
|
pos = fprintf(outfile, " ");
|
2012-02-28 19:06:09 +00:00
|
|
|
if (opts->short_name) {
|
2009-05-07 19:45:42 +00:00
|
|
|
if (opts->flags & PARSE_OPT_NODASH)
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, "%c", opts->short_name);
|
2009-05-07 19:45:42 +00:00
|
|
|
else
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, "-%c", opts->short_name);
|
2009-05-07 19:45:42 +00:00
|
|
|
}
|
2007-10-14 23:38:30 +00:00
|
|
|
if (opts->long_name && opts->short_name)
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += fprintf(outfile, ", ");
|
2007-10-14 23:38:30 +00:00
|
|
|
if (opts->long_name)
|
2012-02-28 19:06:09 +00:00
|
|
|
pos += fprintf(outfile, "--%s", opts->long_name);
|
2009-05-07 19:45:08 +00:00
|
|
|
if (opts->type == OPTION_NUMBER)
|
2013-02-09 06:31:09 +00:00
|
|
|
pos += utf8_fprintf(outfile, _("-NUM"));
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2010-12-01 23:31:36 +00:00
|
|
|
if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG))
|
2010-05-17 15:34:41 +00:00
|
|
|
pos += usage_argh(opts, outfile);
|
2007-10-14 23:38:30 +00:00
|
|
|
|
2007-10-13 22:10:51 +00:00
|
|
|
if (pos <= USAGE_OPTS_WIDTH)
|
|
|
|
pad = USAGE_OPTS_WIDTH - pos;
|
2007-10-14 23:38:30 +00:00
|
|
|
else {
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2007-10-14 23:38:30 +00:00
|
|
|
pad = USAGE_OPTS_WIDTH;
|
|
|
|
}
|
2019-04-29 10:05:25 +00:00
|
|
|
if (opts->type == OPTION_ALIAS) {
|
|
|
|
fprintf(outfile, "%*s", pad + USAGE_GAP, "");
|
|
|
|
fprintf_ln(outfile, _("alias of --%s"),
|
|
|
|
(const char *)opts->value);
|
|
|
|
continue;
|
|
|
|
}
|
2012-05-06 14:23:51 +00:00
|
|
|
fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help));
|
2007-10-14 23:38:30 +00:00
|
|
|
}
|
2010-05-17 15:34:41 +00:00
|
|
|
fputc('\n', outfile);
|
2007-10-13 22:10:51 +00:00
|
|
|
|
2010-06-12 12:57:39 +00:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fputs("EOF\n", outfile);
|
|
|
|
|
2008-06-23 20:28:04 +00:00
|
|
|
return PARSE_OPT_HELP;
|
2007-10-14 23:38:30 +00:00
|
|
|
}
|
2007-10-14 09:05:12 +00:00
|
|
|
|
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 07:51:05 +00:00
|
|
|
void NORETURN usage_with_options(const char * const *usagestr,
|
2008-06-23 20:38:58 +00:00
|
|
|
const struct option *opts)
|
2007-11-19 09:21:44 +00:00
|
|
|
{
|
2010-06-12 12:57:39 +00:00
|
|
|
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
|
2008-06-23 20:38:58 +00:00
|
|
|
exit(129);
|
2007-11-19 09:21:44 +00:00
|
|
|
}
|
|
|
|
|
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 07:51:05 +00:00
|
|
|
void NORETURN usage_msg_opt(const char *msg,
|
2009-02-02 05:12:58 +00:00
|
|
|
const char * const *usagestr,
|
|
|
|
const struct option *options)
|
|
|
|
{
|
2021-12-07 18:26:30 +00:00
|
|
|
die_message("%s\n", msg); /* The extra \n is intentional */
|
2009-02-02 05:12:58 +00:00
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
}
|
2021-12-28 13:28:43 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2022-02-25 23:47:35 +00:00
|
|
|
|
2022-01-31 22:07:46 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|