2007-10-15 07:35:37 +08:00
|
|
|
#include "git-compat-util.h"
|
|
|
|
#include "parse-options.h"
|
2023-03-21 14:25:58 +08:00
|
|
|
#include "abspath.h"
|
2023-09-30 05:20:51 +08:00
|
|
|
#include "parse.h"
|
2023-03-21 14:25:54 +08:00
|
|
|
#include "gettext.h"
|
2023-04-23 04:17:26 +08:00
|
|
|
#include "strbuf.h"
|
2023-09-30 05:20:51 +08:00
|
|
|
#include "string-list.h"
|
2013-02-09 14:31:09 +08:00
|
|
|
#include "utf8.h"
|
2007-10-15 07:35:37 +08: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 17:37:24 +08:00
|
|
|
static int disallow_abbreviated_options;
|
|
|
|
|
2021-10-09 03:07:46 +08:00
|
|
|
enum opt_parsed {
|
|
|
|
OPT_LONG = 0,
|
|
|
|
OPT_SHORT = 1<<0,
|
|
|
|
OPT_UNSET = 1<<1,
|
|
|
|
};
|
2007-10-15 07:35:37 +08:00
|
|
|
|
2022-06-02 20:25:34 +08:00
|
|
|
static void optbug(const struct option *opt, const char *reason)
|
2010-12-02 14:01:18 +08:00
|
|
|
{
|
2022-06-02 20:25:34 +08: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 14:01:18 +08:00
|
|
|
}
|
|
|
|
|
2021-10-09 03:07:46 +08:00
|
|
|
static const char *optname(const struct option *opt, enum opt_parsed flags)
|
2021-10-09 03:07:42 +08: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-09 03:07:46 +08:00
|
|
|
else if (flags == OPT_LONG)
|
2021-10-09 03:07:42 +08:00
|
|
|
strbuf_addf(&sb, "option `%s'", opt->long_name);
|
2021-10-09 03:07:46 +08:00
|
|
|
else
|
|
|
|
BUG("optname() got unknown flags %d", flags);
|
2021-10-09 03:07:42 +08:00
|
|
|
|
|
|
|
return sb.buf;
|
|
|
|
}
|
|
|
|
|
2019-01-27 08:35:27 +08:00
|
|
|
static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *opt,
|
2021-10-09 03:07:46 +08:00
|
|
|
enum opt_parsed flags, const char **arg)
|
2008-07-08 18:34:08 +08: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-22 02:30:36 +08:00
|
|
|
} else if (p->argc > 1) {
|
2008-07-08 18:34:08 +08:00
|
|
|
p->argc--;
|
|
|
|
*arg = *++p->argv;
|
|
|
|
} else
|
2018-11-10 13:16:11 +08:00
|
|
|
return error(_("%s requires a value"), optname(opt, flags));
|
2008-07-08 18:34:08 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-26 19:46:32 +08:00
|
|
|
static char *fix_filename(const char *prefix, const char *file)
|
2009-05-24 02:53:13 +08:00
|
|
|
{
|
parse-options: consistently allocate memory in fix_filename()
When handling OPT_FILENAME(), we have to stick the "prefix" (if any) in
front of the filename to make up for the fact that Git has chdir()'d to
the top of the repository. We can do this with prefix_filename(), but
there are a few special cases we handle ourselves.
Unfortunately the memory allocation is inconsistent here; if we do make
it to prefix_filename(), we'll allocate a string which the caller must
free to avoid a leak. But if we hit our special cases, we'll return the
string as-is, and a caller which tries to free it will crash. So there's
no way to win.
Let's consistently allocate, so that callers can do the right thing.
There are now three cases to care about in the function (and hence a
three-armed if/else):
1. we got a NULL input (and should leave it as NULL, though arguably
this is the sign of a bug; let's keep the status quo for now and we
can pick at that scab later)
2. we hit a special case that means we leave the name intact; we
should duplicate the string. This includes our special "-"
matching. Prior to this patch, it also included empty prefixes and
absolute filenames. But we can observe that prefix_filename()
already handles these, so we don't need to detect them.
3. everything else goes to prefix_filename()
I've dropped the "const" from the "char **file" parameter to indicate
that we're allocating, though in practice it's not really important.
This is all being shuffled through a void pointer via opt->value before
it hits code which ever looks at the string. And it's even a bit weird,
because we are really taking _in_ a const string and using the same
out-parameter for a non-const string. A better function signature would
be:
static char *fix_filename(const char *prefix, const char *file);
but that would mean the caller dereferences the double-pointer (and the
NULL check is currently handled inside this function). So I took the
path of least-change here.
Note that we have to fix several callers in this commit, too, or we'll
break the leak-checking tests. These are "new" leaks in the sense that
they are now triggered by the test suite, but these spots have always
been leaky when Git is run in a subdirectory of the repository. I fixed
all of the cases that trigger with GIT_TEST_PASSING_SANITIZE_LEAK. There
may be others in scripts that have other leaks, but we can fix them
later along with those other leaks (and again, you _couldn't_ fix them
before this patch, so this is the necessary first step).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-03-04 18:31:22 +08:00
|
|
|
if (!file || !*file)
|
2024-09-26 19:46:32 +08:00
|
|
|
return NULL;
|
parse-options: consistently allocate memory in fix_filename()
When handling OPT_FILENAME(), we have to stick the "prefix" (if any) in
front of the filename to make up for the fact that Git has chdir()'d to
the top of the repository. We can do this with prefix_filename(), but
there are a few special cases we handle ourselves.
Unfortunately the memory allocation is inconsistent here; if we do make
it to prefix_filename(), we'll allocate a string which the caller must
free to avoid a leak. But if we hit our special cases, we'll return the
string as-is, and a caller which tries to free it will crash. So there's
no way to win.
Let's consistently allocate, so that callers can do the right thing.
There are now three cases to care about in the function (and hence a
three-armed if/else):
1. we got a NULL input (and should leave it as NULL, though arguably
this is the sign of a bug; let's keep the status quo for now and we
can pick at that scab later)
2. we hit a special case that means we leave the name intact; we
should duplicate the string. This includes our special "-"
matching. Prior to this patch, it also included empty prefixes and
absolute filenames. But we can observe that prefix_filename()
already handles these, so we don't need to detect them.
3. everything else goes to prefix_filename()
I've dropped the "const" from the "char **file" parameter to indicate
that we're allocating, though in practice it's not really important.
This is all being shuffled through a void pointer via opt->value before
it hits code which ever looks at the string. And it's even a bit weird,
because we are really taking _in_ a const string and using the same
out-parameter for a non-const string. A better function signature would
be:
static char *fix_filename(const char *prefix, const char *file);
but that would mean the caller dereferences the double-pointer (and the
NULL check is currently handled inside this function). So I took the
path of least-change here.
Note that we have to fix several callers in this commit, too, or we'll
break the leak-checking tests. These are "new" leaks in the sense that
they are now triggered by the test suite, but these spots have always
been leaky when Git is run in a subdirectory of the repository. I fixed
all of the cases that trigger with GIT_TEST_PASSING_SANITIZE_LEAK. There
may be others in scripts that have other leaks, but we can fix them
later along with those other leaks (and again, you _couldn't_ fix them
before this patch, so this is the necessary first step).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-03-04 18:31:22 +08:00
|
|
|
else
|
2024-09-26 19:46:32 +08:00
|
|
|
return prefix_filename_except_for_dash(prefix, file);
|
2009-05-24 02:53:13 +08:00
|
|
|
}
|
|
|
|
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *opt,
|
|
|
|
enum opt_parsed flags,
|
|
|
|
const char **argp)
|
2007-10-15 07:35:37 +08:00
|
|
|
{
|
2007-10-15 07:45:45 +08:00
|
|
|
const char *s, *arg;
|
2007-11-07 18:20:27 +08:00
|
|
|
const int unset = flags & OPT_UNSET;
|
2009-05-24 02:53:13 +08:00
|
|
|
int err;
|
2007-10-15 07:35:37 +08:00
|
|
|
|
2007-11-07 18:20:27 +08:00
|
|
|
if (unset && p->opt)
|
2018-11-10 13:16:11 +08:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 18:20:27 +08:00
|
|
|
if (unset && (opt->flags & PARSE_OPT_NONEG))
|
2018-11-10 13:16:11 +08:00
|
|
|
return error(_("%s isn't available"), optname(opt, flags));
|
2010-12-02 07:30:40 +08:00
|
|
|
if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG))
|
2018-11-10 13:16:11 +08:00
|
|
|
return error(_("%s takes no value"), optname(opt, flags));
|
2007-11-07 18:20:27 +08: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-02 07:32:16 +08:00
|
|
|
case OPTION_LOWLEVEL_CALLBACK:
|
2019-01-27 08:35:28 +08: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-02 07:32:16 +08:00
|
|
|
|
2007-11-07 18:20:27 +08:00
|
|
|
case OPTION_BIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
2007-10-15 07:35:37 +08:00
|
|
|
else
|
2007-11-07 18:20:27 +08:00
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2009-05-08 03:44:17 +08:00
|
|
|
case OPTION_NEGBIT:
|
|
|
|
if (unset)
|
|
|
|
*(int *)opt->value |= opt->defval;
|
|
|
|
else
|
|
|
|
*(int *)opt->value &= ~opt->defval;
|
|
|
|
return 0;
|
|
|
|
|
2019-01-27 08:35:25 +08: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-28 07:56:49 +08:00
|
|
|
case OPTION_COUNTUP:
|
parse-options.c: make OPTION_COUNTUP respect "unspecified" values
OPT_COUNTUP() merely increments the counter upon --option, and resets it
to 0 upon --no-option, which means that there is no "unspecified" value
with which a client can initialize the counter to determine whether or
not --[no]-option was seen at all.
Make OPT_COUNTUP() treat any negative number as an "unspecified" value
to address this shortcoming. In particular, if a client initializes the
counter to -1, then if it is still -1 after parse_options(), then
neither --option nor --no-option was seen; if it is 0, then --no-option
was seen last, and if it is 1 or greater, than --option was seen last.
This change does not affect the behavior of existing clients because
they all use the initial value of 0 (or more).
Note that builtin/clean.c initializes the variable used with
OPT__FORCE (which uses OPT_COUNTUP()) to a negative value, but it is set
to either 0 or 1 by reading the configuration before the code calls
parse_options(), i.e. as far as parse_options() is concerned, the
initial value of the variable is not negative.
To test this behavior, in test-parse-options.c, "verbose" is set to
"unspecified" while quiet is set to 0 which will test the new behavior
with all sets of values.
Helped-by: Jeff King <peff@peff.net>
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Pranit Bauva <pranit.bauva@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-05-05 17:50:00 +08:00
|
|
|
if (*(int *)opt->value < 0)
|
|
|
|
*(int *)opt->value = 0;
|
2007-11-07 18:20:27 +08: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-15 07:35:37 +08:00
|
|
|
case OPTION_STRING:
|
2008-07-08 18:34:08 +08:00
|
|
|
if (unset)
|
2007-11-07 18:20:27 +08:00
|
|
|
*(const char **)opt->value = NULL;
|
2008-07-08 18:34:08 +08:00
|
|
|
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
2007-10-15 07:45:45 +08:00
|
|
|
*(const char **)opt->value = (const char *)opt->defval;
|
2008-07-08 18:34:08 +08:00
|
|
|
else
|
|
|
|
return get_arg(p, opt, flags, (const char **)opt->value);
|
2007-10-15 07:35:37 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-05-24 02:53:13 +08:00
|
|
|
case OPTION_FILENAME:
|
2024-09-26 19:46:32 +08:00
|
|
|
{
|
|
|
|
const char *value;
|
|
|
|
|
|
|
|
FREE_AND_NULL(*(char **)opt->value);
|
|
|
|
|
2009-05-24 02:53:13 +08:00
|
|
|
err = 0;
|
2024-09-26 19:46:32 +08:00
|
|
|
|
2009-05-24 02:53:13 +08:00
|
|
|
if (unset)
|
2024-09-26 19:46:32 +08:00
|
|
|
value = NULL;
|
2009-05-24 02:53:13 +08:00
|
|
|
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
|
2024-09-26 19:46:32 +08:00
|
|
|
value = (const char *) opt->defval;
|
2009-05-24 02:53:13 +08:00
|
|
|
else
|
2024-09-26 19:46:32 +08:00
|
|
|
err = get_arg(p, opt, flags, &value);
|
2009-05-24 02:53:13 +08:00
|
|
|
|
|
|
|
if (!err)
|
2024-09-26 19:46:32 +08:00
|
|
|
*(char **)opt->value = fix_filename(p->prefix, value);
|
2009-05-24 02:53:13 +08:00
|
|
|
return err;
|
2024-09-26 19:46:32 +08:00
|
|
|
}
|
2007-10-15 07:45:45 +08:00
|
|
|
case OPTION_CALLBACK:
|
2019-01-27 08:35:28 +08:00
|
|
|
{
|
|
|
|
const char *p_arg = NULL;
|
|
|
|
int p_unset;
|
|
|
|
|
2007-11-07 18:20:27 +08:00
|
|
|
if (unset)
|
2019-01-27 08:35:28 +08: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 18:34:08 +08:00
|
|
|
return -1;
|
2019-01-27 08:35:28 +08:00
|
|
|
else {
|
|
|
|
p_unset = 0;
|
|
|
|
p_arg = arg;
|
|
|
|
}
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
if (opt->flags & PARSE_OPT_CMDMODE)
|
|
|
|
*argp = p_arg;
|
2019-01-27 08:35:28 +08:00
|
|
|
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-15 07:35:37 +08:00
|
|
|
case OPTION_INTEGER:
|
2007-11-07 18:20:27 +08:00
|
|
|
if (unset) {
|
2007-10-15 07:35:37 +08:00
|
|
|
*(int *)opt->value = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2007-12-21 18:41:41 +08:00
|
|
|
if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
|
2007-10-15 07:45:45 +08:00
|
|
|
*(int *)opt->value = opt->defval;
|
|
|
|
return 0;
|
|
|
|
}
|
2008-07-08 18:34:08 +08:00
|
|
|
if (get_arg(p, opt, flags, &arg))
|
|
|
|
return -1;
|
2019-05-29 17:11:16 +08:00
|
|
|
if (!*arg)
|
|
|
|
return error(_("%s expects a numerical value"),
|
|
|
|
optname(opt, flags));
|
2008-07-08 18:34:08 +08:00
|
|
|
*(int *)opt->value = strtol(arg, (char **)&s, 10);
|
2007-10-15 07:35:37 +08:00
|
|
|
if (*s)
|
2018-11-10 13:16:11 +08:00
|
|
|
return error(_("%s expects a numerical value"),
|
|
|
|
optname(opt, flags));
|
2007-10-15 07:35:37 +08:00
|
|
|
return 0;
|
|
|
|
|
2015-06-22 02:25:44 +08: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 13:16:11 +08:00
|
|
|
return error(_("%s expects a non-negative integer value"
|
|
|
|
" with an optional k/m/g suffix"),
|
|
|
|
optname(opt, flags));
|
2015-06-22 02:25:44 +08:00
|
|
|
return 0;
|
|
|
|
|
2007-10-15 07:35:37 +08:00
|
|
|
default:
|
2018-11-10 13:16:12 +08:00
|
|
|
BUG("opt->type %d should not happen", opt->type);
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
struct parse_opt_cmdmode_list {
|
|
|
|
int value, *value_ptr;
|
|
|
|
const struct option *opt;
|
|
|
|
const char *arg;
|
|
|
|
enum opt_parsed flags;
|
|
|
|
struct parse_opt_cmdmode_list *next;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void build_cmdmode_list(struct parse_opt_ctx_t *ctx,
|
|
|
|
const struct option *opts)
|
|
|
|
{
|
|
|
|
ctx->cmdmode_list = NULL;
|
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
struct parse_opt_cmdmode_list *elem = ctx->cmdmode_list;
|
|
|
|
int *value_ptr = opts->value;
|
|
|
|
|
|
|
|
if (!(opts->flags & PARSE_OPT_CMDMODE) || !value_ptr)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
while (elem && elem->value_ptr != value_ptr)
|
|
|
|
elem = elem->next;
|
|
|
|
if (elem)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
CALLOC_ARRAY(elem, 1);
|
|
|
|
elem->value_ptr = value_ptr;
|
|
|
|
elem->value = *value_ptr;
|
|
|
|
elem->next = ctx->cmdmode_list;
|
|
|
|
ctx->cmdmode_list = elem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *optnamearg(const struct option *opt, const char *arg,
|
|
|
|
enum opt_parsed flags)
|
|
|
|
{
|
|
|
|
if (flags & OPT_SHORT)
|
|
|
|
return xstrfmt("-%c%s", opt->short_name, arg ? arg : "");
|
|
|
|
return xstrfmt("--%s%s%s%s", flags & OPT_UNSET ? "no-" : "",
|
|
|
|
opt->long_name, arg ? "=" : "", arg ? arg : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *opt,
|
|
|
|
enum opt_parsed flags)
|
|
|
|
{
|
|
|
|
const char *arg = NULL;
|
|
|
|
enum parse_opt_result result = do_get_value(p, opt, flags, &arg);
|
|
|
|
struct parse_opt_cmdmode_list *elem = p->cmdmode_list;
|
|
|
|
char *opt_name, *other_opt_name;
|
|
|
|
|
|
|
|
for (; elem; elem = elem->next) {
|
|
|
|
if (*elem->value_ptr == elem->value)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (elem->opt &&
|
|
|
|
(elem->opt->flags | opt->flags) & PARSE_OPT_CMDMODE)
|
|
|
|
break;
|
|
|
|
|
|
|
|
elem->opt = opt;
|
|
|
|
elem->arg = arg;
|
|
|
|
elem->flags = flags;
|
|
|
|
elem->value = *elem->value_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result || !elem)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
opt_name = optnamearg(opt, arg, flags);
|
|
|
|
other_opt_name = optnamearg(elem->opt, elem->arg, elem->flags);
|
2023-11-26 19:57:43 +08:00
|
|
|
error(_("options '%s' and '%s' cannot be used together"),
|
|
|
|
opt_name, other_opt_name);
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
free(opt_name);
|
|
|
|
free(other_opt_name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-01-27 08:35:27 +08:00
|
|
|
static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *options)
|
2007-10-15 07:35:37 +08:00
|
|
|
{
|
2009-05-08 03:45:08 +08:00
|
|
|
const struct option *numopt = NULL;
|
|
|
|
|
2007-10-15 07:35:37 +08:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (options->short_name == *p->opt) {
|
|
|
|
p->opt = p->opt[1] ? p->opt + 1 : NULL;
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
return get_value(p, options, OPT_SHORT);
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
2009-05-08 03:45:08 +08: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 08:35:28 +08:00
|
|
|
if (numopt->callback)
|
|
|
|
rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
|
|
|
|
else
|
|
|
|
rc = (*numopt->ll_callback)(p, numopt, arg, 0);
|
2009-05-08 03:45:08 +08:00
|
|
|
free(arg);
|
|
|
|
return rc;
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
2019-01-27 08:35:27 +08:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
|
2019-04-29 18:05:25 +08: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;
|
|
|
|
}
|
|
|
|
|
2024-03-03 20:19:40 +08:00
|
|
|
struct parsed_option {
|
|
|
|
const struct option *option;
|
|
|
|
enum opt_parsed flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void register_abbrev(struct parse_opt_ctx_t *p,
|
|
|
|
const struct option *option, enum opt_parsed flags,
|
|
|
|
struct parsed_option *abbrev,
|
|
|
|
struct parsed_option *ambiguous)
|
|
|
|
{
|
|
|
|
if (p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
|
|
|
|
return;
|
|
|
|
if (abbrev->option &&
|
2024-03-03 20:19:41 +08:00
|
|
|
!(abbrev->flags == flags && is_alias(p, abbrev->option, option))) {
|
2024-03-03 20:19:40 +08: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;
|
|
|
|
}
|
|
|
|
abbrev->option = option;
|
|
|
|
abbrev->flags = flags;
|
|
|
|
}
|
|
|
|
|
2019-01-27 08:35:27 +08:00
|
|
|
static enum parse_opt_result parse_long_opt(
|
|
|
|
struct parse_opt_ctx_t *p, const char *arg,
|
|
|
|
const struct option *options)
|
2007-10-15 07:35:37 +08:00
|
|
|
{
|
2014-03-08 14:48:31 +08:00
|
|
|
const char *arg_end = strchrnul(arg, '=');
|
2024-03-03 20:19:42 +08:00
|
|
|
const char *arg_start = arg;
|
|
|
|
enum opt_parsed flags = OPT_LONG;
|
|
|
|
int arg_starts_with_no_no = 0;
|
2024-03-03 20:19:40 +08:00
|
|
|
struct parsed_option abbrev = { .option = NULL, .flags = OPT_LONG };
|
|
|
|
struct parsed_option ambiguous = { .option = NULL, .flags = OPT_LONG };
|
2007-10-15 00:54:06 +08:00
|
|
|
|
2024-03-03 20:19:42 +08:00
|
|
|
if (skip_prefix(arg_start, "no-", &arg_start)) {
|
|
|
|
if (skip_prefix(arg_start, "no-", &arg_start))
|
|
|
|
arg_starts_with_no_no = 1;
|
|
|
|
else
|
|
|
|
flags |= OPT_UNSET;
|
|
|
|
}
|
|
|
|
|
2007-10-15 07:35:37 +08:00
|
|
|
for (; options->type != OPTION_END; options++) {
|
2012-02-26 03:14:54 +08:00
|
|
|
const char *rest, *long_name = options->long_name;
|
2024-03-03 20:19:42 +08:00
|
|
|
enum opt_parsed opt_flags = OPT_LONG;
|
|
|
|
int allow_unset = !(options->flags & PARSE_OPT_NONEG);
|
2007-10-15 07:35:37 +08: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-20 00:04:00 +08:00
|
|
|
if (options->type == OPTION_SUBCOMMAND)
|
|
|
|
continue;
|
2012-02-26 03:14:54 +08:00
|
|
|
if (!long_name)
|
2007-10-15 07:35:37 +08:00
|
|
|
continue;
|
|
|
|
|
2024-03-03 20:19:42 +08:00
|
|
|
if (skip_prefix(long_name, "no-", &long_name))
|
2024-01-22 01:56:39 +08:00
|
|
|
opt_flags |= OPT_UNSET;
|
2024-03-03 20:19:42 +08:00
|
|
|
else if (arg_starts_with_no_no)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (((flags ^ opt_flags) & OPT_UNSET) && !allow_unset)
|
|
|
|
continue;
|
2024-01-22 01:56:39 +08:00
|
|
|
|
2024-03-03 20:19:43 +08:00
|
|
|
if (skip_prefix(arg_start, long_name, &rest)) {
|
|
|
|
if (*rest == '=')
|
|
|
|
p->opt = rest + 1;
|
|
|
|
else if (*rest)
|
2009-09-26 02:44:44 +08:00
|
|
|
continue;
|
2024-03-03 20:19:43 +08:00
|
|
|
return get_value(p, options, flags ^ opt_flags);
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
2024-03-03 20:19:43 +08:00
|
|
|
|
|
|
|
/* abbreviated? */
|
|
|
|
if (!strncmp(long_name, arg_start, arg_end - arg_start))
|
|
|
|
register_abbrev(p, options, flags ^ opt_flags,
|
|
|
|
&abbrev, &ambiguous);
|
|
|
|
|
|
|
|
/* negated and abbreviated very much? */
|
|
|
|
if (allow_unset && starts_with("no-", arg))
|
|
|
|
register_abbrev(p, options, OPT_UNSET ^ opt_flags,
|
|
|
|
&abbrev, &ambiguous);
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
2007-11-05 21:15:21 +08:00
|
|
|
|
2024-03-03 20:19:40 +08:00
|
|
|
if (disallow_abbreviated_options && (ambiguous.option || abbrev.option))
|
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 17:37:24 +08:00
|
|
|
die("disallowed abbreviated or ambiguous option '%.*s'",
|
|
|
|
(int)(arg_end - arg), arg);
|
|
|
|
|
2024-03-03 20:19:40 +08:00
|
|
|
if (ambiguous.option) {
|
2018-11-10 13:16:13 +08:00
|
|
|
error(_("ambiguous option: %s "
|
|
|
|
"(could be --%s%s or --%s%s)"),
|
2007-11-05 21:15:21 +08:00
|
|
|
arg,
|
2024-03-03 20:19:40 +08:00
|
|
|
(ambiguous.flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
ambiguous.option->long_name,
|
|
|
|
(abbrev.flags & OPT_UNSET) ? "no-" : "",
|
|
|
|
abbrev.option->long_name);
|
2019-01-27 08:35:27 +08:00
|
|
|
return PARSE_OPT_HELP;
|
2018-03-23 02:43:51 +08:00
|
|
|
}
|
2024-03-03 20:19:40 +08:00
|
|
|
if (abbrev.option) {
|
2024-03-03 20:19:39 +08:00
|
|
|
if (*arg_end)
|
|
|
|
p->opt = arg_end + 1;
|
2024-03-03 20:19:40 +08:00
|
|
|
return get_value(p, abbrev.option, abbrev.flags);
|
2024-03-03 20:19:39 +08:00
|
|
|
}
|
2019-01-27 08:35:27 +08:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
|
2021-11-10 09:27:04 +08:00
|
|
|
static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
|
|
|
|
const char *arg,
|
|
|
|
const struct option *options)
|
2009-05-08 03:45:42 +08:00
|
|
|
{
|
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!(options->flags & PARSE_OPT_NODASH))
|
|
|
|
continue;
|
|
|
|
if (options->short_name == arg[0] && arg[1] == '\0')
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
return get_value(p, options, OPT_SHORT);
|
2009-05-08 03:45:42 +08:00
|
|
|
}
|
2021-11-10 09:27:04 +08:00
|
|
|
return PARSE_OPT_ERROR;
|
2009-05-08 03:45:42 +08: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-20 00:04:00 +08: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 18:42:18 +08:00
|
|
|
static void check_typos(const char *arg, const struct option *options)
|
2008-01-26 19:26:57 +08:00
|
|
|
{
|
|
|
|
if (strlen(arg) < 3)
|
|
|
|
return;
|
|
|
|
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(arg, "no-")) {
|
2020-02-05 21:07:23 +08:00
|
|
|
error(_("did you mean `--%s` (with two dashes)?"), arg);
|
2008-01-26 19:26:57 +08:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; options->type != OPTION_END; options++) {
|
|
|
|
if (!options->long_name)
|
|
|
|
continue;
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(options->long_name, arg)) {
|
2020-02-05 21:07:23 +08:00
|
|
|
error(_("did you mean `--%s` (with two dashes)?"), arg);
|
2008-01-26 19:26:57 +08:00
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-09 16:23:44 +08:00
|
|
|
static void parse_options_check(const struct option *opts)
|
|
|
|
{
|
2014-09-04 03:42:37 +08: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-20 00:04:00 +08:00
|
|
|
void *subcommand_value = NULL;
|
2009-06-09 16:23:44 +08:00
|
|
|
|
2014-09-04 03:42:37 +08:00
|
|
|
memset(short_opts, '\0', sizeof(short_opts));
|
2009-06-09 16:23:44 +08:00
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
|
2010-12-02 14:01:18 +08:00
|
|
|
(opts->flags & PARSE_OPT_OPTARG))
|
2022-06-02 20:25:34 +08:00
|
|
|
optbug(opts, "uses incompatible flags "
|
|
|
|
"LASTARG_DEFAULT and OPTARG");
|
2014-09-04 03:42:37 +08:00
|
|
|
if (opts->short_name) {
|
|
|
|
if (0x7F <= opts->short_name)
|
2022-06-02 20:25:34 +08:00
|
|
|
optbug(opts, "invalid short name");
|
2014-09-04 03:42:37 +08:00
|
|
|
else if (short_opts[opts->short_name]++)
|
2022-06-02 20:25:34 +08:00
|
|
|
optbug(opts, "short name already used");
|
2014-09-04 03:42:37 +08:00
|
|
|
}
|
2010-12-02 14:05:05 +08: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 20:25:34 +08:00
|
|
|
optbug(opts, "uses feature "
|
|
|
|
"not supported for dashless options");
|
parse-options: disallow negating OPTION_SET_INT 0
An option of type OPTION_SET_INT can be defined to set its variable to
zero. It's negated variant will do the same, though, which is
confusing. Several such options were fixed by disabling negation,
changing the value to set or using a different option type:
991c552916 (ls-tree: fix --no-full-name, 2023-07-18)
e12cb98e1e (branch: reject "--no-all" and "--no-remotes" early, 2023-07-18)
68cbb20e73 (show-branch: reject --[no-](topo|date)-order, 2023-07-19)
3821eb6c3d (reset: reject --no-(mixed|soft|hard|merge|keep) option, 2023-07-19)
36f76d2a25 (pack-objects: fix --no-quiet, 2023-07-21)
3a5f308741 (pack-objects: fix --no-keep-true-parents, 2023-07-21)
c95ae3ff9c (describe: fix --no-exact-match, 2023-07-21)
d089a06421 (bundle: use OPT_PASSTHRU_ARGV, 2023-07-29)
Check for such options that allow negation in parse_options_check() and
report them to find future cases quicker.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-08-09 04:05:57 +08:00
|
|
|
if (opts->type == OPTION_SET_INT && !opts->defval &&
|
|
|
|
opts->long_name && !(opts->flags & PARSE_OPT_NONEG))
|
|
|
|
optbug(opts, "OPTION_SET_INT 0 should not be negatable");
|
2010-12-02 14:08:57 +08: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-28 07:56:49 +08:00
|
|
|
case OPTION_COUNTUP:
|
2010-12-02 14:08:57 +08: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 20:25:34 +08:00
|
|
|
optbug(opts, "should not accept an argument");
|
2019-01-27 08:35:26 +08:00
|
|
|
break;
|
|
|
|
case OPTION_CALLBACK:
|
2019-01-27 08:35:28 +08:00
|
|
|
if (!opts->callback && !opts->ll_callback)
|
2022-06-02 20:25:35 +08: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 08:35:26 +08:00
|
|
|
break;
|
|
|
|
case OPTION_LOWLEVEL_CALLBACK:
|
|
|
|
if (!opts->ll_callback)
|
2022-06-02 20:25:35 +08:00
|
|
|
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback");
|
2019-01-27 08:35:26 +08:00
|
|
|
if (opts->callback)
|
2022-06-02 20:25:35 +08:00
|
|
|
optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback");
|
2019-01-27 08:35:26 +08:00
|
|
|
break;
|
2019-04-29 18:05:25 +08:00
|
|
|
case OPTION_ALIAS:
|
2022-06-02 20:25:35 +08: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-20 00:04:00 +08: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 14:08:57 +08:00
|
|
|
default:
|
|
|
|
; /* ok. (usually accepts an argument) */
|
|
|
|
}
|
2014-03-24 07:04:36 +08:00
|
|
|
if (opts->argh &&
|
|
|
|
strcspn(opts->argh, " _") != strlen(opts->argh))
|
2022-06-02 20:25:34 +08:00
|
|
|
optbug(opts, "multi-word argh should use dash to separate words");
|
2009-06-09 16:23:44 +08:00
|
|
|
}
|
2022-06-02 20:25:34 +08:00
|
|
|
BUG_if_bug("invalid 'struct option'");
|
2009-06-09 16:23:44 +08: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-20 00:04:00 +08: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 18:05:25 +08:00
|
|
|
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
|
|
|
|
int argc, const char **argv, const char *prefix,
|
2021-10-09 03:07:38 +08:00
|
|
|
const struct option *options,
|
|
|
|
enum parse_opt_flags flags)
|
2008-06-24 03:59:37 +08:00
|
|
|
{
|
2019-01-27 08:35:23 +08: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-24 02:53:12 +08:00
|
|
|
ctx->prefix = prefix;
|
2008-06-24 06:31:31 +08:00
|
|
|
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
|
2008-06-24 03:59:37 +08: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-20 00:04:00 +08: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-20 00:03:57 +08:00
|
|
|
if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
|
2019-01-27 08:35:23 +08:00
|
|
|
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
|
|
|
|
!(flags & PARSE_OPT_ONE_SHOT))
|
2018-11-10 13:16:12 +08:00
|
|
|
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
|
2019-01-27 08:35:23 +08: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 15:57:42 +08:00
|
|
|
parse_options_check(options);
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
build_cmdmode_list(ctx, options);
|
2008-06-24 03:59:37 +08:00
|
|
|
}
|
|
|
|
|
2019-04-29 18:05:25 +08:00
|
|
|
void parse_options_start(struct parse_opt_ctx_t *ctx,
|
|
|
|
int argc, const char **argv, const char *prefix,
|
2021-10-09 03:07:38 +08:00
|
|
|
const struct option *options,
|
|
|
|
enum parse_opt_flags flags)
|
2019-04-29 18:05:25 +08:00
|
|
|
{
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
parse_options_start_1(ctx, argc, argv, prefix, options, flags);
|
|
|
|
}
|
|
|
|
|
2020-08-20 07:06:08 +08:00
|
|
|
static void show_negated_gitcomp(const struct option *opts, int show_all,
|
|
|
|
int nr_noopts)
|
2018-06-06 17:41:39 +08:00
|
|
|
{
|
|
|
|
int printed_dashdash = 0;
|
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
int has_unset_form = 0;
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
2020-08-20 07:06:08 +08:00
|
|
|
if (!show_all &&
|
|
|
|
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)))
|
2018-06-06 17:41:39 +08:00
|
|
|
continue;
|
|
|
|
if (opts->flags & PARSE_OPT_NONEG)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch (opts->type) {
|
|
|
|
case OPTION_STRING:
|
|
|
|
case OPTION_FILENAME:
|
|
|
|
case OPTION_INTEGER:
|
|
|
|
case OPTION_MAGNITUDE:
|
|
|
|
case OPTION_CALLBACK:
|
|
|
|
case OPTION_BIT:
|
|
|
|
case OPTION_NEGBIT:
|
|
|
|
case OPTION_COUNTUP:
|
|
|
|
case OPTION_SET_INT:
|
|
|
|
has_unset_form = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!has_unset_form)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (skip_prefix(opts->long_name, "no-", &name)) {
|
|
|
|
if (nr_noopts < 0)
|
|
|
|
printf(" --%s", name);
|
|
|
|
} else if (nr_noopts >= 0) {
|
|
|
|
if (nr_noopts && !printed_dashdash) {
|
|
|
|
printf(" --");
|
|
|
|
printed_dashdash = 1;
|
|
|
|
}
|
|
|
|
printf(" --no-%s", opts->long_name);
|
|
|
|
nr_noopts++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-20 07:06:08 +08:00
|
|
|
static int show_gitcomp(const struct option *opts, int show_all)
|
2018-02-09 19:01:40 +08:00
|
|
|
{
|
2018-06-06 17:41:39 +08:00
|
|
|
const struct option *original_opts = opts;
|
|
|
|
int nr_noopts = 0;
|
|
|
|
|
2018-02-09 19:01:40 +08: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-20 00:04:00 +08:00
|
|
|
const char *prefix = "--";
|
2018-02-09 19:01:40 +08:00
|
|
|
const char *suffix = "";
|
|
|
|
|
|
|
|
if (!opts->long_name)
|
|
|
|
continue;
|
2020-08-20 07:06:08 +08: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 09:55:43 +08:00
|
|
|
(opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE | PARSE_OPT_FROM_ALIAS)))
|
2018-02-09 19:01:40 +08: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-20 00:04:00 +08:00
|
|
|
case OPTION_SUBCOMMAND:
|
|
|
|
prefix = "";
|
|
|
|
break;
|
2018-02-09 19:01:40 +08: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 19:02:12 +08:00
|
|
|
if (opts->flags & PARSE_OPT_COMP_ARG)
|
|
|
|
suffix = "=";
|
2018-06-06 17:41:39 +08: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-20 00:04:00 +08:00
|
|
|
printf("%s%s%s%s", opts == original_opts ? "" : " ",
|
|
|
|
prefix, opts->long_name, suffix);
|
2018-02-09 19:01:40 +08:00
|
|
|
}
|
2020-08-20 07:06:08 +08:00
|
|
|
show_negated_gitcomp(original_opts, show_all, -1);
|
|
|
|
show_negated_gitcomp(original_opts, show_all, nr_noopts);
|
2018-02-09 19:01:40 +08:00
|
|
|
fputc('\n', stdout);
|
2018-12-11 23:35:01 +08:00
|
|
|
return PARSE_OPT_COMPLETE;
|
2018-02-09 19:01:40 +08:00
|
|
|
}
|
|
|
|
|
2019-04-29 18:05:25 +08:00
|
|
|
/*
|
|
|
|
* Scan and may produce a new option[] array, which should be used
|
|
|
|
* instead of the original 'options'.
|
|
|
|
*
|
2019-11-06 01:07:23 +08:00
|
|
|
* Right now this is only used to preprocess and substitute
|
2019-04-29 18:05:25 +08:00
|
|
|
* OPTION_ALIAS.
|
2021-03-22 00:58:36 +08:00
|
|
|
*
|
|
|
|
* The returned options should be freed using free_preprocessed_options.
|
2019-04-29 18:05:25 +08: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-02 05:16:48 +08:00
|
|
|
DUP_ARRAY(newopt, options, nr + 1);
|
2019-04-29 18:05:25 +08: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-17 04:22:54 +08:00
|
|
|
struct strbuf help = STRBUF_INIT;
|
2019-04-29 18:05:25 +08: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-17 04:22:54 +08:00
|
|
|
strbuf_addf(&help, _("alias of --%s"), source);
|
2019-04-29 18:05:25 +08: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-17 04:22:54 +08:00
|
|
|
newopt[i].help = strbuf_detach(&help, NULL);
|
2021-03-22 00:58:36 +08:00
|
|
|
newopt[i].flags |= PARSE_OPT_FROM_ALIAS;
|
2019-04-29 18:05:25 +08: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-22 00:58:36 +08:00
|
|
|
static void free_preprocessed_options(struct option *options)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!options)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; options[i].type != OPTION_END; i++) {
|
|
|
|
if (options[i].flags & PARSE_OPT_FROM_ALIAS)
|
|
|
|
free((void *)options[i].help);
|
|
|
|
}
|
|
|
|
free(options);
|
|
|
|
}
|
|
|
|
|
2021-10-09 03:07:39 +08: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 17:21:44 +08:00
|
|
|
|
2021-10-09 03:07:39 +08:00
|
|
|
enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
|
|
|
|
const struct option *options,
|
|
|
|
const char * const usagestr[])
|
2007-10-15 07:35:37 +08:00
|
|
|
{
|
2009-03-09 02:15:08 +08:00
|
|
|
int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
|
|
|
|
|
2008-06-24 04:55:11 +08:00
|
|
|
/* we must reset ->opt, unknown short option leave it dangling */
|
|
|
|
ctx->opt = NULL;
|
|
|
|
|
2008-06-24 04:38:58 +08:00
|
|
|
for (; ctx->argc; ctx->argc--, ctx->argv++) {
|
|
|
|
const char *arg = ctx->argv[0];
|
2007-10-15 07:35:37 +08:00
|
|
|
|
2019-01-27 08:35:23 +08:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT &&
|
|
|
|
ctx->argc != ctx->total)
|
|
|
|
break;
|
|
|
|
|
2007-10-15 07:35:37 +08:00
|
|
|
if (*arg != '-' || !arg[1]) {
|
2009-05-08 03:45:42 +08: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-20 00:04:00 +08: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-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
|
2015-11-17 18:25:38 +08:00
|
|
|
/* lone -h asks for help */
|
|
|
|
if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h"))
|
|
|
|
goto show_usage;
|
|
|
|
|
2020-08-20 07:06:08 +08: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 19:01:40 +08:00
|
|
|
|
2007-10-15 07:35:37 +08:00
|
|
|
if (arg[1] != '-') {
|
2008-06-24 04:38:58 +08:00
|
|
|
ctx->opt = arg + 1;
|
2008-06-24 04:46:36 +08:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-23 02:43:51 +08:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2012-03-03 19:00:29 +08:00
|
|
|
if (ctx->opt)
|
|
|
|
check_typos(arg + 1, options);
|
2015-11-17 18:25:38 +08:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
2009-03-09 02:12:47 +08:00
|
|
|
goto unknown;
|
2019-01-27 08:35:27 +08: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-20 00:04:00 +08:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
BUG("parse_short_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-24 04:46:36 +08:00
|
|
|
}
|
2008-06-24 04:38:58 +08:00
|
|
|
if (ctx->opt)
|
2008-01-26 19:26:57 +08:00
|
|
|
check_typos(arg + 1, options);
|
2008-06-24 04:38:58 +08:00
|
|
|
while (ctx->opt) {
|
2008-06-24 04:46:36 +08:00
|
|
|
switch (parse_short_opt(ctx, options)) {
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-23 02:43:51 +08:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2015-11-17 18:25:38 +08:00
|
|
|
if (internal_help && *ctx->opt == 'h')
|
|
|
|
goto show_usage;
|
|
|
|
|
2008-06-24 04:55:11 +08: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-09 02:12:47 +08:00
|
|
|
goto unknown;
|
2019-01-27 08:35:27 +08: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-20 00:04:00 +08:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
case PARSE_OPT_HELP:
|
|
|
|
BUG("parse_short_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-24 04:46:36 +08:00
|
|
|
}
|
2008-01-26 19:26:57 +08:00
|
|
|
}
|
2007-10-15 07:35:37 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
parse-options: decouple "--end-of-options" and "--"
When we added generic end-of-options support in 51b4594b40
(parse-options: allow --end-of-options as a synonym for "--",
2019-08-06), we made them true synonyms. They both stop option parsing,
and they are both returned in the resulting argv if the KEEP_DASHDASH
flag is used.
The hope was that this would work for all callers:
- most generic callers would not pass KEEP_DASHDASH, and so would just
do the right thing (stop parsing there) without needing to know
anything more.
- callers with KEEP_DASHDASH were generally going to rely on
setup_revisions(), which knew to handle --end-of-options specially
But that turned out miss quite a few cases that pass KEEP_DASHDASH but
do their own manual parsing. For example, "git reset", "git checkout",
and so on want pass KEEP_DASHDASH so they can support:
git reset $revs -- $paths
but of course aren't going to actually do a traversal, so they don't
call setup_revisions(). And those cases currently get confused by
--end-of-options being left in place, like:
$ git reset --end-of-options HEAD
fatal: option '--end-of-options' must come before non-option arguments
We could teach each of these callers to handle the leftover option
explicitly. But let's try to be a bit more clever and see if we can
solve it centrally in parse-options.c.
The bogus assumption here is that KEEP_DASHDASH tells us the caller
wants to see --end-of-options in the result. But really, the callers
which need to know that --end-of-options was reached are those that may
potentially parse more options from argv. In other words, those that
pass the KEEP_UNKNOWN_OPT flag.
If such a caller is aware of --end-of-options (e.g., because they call
setup_revisions() with the result), then this will continue to do the
right thing, treating anything after --end-of-options as a non-option.
And if the caller is not aware of --end-of-options, they are better off
keeping it intact, because either:
1. They are just passing the options along to somebody else anyway, in
which case that somebody would need to know about the
--end-of-options marker.
2. They are going to parse the remainder themselves, at which point
choking on --end-of-options is much better than having it silently
removed. The point is to avoid option injection from untrusted
command line arguments, and bailing is better than quietly treating
the untrusted argument as an option.
This fixes bugs with --end-of-options across several commands, but I've
focused on two in particular here:
- t7102 confirms that "git reset --end-of-options --foo" now works.
This checks two things. One, that we no longer barf on
"--end-of-options" itself (which previously we did, even if the rev
was something vanilla like "HEAD" instead of "--foo"). And two, that
we correctly treat "--foo" as a revision rather than an option.
This fix applies to any other cases which pass KEEP_DASHDASH but not
KEEP_UNKNOWN_OPT, like "git checkout", "git check-attr", "git grep",
etc, which would previously choke on "--end-of-options".
- t9350 shows the opposite case: fast-export passed KEEP_UNKNOWN_OPT
but not KEEP_DASHDASH, but then passed the result on to
setup_revisions(). So it never saw --end-of-options, and would
erroneously parse "fast-export --end-of-options --foo" as having a
"--foo" option. This is now fixed.
Note that this does shut the door for callers which want to know if we
hit end-of-options, but don't otherwise need to keep unknown opts. The
obvious thing here is feeding it to the DWIM verify_filename()
machinery. And indeed, this is a problem even for commands which do
understand --end-of-options already. For example, without this patch,
you get:
$ git log --end-of-options --foo
fatal: option '--foo' must come before non-option arguments
because we refuse to accept "--foo" as a filename (because it starts
with a dash) even though we could know that we saw end-of-options. The
verify_filename() function simply doesn't accept this extra information.
So that is the status quo, and this patch doubles down further on that.
Commands like "git reset" have the same problem, but they won't even
know that parse-options saw --end-of-options! So even if we fixed
verify_filename(), they wouldn't have anything to pass to it.
But in practice I don't think this is a big deal. If you are being
careful enough to use --end-of-options, then you should also be using
"--" to disambiguate and avoid the DWIM behavior in the first place. In
other words, doing:
git log --end-of-options --this-is-a-rev -- --this-is-a-path
works correctly, and will continue to do so. And likewise, with this
patch now:
git reset --end-of-options --this-is-a-rev -- --this-is-a-path
will work, as well.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-12-07 06:21:45 +08:00
|
|
|
if (!arg[2] /* "--" */) {
|
2008-06-24 04:38:58 +08:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
break;
|
parse-options: decouple "--end-of-options" and "--"
When we added generic end-of-options support in 51b4594b40
(parse-options: allow --end-of-options as a synonym for "--",
2019-08-06), we made them true synonyms. They both stop option parsing,
and they are both returned in the resulting argv if the KEEP_DASHDASH
flag is used.
The hope was that this would work for all callers:
- most generic callers would not pass KEEP_DASHDASH, and so would just
do the right thing (stop parsing there) without needing to know
anything more.
- callers with KEEP_DASHDASH were generally going to rely on
setup_revisions(), which knew to handle --end-of-options specially
But that turned out miss quite a few cases that pass KEEP_DASHDASH but
do their own manual parsing. For example, "git reset", "git checkout",
and so on want pass KEEP_DASHDASH so they can support:
git reset $revs -- $paths
but of course aren't going to actually do a traversal, so they don't
call setup_revisions(). And those cases currently get confused by
--end-of-options being left in place, like:
$ git reset --end-of-options HEAD
fatal: option '--end-of-options' must come before non-option arguments
We could teach each of these callers to handle the leftover option
explicitly. But let's try to be a bit more clever and see if we can
solve it centrally in parse-options.c.
The bogus assumption here is that KEEP_DASHDASH tells us the caller
wants to see --end-of-options in the result. But really, the callers
which need to know that --end-of-options was reached are those that may
potentially parse more options from argv. In other words, those that
pass the KEEP_UNKNOWN_OPT flag.
If such a caller is aware of --end-of-options (e.g., because they call
setup_revisions() with the result), then this will continue to do the
right thing, treating anything after --end-of-options as a non-option.
And if the caller is not aware of --end-of-options, they are better off
keeping it intact, because either:
1. They are just passing the options along to somebody else anyway, in
which case that somebody would need to know about the
--end-of-options marker.
2. They are going to parse the remainder themselves, at which point
choking on --end-of-options is much better than having it silently
removed. The point is to avoid option injection from untrusted
command line arguments, and bailing is better than quietly treating
the untrusted argument as an option.
This fixes bugs with --end-of-options across several commands, but I've
focused on two in particular here:
- t7102 confirms that "git reset --end-of-options --foo" now works.
This checks two things. One, that we no longer barf on
"--end-of-options" itself (which previously we did, even if the rev
was something vanilla like "HEAD" instead of "--foo"). And two, that
we correctly treat "--foo" as a revision rather than an option.
This fix applies to any other cases which pass KEEP_DASHDASH but not
KEEP_UNKNOWN_OPT, like "git checkout", "git check-attr", "git grep",
etc, which would previously choke on "--end-of-options".
- t9350 shows the opposite case: fast-export passed KEEP_UNKNOWN_OPT
but not KEEP_DASHDASH, but then passed the result on to
setup_revisions(). So it never saw --end-of-options, and would
erroneously parse "fast-export --end-of-options --foo" as having a
"--foo" option. This is now fixed.
Note that this does shut the door for callers which want to know if we
hit end-of-options, but don't otherwise need to keep unknown opts. The
obvious thing here is feeding it to the DWIM verify_filename()
machinery. And indeed, this is a problem even for commands which do
understand --end-of-options already. For example, without this patch,
you get:
$ git log --end-of-options --foo
fatal: option '--foo' must come before non-option arguments
because we refuse to accept "--foo" as a filename (because it starts
with a dash) even though we could know that we saw end-of-options. The
verify_filename() function simply doesn't accept this extra information.
So that is the status quo, and this patch doubles down further on that.
Commands like "git reset" have the same problem, but they won't even
know that parse-options saw --end-of-options! So even if we fixed
verify_filename(), they wouldn't have anything to pass to it.
But in practice I don't think this is a big deal. If you are being
careful enough to use --end-of-options, then you should also be using
"--" to disambiguate and avoid the DWIM behavior in the first place. In
other words, doing:
git log --end-of-options --this-is-a-rev -- --this-is-a-path
works correctly, and will continue to do so. And likewise, with this
patch now:
git reset --end-of-options --this-is-a-rev -- --this-is-a-path
will work, as well.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-12-07 06:21:45 +08:00
|
|
|
} else if (!strcmp(arg + 2, "end-of-options")) {
|
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) {
|
|
|
|
ctx->argc--;
|
|
|
|
ctx->argv++;
|
|
|
|
}
|
|
|
|
break;
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
|
|
|
|
2009-03-09 02:15:08 +08:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help-all"))
|
2010-06-12 20:57:39 +08:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 1, 0);
|
2009-03-09 02:15:08 +08:00
|
|
|
if (internal_help && !strcmp(arg + 2, "help"))
|
2015-11-17 18:25:14 +08:00
|
|
|
goto show_usage;
|
2008-06-24 04:46:36 +08:00
|
|
|
switch (parse_long_opt(ctx, arg + 2, options)) {
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_ERROR:
|
2018-03-23 02:43:51 +08:00
|
|
|
return PARSE_OPT_ERROR;
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2009-03-09 02:12:47 +08:00
|
|
|
goto unknown;
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_HELP:
|
2018-03-23 02:43:51 +08:00
|
|
|
goto show_usage;
|
2019-01-27 08:35:27 +08: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-20 00:04:00 +08:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
2019-01-27 08:35:27 +08:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
BUG("parse_long_opt() cannot return these");
|
|
|
|
case PARSE_OPT_DONE:
|
|
|
|
break;
|
2008-06-24 04:46:36 +08:00
|
|
|
}
|
2009-03-09 02:12:47 +08:00
|
|
|
continue;
|
|
|
|
unknown:
|
2019-01-27 08:35:23 +08: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-20 00:04:00 +08: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-20 00:03:57 +08:00
|
|
|
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT))
|
2009-03-09 02:12:47 +08:00
|
|
|
return PARSE_OPT_UNKNOWN;
|
|
|
|
ctx->out[ctx->cpidx++] = ctx->argv[0];
|
|
|
|
ctx->opt = NULL;
|
2008-06-24 04:38:58 +08:00
|
|
|
}
|
|
|
|
return PARSE_OPT_DONE;
|
2015-11-17 18:25:14 +08:00
|
|
|
|
|
|
|
show_usage:
|
2018-03-23 02:43:51 +08:00
|
|
|
return usage_with_options_internal(ctx, usagestr, options, 0, 0);
|
2008-06-24 04:38:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int parse_options_end(struct parse_opt_ctx_t *ctx)
|
|
|
|
{
|
2019-01-27 08:35:23 +08:00
|
|
|
if (ctx->flags & PARSE_OPT_ONE_SHOT)
|
|
|
|
return ctx->total - ctx->argc;
|
|
|
|
|
2018-01-23 01:50:09 +08:00
|
|
|
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
|
2008-06-24 04:38:58 +08:00
|
|
|
ctx->out[ctx->cpidx + ctx->argc] = NULL;
|
|
|
|
return ctx->cpidx + ctx->argc;
|
|
|
|
}
|
|
|
|
|
2021-11-09 19:04:43 +08: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-24 04:38:58 +08:00
|
|
|
{
|
|
|
|
struct parse_opt_ctx_t ctx;
|
2019-04-29 18:05:25 +08:00
|
|
|
struct option *real_options;
|
2008-06-24 04:38:58 +08: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 17:37:24 +08:00
|
|
|
disallow_abbreviated_options =
|
|
|
|
git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0);
|
|
|
|
|
2019-04-29 18:05:25 +08: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-24 04:38:58 +08:00
|
|
|
switch (parse_options_step(&ctx, options, usagestr)) {
|
|
|
|
case PARSE_OPT_HELP:
|
2018-03-23 02:43:51 +08:00
|
|
|
case PARSE_OPT_ERROR:
|
2008-06-24 04:38:58 +08:00
|
|
|
exit(129);
|
2018-12-11 23:35:01 +08:00
|
|
|
case PARSE_OPT_COMPLETE:
|
|
|
|
exit(0);
|
2010-12-02 07:32:55 +08: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-20 00:04:00 +08:00
|
|
|
case PARSE_OPT_SUBCOMMAND:
|
|
|
|
break;
|
2008-06-24 04:38:58 +08: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-20 00:04:00 +08:00
|
|
|
if (ctx.has_subcommands &&
|
|
|
|
!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
|
|
|
|
error(_("need a subcommand"));
|
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
}
|
2008-06-24 04:38:58 +08:00
|
|
|
break;
|
2021-10-09 03:07:40 +08:00
|
|
|
case PARSE_OPT_UNKNOWN:
|
2008-06-24 04:46:36 +08:00
|
|
|
if (ctx.argv[0][1] == '-') {
|
2018-11-10 13:16:13 +08:00
|
|
|
error(_("unknown option `%s'"), ctx.argv[0] + 2);
|
2013-02-12 07:13:48 +08:00
|
|
|
} else if (isascii(*ctx.opt)) {
|
2018-11-10 13:16:13 +08:00
|
|
|
error(_("unknown switch `%c'"), *ctx.opt);
|
2013-02-12 07:13:48 +08:00
|
|
|
} else {
|
2018-11-10 13:16:13 +08:00
|
|
|
error(_("unknown non-ascii option in string: `%s'"),
|
2013-02-12 07:13:48 +08:00
|
|
|
ctx.argv[0]);
|
2008-06-24 04:46:36 +08:00
|
|
|
}
|
|
|
|
usage_with_options(usagestr, options);
|
2007-10-15 07:35:37 +08: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-04 00:28:23 +08:00
|
|
|
precompose_argv_prefix(argc, argv, NULL);
|
2021-03-22 00:58:36 +08:00
|
|
|
free_preprocessed_options(real_options);
|
2019-04-29 18:05:25 +08:00
|
|
|
free(ctx.alias_groups);
|
parse-options: make CMDMODE errors more precise
Only a single PARSE_OPT_CMDMODE option can be specified for the same
variable at the same time. This is enforced by get_value(), but the
error messages are imprecise in three ways:
1. If a non-PARSE_OPT_CMDMODE option changes the value variable of a
PARSE_OPT_CMDMODE option then an ominously vague message is shown:
$ t/helper/test-tool parse-options --set23 --mode1
error: option `mode1' : incompatible with something else
Worse: If the order of options is reversed then no error is reported at
all:
$ t/helper/test-tool parse-options --mode1 --set23
boolean: 0
integer: 23
magnitude: 0
timestamp: 0
string: (not set)
abbrev: 7
verbose: -1
quiet: 0
dry run: no
file: (not set)
Fortunately this can currently only happen in the test helper; actual
Git commands don't share the same variable for the value of options with
and without the flag PARSE_OPT_CMDMODE.
2. If there are multiple options with the same value (synonyms), then
the one that is defined first is shown rather than the one actually
given on the command line, which is confusing:
$ git am --resolved --quit
error: option `quit' is incompatible with --continue
3. Arguments of PARSE_OPT_CMDMODE options are not handled by the
parse-option machinery. This is left to the callback function. We
currently only have a single affected option, --show-current-patch of
git am. Errors for it can show an argument that was not actually given
on the command line:
$ git am --show-current-patch --show-current-patch=diff
error: options '--show-current-patch=diff' and '--show-current-patch=raw' cannot be used together
The options --show-current-patch and --show-current-patch=raw are
synonyms, but the error accuses the user of input they did not actually
made. Or it can awkwardly print a NULL pointer:
$ git am --show-current-patch=diff --show-current-patch
error: options '--show-current-patch=(null)' and '--show-current-patch=diff' cannot be used together
The reasons for these shortcomings is that the current code checks
incompatibility only when encountering a PARSE_OPT_CMDMODE option at the
command line, and that it searches the previous incompatible option by
value.
Fix the first two points by checking all PARSE_OPT_CMDMODE variables
after parsing each option and by storing all relevant details if their
value changed. Do that whether or not the changing options has the flag
PARSE_OPT_CMDMODE set. Report an incompatibility only if two options
change the variable to different values and at least one of them is a
PARSE_OPT_CMDMODE option. This changes the output of the first three
examples above to:
$ t/helper/test-tool parse-options --set23 --mode1
error: --mode1 is incompatible with --set23
$ t/helper/test-tool parse-options --mode1 --set23
error: --set23 is incompatible with --mode1
$ git am --resolved --quit
error: --quit is incompatible with --resolved
Store the argument of PARSE_OPT_CMDMODE options of type OPTION_CALLBACK
as well to allow taking over the responsibility for compatibility
checking from the callback function. The next patch will use this
capability to fix the messages for git am --show-current-patch.
Use a linked list for storing the PARSE_OPT_CMDMODE variables. This
somewhat outdated data structure is simple and suffices, as the number
of elements per command is currently only zero or one. We do support
multiple different command modes variables per command, but I don't
expect that we'd ever use a significant number of them. Once we do we
can switch to a hashmap.
Since we no longer need to search the conflicting option, the all_opts
parameter of get_value() is no longer used. Remove it.
Extend the tests to check for both conflicting option names, but don't
insist on a particular order.
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-10-28 19:53:01 +08:00
|
|
|
for (struct parse_opt_cmdmode_list *elem = ctx.cmdmode_list; elem;) {
|
|
|
|
struct parse_opt_cmdmode_list *next = elem->next;
|
|
|
|
free(elem);
|
|
|
|
elem = next;
|
|
|
|
}
|
2008-06-24 03:59:37 +08:00
|
|
|
return parse_options_end(&ctx);
|
2007-10-15 07:35:37 +08:00
|
|
|
}
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2010-05-17 23:34:41 +08:00
|
|
|
static int usage_argh(const struct option *opts, FILE *outfile)
|
2009-05-21 15:33:17 +08:00
|
|
|
{
|
|
|
|
const char *s;
|
2018-08-03 03:18:14 +08:00
|
|
|
int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!opts->argh || !!strpbrk(opts->argh, "()<>[]|");
|
2009-05-21 15:33:17 +08: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 14:31:09 +08:00
|
|
|
return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
|
2009-05-21 15:33:17 +08:00
|
|
|
}
|
|
|
|
|
2023-08-05 22:43:04 +08:00
|
|
|
static int usage_indent(FILE *outfile)
|
|
|
|
{
|
|
|
|
return fprintf(outfile, " ");
|
|
|
|
}
|
|
|
|
|
2023-08-05 22:52:42 +08:00
|
|
|
#define USAGE_OPTS_WIDTH 26
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2023-08-05 22:43:04 +08:00
|
|
|
static void usage_padding(FILE *outfile, size_t pos)
|
|
|
|
{
|
2023-08-05 22:52:42 +08:00
|
|
|
if (pos < USAGE_OPTS_WIDTH)
|
|
|
|
fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, "");
|
|
|
|
else
|
|
|
|
fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, "");
|
2023-08-05 22:43:04 +08:00
|
|
|
}
|
|
|
|
|
2023-08-05 22:44:45 +08:00
|
|
|
static const struct option *find_option_by_long_name(const struct option *opts,
|
|
|
|
const char *long_name)
|
|
|
|
{
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
if (opts->long_name && !strcmp(opts->long_name, long_name))
|
|
|
|
return opts;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2021-10-09 03:07:39 +08: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-15 07:38:30 +08:00
|
|
|
{
|
2023-08-05 22:44:45 +08:00
|
|
|
const struct option *all_opts = opts;
|
2010-05-17 23:34:41 +08:00
|
|
|
FILE *outfile = err ? stderr : stdout;
|
2017-09-25 12:08:05 +08:00
|
|
|
int need_newline;
|
2010-05-17 23:34:41 +08: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 21:30:11 +08:00
|
|
|
const char *usage_prefix = _("usage: %s");
|
|
|
|
/*
|
|
|
|
* The translation could be anything, but we can count on
|
|
|
|
* msgfmt(1)'s --check option to have asserted that "%s" is in
|
|
|
|
* the translation. So compute the length of the "usage: "
|
|
|
|
* part. We are assuming that the translator wasn't overly
|
|
|
|
* clever and used e.g. "%1$s" instead of "%s", there's only
|
|
|
|
* one "%s" in "usage_prefix" above, so there's no reason to
|
|
|
|
* do so even with a RTL language.
|
|
|
|
*/
|
|
|
|
size_t usage_len = strlen(usage_prefix) - strlen("%s");
|
|
|
|
/*
|
|
|
|
* TRANSLATORS: the colon here should align with the
|
|
|
|
* one in "usage: %s" translation.
|
|
|
|
*/
|
|
|
|
const char *or_prefix = _(" or: %s");
|
|
|
|
/*
|
|
|
|
* TRANSLATORS: You should only need to translate this format
|
|
|
|
* string if your language is a RTL language (e.g. Arabic,
|
|
|
|
* Hebrew etc.), not if it's a LTR language (e.g. German,
|
|
|
|
* Russian, Chinese etc.).
|
|
|
|
*
|
|
|
|
* When a translated usage string has an embedded "\n" it's
|
|
|
|
* because options have wrapped to the next line. The line
|
|
|
|
* after the "\n" will then be padded to align with the
|
|
|
|
* command name, such as N_("git cmd [opt]\n<8
|
|
|
|
* spaces>[opt2]"), where the 8 spaces are the same length as
|
|
|
|
* "git cmd ".
|
|
|
|
*
|
|
|
|
* This format string prints out that already-translated
|
|
|
|
* line. The "%*s" is whitespace padding to account for the
|
|
|
|
* padding at the start of the line that we add in this
|
|
|
|
* function. The "%s" is a line in the (hopefully already
|
|
|
|
* translated) N_() usage string, which contained embedded
|
|
|
|
* newlines before we split it up.
|
|
|
|
*/
|
|
|
|
const char *usage_continued = _("%*s%s");
|
|
|
|
const char *prefix = usage_prefix;
|
|
|
|
int saw_empty_line = 0;
|
|
|
|
|
2009-03-09 02:16:58 +08:00
|
|
|
if (!usagestr)
|
|
|
|
return PARSE_OPT_HELP;
|
|
|
|
|
2010-06-12 20:57:39 +08:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fprintf(outfile, "cat <<\\EOF\n");
|
|
|
|
|
2008-06-14 15:27:21 +08: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 21:30:11 +08:00
|
|
|
const char *str = _(*usagestr++);
|
|
|
|
struct string_list list = STRING_LIST_INIT_DUP;
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
if (!saw_empty_line && !*str)
|
|
|
|
saw_empty_line = 1;
|
|
|
|
|
|
|
|
string_list_split(&list, str, '\n', -1);
|
|
|
|
for (j = 0; j < list.nr; j++) {
|
|
|
|
const char *line = list.items[j].string;
|
|
|
|
|
|
|
|
if (saw_empty_line && *line)
|
|
|
|
fprintf_ln(outfile, _(" %s"), line);
|
|
|
|
else if (saw_empty_line)
|
|
|
|
fputc('\n', outfile);
|
|
|
|
else if (!j)
|
|
|
|
fprintf_ln(outfile, prefix, line);
|
|
|
|
else
|
|
|
|
fprintf_ln(outfile, usage_continued,
|
|
|
|
(int)usage_len, "", line);
|
|
|
|
}
|
|
|
|
string_list_clear(&list, 0);
|
|
|
|
|
|
|
|
prefix = or_prefix;
|
2008-06-14 15:27:21 +08:00
|
|
|
}
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2017-09-25 12:08:05 +08:00
|
|
|
need_newline = 1;
|
2007-10-15 07:38:30 +08:00
|
|
|
|
|
|
|
for (; opts->type != OPTION_END; opts++) {
|
|
|
|
size_t pos;
|
2023-07-19 06:54:04 +08:00
|
|
|
const char *cp, *np;
|
2023-08-05 22:44:45 +08:00
|
|
|
const char *positive_name = NULL;
|
2007-10-15 07:38:30 +08: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-20 00:04:00 +08:00
|
|
|
if (opts->type == OPTION_SUBCOMMAND)
|
|
|
|
continue;
|
2007-10-15 07:38:30 +08:00
|
|
|
if (opts->type == OPTION_GROUP) {
|
2010-05-17 23:34:41 +08:00
|
|
|
fputc('\n', outfile);
|
2017-09-25 12:08:05 +08:00
|
|
|
need_newline = 0;
|
2007-10-15 07:38:30 +08:00
|
|
|
if (*opts->help)
|
2012-05-06 22:23:51 +08:00
|
|
|
fprintf(outfile, "%s\n", _(opts->help));
|
2007-10-15 07:38:30 +08:00
|
|
|
continue;
|
|
|
|
}
|
2007-11-19 17:21:44 +08:00
|
|
|
if (!full && (opts->flags & PARSE_OPT_HIDDEN))
|
|
|
|
continue;
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2017-09-25 12:08:05 +08:00
|
|
|
if (need_newline) {
|
|
|
|
fputc('\n', outfile);
|
|
|
|
need_newline = 0;
|
|
|
|
}
|
|
|
|
|
2023-08-05 22:43:04 +08:00
|
|
|
pos = usage_indent(outfile);
|
2012-02-29 03:06:09 +08:00
|
|
|
if (opts->short_name) {
|
2009-05-08 03:45:42 +08:00
|
|
|
if (opts->flags & PARSE_OPT_NODASH)
|
2010-05-17 23:34:41 +08:00
|
|
|
pos += fprintf(outfile, "%c", opts->short_name);
|
2009-05-08 03:45:42 +08:00
|
|
|
else
|
2010-05-17 23:34:41 +08:00
|
|
|
pos += fprintf(outfile, "-%c", opts->short_name);
|
2009-05-08 03:45:42 +08:00
|
|
|
}
|
2007-10-15 07:38:30 +08:00
|
|
|
if (opts->long_name && opts->short_name)
|
2010-05-17 23:34:41 +08:00
|
|
|
pos += fprintf(outfile, ", ");
|
2023-08-05 22:40:59 +08:00
|
|
|
if (opts->long_name) {
|
|
|
|
const char *long_name = opts->long_name;
|
2023-08-05 22:44:45 +08:00
|
|
|
if ((opts->flags & PARSE_OPT_NONEG) ||
|
|
|
|
skip_prefix(long_name, "no-", &positive_name))
|
2023-08-05 22:40:59 +08:00
|
|
|
pos += fprintf(outfile, "--%s", long_name);
|
|
|
|
else
|
|
|
|
pos += fprintf(outfile, "--[no-]%s", long_name);
|
|
|
|
}
|
|
|
|
|
2009-05-08 03:45:08 +08:00
|
|
|
if (opts->type == OPTION_NUMBER)
|
2013-02-09 14:31:09 +08:00
|
|
|
pos += utf8_fprintf(outfile, _("-NUM"));
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2010-12-02 07:31:36 +08:00
|
|
|
if ((opts->flags & PARSE_OPT_LITERAL_ARGHELP) ||
|
|
|
|
!(opts->flags & PARSE_OPT_NOARG))
|
2010-05-17 23:34:41 +08:00
|
|
|
pos += usage_argh(opts, outfile);
|
2007-10-15 07:38:30 +08:00
|
|
|
|
2019-04-29 18:05:25 +08:00
|
|
|
if (opts->type == OPTION_ALIAS) {
|
2023-08-05 22:43:04 +08:00
|
|
|
usage_padding(outfile, pos);
|
2019-04-29 18:05:25 +08:00
|
|
|
fprintf_ln(outfile, _("alias of --%s"),
|
|
|
|
(const char *)opts->value);
|
|
|
|
continue;
|
|
|
|
}
|
2023-07-19 06:54:04 +08:00
|
|
|
|
2023-08-26 16:06:00 +08:00
|
|
|
for (cp = opts->help ? _(opts->help) : ""; *cp; cp = np) {
|
2023-07-19 06:54:04 +08:00
|
|
|
np = strchrnul(cp, '\n');
|
|
|
|
if (*np)
|
|
|
|
np++;
|
2023-08-26 16:06:00 +08:00
|
|
|
usage_padding(outfile, pos);
|
|
|
|
fwrite(cp, 1, np - cp, outfile);
|
2023-08-05 22:43:04 +08:00
|
|
|
pos = 0;
|
2023-07-19 06:54:04 +08:00
|
|
|
}
|
2023-08-26 16:06:00 +08:00
|
|
|
fputc('\n', outfile);
|
2023-08-05 22:44:45 +08:00
|
|
|
|
|
|
|
if (positive_name) {
|
|
|
|
if (find_option_by_long_name(all_opts, positive_name))
|
|
|
|
continue;
|
|
|
|
pos = usage_indent(outfile);
|
|
|
|
pos += fprintf(outfile, "--%s", positive_name);
|
|
|
|
usage_padding(outfile, pos);
|
|
|
|
fprintf_ln(outfile, _("opposite of --no-%s"),
|
|
|
|
positive_name);
|
2023-07-19 06:54:04 +08:00
|
|
|
}
|
2007-10-15 07:38:30 +08:00
|
|
|
}
|
2010-05-17 23:34:41 +08:00
|
|
|
fputc('\n', outfile);
|
2007-10-14 06:10:51 +08:00
|
|
|
|
2010-06-12 20:57:39 +08:00
|
|
|
if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
|
|
|
|
fputs("EOF\n", outfile);
|
|
|
|
|
2008-06-24 04:28:04 +08:00
|
|
|
return PARSE_OPT_HELP;
|
2007-10-15 07:38:30 +08:00
|
|
|
}
|
2007-10-14 17:05:12 +08: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 15:51:05 +08:00
|
|
|
void NORETURN usage_with_options(const char * const *usagestr,
|
2008-06-24 04:38:58 +08:00
|
|
|
const struct option *opts)
|
2007-11-19 17:21:44 +08:00
|
|
|
{
|
2010-06-12 20:57:39 +08:00
|
|
|
usage_with_options_internal(NULL, usagestr, opts, 0, 1);
|
2008-06-24 04:38:58 +08:00
|
|
|
exit(129);
|
2007-11-19 17:21:44 +08: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 15:51:05 +08:00
|
|
|
void NORETURN usage_msg_opt(const char *msg,
|
2009-02-02 13:12:58 +08:00
|
|
|
const char * const *usagestr,
|
|
|
|
const struct option *options)
|
|
|
|
{
|
2021-12-08 02:26:30 +08:00
|
|
|
die_message("%s\n", msg); /* The extra \n is intentional */
|
2009-02-02 13:12:58 +08:00
|
|
|
usage_with_options(usagestr, options);
|
|
|
|
}
|
2021-12-28 21:28:43 +08: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-26 07:47:35 +08:00
|
|
|
|
2022-02-01 06:07:46 +08: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;
|
|
|
|
}
|
|
|
|
}
|