Merge branch 'cc/early-scan-options' into seen
The process of parsing command-line options in commands that perform an early scan over their arguments (such as 'git bisect', 'git rev-parse', and 'git fast-import') has been unified using a new early-scan sub-API, which parses and skips known options taking separate values to prevent logic bugs. * cc/early-scan-options: fast-import: use early_scan_options() for --allow-unsafe-features parse-options: add early_scan_options() parse-options: extract lookup_long_opt() from parse_long_opt() parse-options: add parse_options_takes_argument()seen
commit
8fdf16a778
|
|
@ -66,13 +66,6 @@ Only enable this option if you trust the program generating the
|
|||
fast-import stream! This option is enabled automatically for
|
||||
remote-helpers that use the `import` capability, as they are
|
||||
already trusted to run their own code.
|
||||
+
|
||||
Note that this option has to be spelled in full, and has to appear
|
||||
before any option whose value is separated from it by a space, for
|
||||
the unsafe `feature` commands in the stream to be allowed. So
|
||||
`--allow-unsafe` or `--depth 5 --allow-unsafe-features` still refuse
|
||||
them, while `--allow-unsafe-features --depth 5` and
|
||||
`--depth=5 --allow-unsafe-features` allow them.
|
||||
|
||||
`--signed-tags=<mode>`::
|
||||
Specify how to handle signed tags. Behaves in the same way as
|
||||
|
|
|
|||
|
|
@ -197,6 +197,11 @@ are the bitwise-or of:
|
|||
Internal flag, set on options that were expanded from a
|
||||
configured alias. It should not be set by callers.
|
||||
|
||||
`PARSE_OPT_EARLY`::
|
||||
Report this option to `early_scan_options()`, which looks at a
|
||||
few options before parsing the command line for real. Ignored
|
||||
by `parse_options()` itself.
|
||||
|
||||
`PARSE_OPT_NOCOMPLETE`::
|
||||
Do not offer this option for completion.
|
||||
|
||||
|
|
|
|||
|
|
@ -4116,6 +4116,18 @@ static int option_parse_quiet(const struct option *opt UNUSED,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int option_parse_early_allow_unsafe(const struct option *option,
|
||||
const char *value UNUSED,
|
||||
bool unset, int pos UNUSED,
|
||||
void *data)
|
||||
{
|
||||
struct fast_import_state *state = data;
|
||||
|
||||
if (!strcmp(option->long_name, "allow-unsafe-features"))
|
||||
state->allow_unsafe_features = !unset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_fast_import(int argc,
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
|
|
@ -4180,7 +4192,7 @@ int cmd_fast_import(int argc,
|
|||
OPT_HIDDEN_GROUP(N_("Advanced")),
|
||||
OPT_BOOL_F(0, "allow-unsafe-features", &state.allow_unsafe_features,
|
||||
N_("allow unsafe mark commands from the stream"),
|
||||
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG),
|
||||
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG | PARSE_OPT_EARLY),
|
||||
OPT_CALLBACK_F(0, "export-pack-edges", &state, N_("file"),
|
||||
N_("dump edge commits to <file>"),
|
||||
PARSE_OPT_HIDDEN | PARSE_OPT_NONEG,
|
||||
|
|
@ -4214,23 +4226,14 @@ int cmd_fast_import(int argc,
|
|||
* line to override stream data). But we must do an early parse of any
|
||||
* command-line options that impact how we interpret the feature lines.
|
||||
*
|
||||
* NEEDSWORK: This scan only matches the exact "--allow-unsafe-features"
|
||||
* spelling and stops at the first argument that doesn't start with a
|
||||
* dash. As parse_options() below also accepts unambiguous abbreviations
|
||||
* and values separated by a space from their option, the two disagree
|
||||
* for command lines like "--allow-unsafe" or "--depth 5
|
||||
* --allow-unsafe-features": parse_options() accepts the option, but
|
||||
* this scan doesn't see it, so unsafe features from the stream are
|
||||
* still refused. This errs on the safe side, but should be fixed by
|
||||
* teaching this scan about the options that take a value.
|
||||
* The scan uses the same options as parse_options() below, so the
|
||||
* two agree on which options take a value and on abbreviations. In
|
||||
* case of doubt, the scan stops early, so unsafe features are then
|
||||
* refused rather than wrongly allowed.
|
||||
*/
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (*arg != '-' || !strcmp(arg, "--"))
|
||||
break;
|
||||
if (!strcmp(arg, "--allow-unsafe-features"))
|
||||
state.allow_unsafe_features = 1;
|
||||
}
|
||||
early_scan_options(argc - 1, argv + 1, fast_import_options,
|
||||
EARLY_SCAN_STOP_AT_NON_OPTION,
|
||||
option_parse_early_allow_unsafe, &state);
|
||||
|
||||
rc_free = mem_pool_alloc(&fi_mem_pool, cmd_save * sizeof(*rc_free));
|
||||
for (unsigned int i = 0; i < (cmd_save - 1); i++)
|
||||
|
|
|
|||
278
parse-options.c
278
parse-options.c
|
|
@ -516,16 +516,46 @@ static void register_abbrev(struct parse_opt_ctx_t *p,
|
|||
abbrev->flags = flags;
|
||||
}
|
||||
|
||||
static enum parse_opt_result parse_long_opt(
|
||||
struct parse_opt_ctx_t *p, const char *arg,
|
||||
const struct option *options)
|
||||
enum long_opt_lookup {
|
||||
LONG_OPT_EXACT, /* exact match found */
|
||||
LONG_OPT_ABBREV, /* unambiguous abbreviation found */
|
||||
LONG_OPT_AMBIGUOUS, /* more than one candidate abbreviation */
|
||||
LONG_OPT_UNKNOWN, /* nothing matches */
|
||||
};
|
||||
|
||||
/*
|
||||
* Look up the long option named by `arg` (without its leading "--")
|
||||
* without acting on it. (Neither prints nor dies.)
|
||||
*
|
||||
* Return value:
|
||||
* - On LONG_OPT_EXACT, `found` is the exact matching option and its flags
|
||||
* (including OPT_UNSET for a negated form), and `stuck` points after
|
||||
* the '=' or is NULL.
|
||||
* - On LONG_OPT_ABBREV, `found` is the unambiguous abbreviation, same as
|
||||
* above for its flags and `stuck`.
|
||||
* - On LONG_OPT_AMBIGUOUS, `found` and `other` are two candidates of an
|
||||
* ambiguous abbreviation.
|
||||
* - On LONG_OPT_UNKNOWN, nothing matches, no result.
|
||||
*
|
||||
* `other` is only meaningful for LONG_OPT_AMBIGUOUS.
|
||||
*/
|
||||
static enum long_opt_lookup lookup_long_opt(struct parse_opt_ctx_t *p,
|
||||
const char *arg,
|
||||
const struct option *options,
|
||||
struct parsed_option *found,
|
||||
struct parsed_option *other,
|
||||
const char **stuck)
|
||||
{
|
||||
enum opt_parsed flags = OPT_LONG;
|
||||
const char *arg_end = strchrnul(arg, '=');
|
||||
const char *arg_start = arg;
|
||||
enum opt_parsed flags = OPT_LONG;
|
||||
int arg_starts_with_no_no = 0;
|
||||
struct parsed_option abbrev = { .option = NULL, .flags = OPT_LONG };
|
||||
struct parsed_option ambiguous = { .option = NULL, .flags = OPT_LONG };
|
||||
|
||||
found->option = NULL;
|
||||
found->flags = OPT_LONG;
|
||||
other->option = NULL;
|
||||
other->flags = OPT_LONG;
|
||||
*stuck = NULL;
|
||||
|
||||
if (skip_prefix(arg_start, "no-", &arg_start)) {
|
||||
if (skip_prefix(arg_start, "no-", &arg_start))
|
||||
|
|
@ -554,43 +584,70 @@ static enum parse_opt_result parse_long_opt(
|
|||
|
||||
if (skip_prefix(arg_start, long_name, &rest)) {
|
||||
if (*rest == '=')
|
||||
p->opt = rest + 1;
|
||||
*stuck = rest + 1;
|
||||
else if (*rest)
|
||||
continue;
|
||||
return get_value(p, options, flags ^ opt_flags);
|
||||
found->option = options;
|
||||
found->flags = flags ^ opt_flags;
|
||||
return LONG_OPT_EXACT;
|
||||
}
|
||||
|
||||
/* abbreviated? */
|
||||
if (!strncmp(long_name, arg_start, arg_end - arg_start))
|
||||
register_abbrev(p, options, flags ^ opt_flags,
|
||||
&abbrev, &ambiguous);
|
||||
found, other);
|
||||
|
||||
/* negated and abbreviated very much? */
|
||||
if (allow_unset && starts_with("no-", arg))
|
||||
register_abbrev(p, options, OPT_UNSET ^ opt_flags,
|
||||
&abbrev, &ambiguous);
|
||||
found, other);
|
||||
}
|
||||
|
||||
if (disallow_abbreviated_options && (ambiguous.option || abbrev.option))
|
||||
if (other->option)
|
||||
return LONG_OPT_AMBIGUOUS;
|
||||
|
||||
if (found->option) {
|
||||
if (*arg_end)
|
||||
*stuck = arg_end + 1;
|
||||
return LONG_OPT_ABBREV;
|
||||
}
|
||||
|
||||
return LONG_OPT_UNKNOWN;
|
||||
}
|
||||
|
||||
static enum parse_opt_result parse_long_opt(struct parse_opt_ctx_t *p,
|
||||
const char *arg,
|
||||
const struct option *options)
|
||||
{
|
||||
struct parsed_option found;
|
||||
struct parsed_option other;
|
||||
const char *stuck;
|
||||
enum long_opt_lookup result = lookup_long_opt(p, arg, options,
|
||||
&found, &other, &stuck);
|
||||
|
||||
if (result == LONG_OPT_UNKNOWN)
|
||||
return PARSE_OPT_UNKNOWN;
|
||||
|
||||
if (disallow_abbreviated_options &&
|
||||
(result == LONG_OPT_ABBREV || result == LONG_OPT_AMBIGUOUS)) {
|
||||
const char *arg_end = strchrnul(arg, '=');
|
||||
die("disallowed abbreviated or ambiguous option '%.*s'",
|
||||
(int)(arg_end - arg), arg);
|
||||
}
|
||||
|
||||
if (ambiguous.option) {
|
||||
error(_("ambiguous option: %s "
|
||||
"(could be --%s%s or --%s%s)"),
|
||||
arg,
|
||||
(ambiguous.flags & OPT_UNSET) ? "no-" : "",
|
||||
ambiguous.option->long_name,
|
||||
(abbrev.flags & OPT_UNSET) ? "no-" : "",
|
||||
abbrev.option->long_name);
|
||||
if (result == LONG_OPT_AMBIGUOUS) {
|
||||
error(_("ambiguous option: %s (could be --%s%s or --%s%s)"), arg,
|
||||
(other.flags & OPT_UNSET) ? "no-" : "", other.option->long_name,
|
||||
(found.flags & OPT_UNSET) ? "no-" : "", found.option->long_name);
|
||||
return PARSE_OPT_HELP_ERROR;
|
||||
}
|
||||
if (abbrev.option) {
|
||||
if (*arg_end)
|
||||
p->opt = arg_end + 1;
|
||||
return get_value(p, abbrev.option, abbrev.flags);
|
||||
|
||||
if (result == LONG_OPT_ABBREV || result == LONG_OPT_EXACT) {
|
||||
p->opt = stuck;
|
||||
return get_value(p, found.option, found.flags);
|
||||
}
|
||||
return PARSE_OPT_UNKNOWN;
|
||||
|
||||
BUG("unknown enum long_opt_lookup value '%d'", result);
|
||||
}
|
||||
|
||||
static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
|
||||
|
|
@ -669,6 +726,8 @@ static void parse_options_check(const struct option *opts)
|
|||
opts->long_name))
|
||||
optbug(opts, "uses feature "
|
||||
"not supported for dashless options");
|
||||
if ((opts->flags & PARSE_OPT_EARLY) && !opts->long_name)
|
||||
optbug(opts, "uses PARSE_OPT_EARLY, which needs a long name");
|
||||
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");
|
||||
|
|
@ -706,6 +765,8 @@ static void parse_options_check(const struct option *opts)
|
|||
case OPTION_SUBCOMMAND:
|
||||
if (!opts->value || !opts->subcommand_fn)
|
||||
optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function");
|
||||
if (opts->flags & PARSE_OPT_EARLY)
|
||||
optbug(opts, "OPTION_SUBCOMMAND does not support PARSE_OPT_EARLY");
|
||||
if (!subcommand_value)
|
||||
subcommand_value = opts->value;
|
||||
else if (subcommand_value != opts->value)
|
||||
|
|
@ -841,6 +902,25 @@ static void show_negated_gitcomp(const struct option *opts, int show_all,
|
|||
}
|
||||
}
|
||||
|
||||
int parse_options_takes_argument(const struct option *opt)
|
||||
{
|
||||
switch (opt->type) {
|
||||
case OPTION_STRING:
|
||||
case OPTION_FILENAME:
|
||||
case OPTION_INTEGER:
|
||||
case OPTION_UNSIGNED:
|
||||
case OPTION_CALLBACK:
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (opt->flags & (PARSE_OPT_NOARG | PARSE_OPT_OPTARG))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int show_gitcomp(const struct option *opts, int show_all)
|
||||
{
|
||||
const struct option *original_opts = opts;
|
||||
|
|
@ -863,20 +943,10 @@ static int show_gitcomp(const struct option *opts, int show_all)
|
|||
break;
|
||||
case OPTION_GROUP:
|
||||
continue;
|
||||
case OPTION_STRING:
|
||||
case OPTION_FILENAME:
|
||||
case OPTION_INTEGER:
|
||||
case OPTION_UNSIGNED:
|
||||
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:
|
||||
if (parse_options_takes_argument(opts) &&
|
||||
!(opts->flags & PARSE_OPT_LASTARG_DEFAULT))
|
||||
suffix = "=";
|
||||
break;
|
||||
}
|
||||
if (opts->flags & PARSE_OPT_COMP_ARG)
|
||||
|
|
@ -1248,6 +1318,140 @@ int parse_options(int argc, const char **argv,
|
|||
return parse_options_end(&ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look for `name`, a long option without its leading "--", among
|
||||
* `option`, the way parse_options() would. Return true if it names an
|
||||
* option exactly, or as an unambiguous abbreviation that is not
|
||||
* disallowed, with `found` set to that option and its flags, and
|
||||
* `stuck` to the value stuck to it, if any, or to NULL.
|
||||
*/
|
||||
static bool find_early_scan_option(struct parse_opt_ctx_t *p,
|
||||
const char *name,
|
||||
const struct option *option,
|
||||
bool disallow_abbrev,
|
||||
struct parsed_option *found,
|
||||
const char **stuck)
|
||||
{
|
||||
struct parsed_option other;
|
||||
enum long_opt_lookup result = lookup_long_opt(p, name, option,
|
||||
found, &other, stuck);
|
||||
|
||||
switch (result) {
|
||||
case LONG_OPT_EXACT:
|
||||
return true;
|
||||
case LONG_OPT_ABBREV:
|
||||
return !disallow_abbrev;
|
||||
case LONG_OPT_AMBIGUOUS:
|
||||
case LONG_OPT_UNKNOWN:
|
||||
return false;
|
||||
}
|
||||
|
||||
BUG("unknown enum long_opt_lookup value '%d'", result);
|
||||
}
|
||||
|
||||
struct early_scan_state {
|
||||
struct parse_opt_ctx_t ctx;
|
||||
const struct option *option;
|
||||
enum early_scan_flags flags;
|
||||
early_scan_fn *fn;
|
||||
void *data;
|
||||
bool disallow_abbrev;
|
||||
};
|
||||
|
||||
/*
|
||||
* Look at argv[*i]. Return 'true' to go on scanning, with *i on the
|
||||
* last argument consumed. Return 'false' to stop, with *i set to the
|
||||
* first argument not consumed.
|
||||
*/
|
||||
static bool early_scan_one_option(struct early_scan_state *s,
|
||||
int argc, const char **argv, int *i)
|
||||
{
|
||||
const char *name;
|
||||
const char *value;
|
||||
struct parsed_option found;
|
||||
bool unset;
|
||||
int pos = *i;
|
||||
const char *arg = argv[pos];
|
||||
|
||||
/*
|
||||
* parse_options() always stops parsing options at these,
|
||||
* whatever its flags, so nothing after them is an option.
|
||||
*/
|
||||
if (!strcmp(arg, "--") || !strcmp(arg, "--end-of-options"))
|
||||
return false;
|
||||
|
||||
/* non-option */
|
||||
if (*arg != '-' || !arg[1])
|
||||
return !(s->flags & EARLY_SCAN_STOP_AT_NON_OPTION);
|
||||
|
||||
/* short option */
|
||||
if (!skip_prefix(arg, "--", &name))
|
||||
return false;
|
||||
|
||||
/* unknown, ambiguous or disallowed abbreviation */
|
||||
if (!find_early_scan_option(&s->ctx, name, s->option,
|
||||
s->disallow_abbrev, &found, &value))
|
||||
return false;
|
||||
|
||||
/* A low-level callback may consume any number of arguments. */
|
||||
if (found.option->ll_callback)
|
||||
return false;
|
||||
|
||||
unset = !!(found.flags & OPT_UNSET);
|
||||
|
||||
/* parse_options() rejects these, so don't go further. */
|
||||
if (value && (unset || (found.option->flags & PARSE_OPT_NOARG)))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Like get_arg(), take the next argument as the value when it is
|
||||
* not stuck to the option. When there is no next argument,
|
||||
* parse_options() uses the default value of an option with
|
||||
* PARSE_OPT_LASTARG_DEFAULT, but errors out for other options.
|
||||
*/
|
||||
if (!value && !unset && parse_options_takes_argument(found.option)) {
|
||||
if (pos + 1 < argc)
|
||||
value = argv[++*i];
|
||||
else if (!(found.option->flags & PARSE_OPT_LASTARG_DEFAULT))
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((found.option->flags & PARSE_OPT_EARLY) &&
|
||||
s->fn(found.option, value, unset, pos, s->data)) {
|
||||
/* the option, and its value if any, have been consumed */
|
||||
++*i;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int early_scan_options(int argc, const char **argv,
|
||||
const struct option *option,
|
||||
enum early_scan_flags flags,
|
||||
early_scan_fn *fn, void *data)
|
||||
{
|
||||
struct early_scan_state s = {
|
||||
.flags = flags,
|
||||
.fn = fn,
|
||||
.data = data,
|
||||
.disallow_abbrev =
|
||||
git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0),
|
||||
};
|
||||
struct option *real_option = preprocess_options(&s.ctx, option);
|
||||
int i;
|
||||
|
||||
s.option = real_option ? real_option : option;
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
if (!early_scan_one_option(&s, argc, argv, &i))
|
||||
break;
|
||||
|
||||
free_preprocessed_options(real_option);
|
||||
free(s.ctx.alias_groups);
|
||||
return i;
|
||||
}
|
||||
|
||||
static int usage_argh(const struct option *opts, FILE *outfile)
|
||||
{
|
||||
const char *s;
|
||||
|
|
|
|||
102
parse-options.h
102
parse-options.h
|
|
@ -51,6 +51,7 @@ enum parse_opt_option_flags {
|
|||
PARSE_OPT_NODASH = 1 << 5,
|
||||
PARSE_OPT_LITERAL_ARGHELP = 1 << 6,
|
||||
PARSE_OPT_FROM_ALIAS = 1 << 7,
|
||||
PARSE_OPT_EARLY = 1 << 8, /* only for early_scan_options() */
|
||||
PARSE_OPT_NOCOMPLETE = 1 << 9,
|
||||
PARSE_OPT_COMP_ARG = 1 << 10,
|
||||
PARSE_OPT_CMDMODE = 1 << 11,
|
||||
|
|
@ -424,6 +425,17 @@ int parse_options(int argc, const char **argv, const char *prefix,
|
|||
const char * const usagestr[],
|
||||
enum parse_opt_flags flags);
|
||||
|
||||
/*
|
||||
* Return non-zero if `opt` takes a value, which means that it consumes
|
||||
* the next argument when that value is not stuck to it with an '='.
|
||||
*
|
||||
* Note that an option with PARSE_OPT_LASTARG_DEFAULT is considered as
|
||||
* taking a value here, even though it only consumes the next argument
|
||||
* when it isn't the last one. Callers that care have to check for that
|
||||
* flag themselves.
|
||||
*/
|
||||
int parse_options_takes_argument(const struct option *opt);
|
||||
|
||||
NORETURN void usage_with_options(const char * const *usagestr,
|
||||
const struct option *options);
|
||||
|
||||
|
|
@ -495,6 +507,96 @@ static inline void die_for_incompatible_opt2(int opt1, const char *opt1_name,
|
|||
BUG("option callback expects an argument"); \
|
||||
} while(0)
|
||||
|
||||
/*----- Early scan: scanning argv before the actual option parsing -----*/
|
||||
|
||||
/*
|
||||
* Some commands need to look at a few options before they can parse
|
||||
* their command line for real, for example because the result decides
|
||||
* whether a repository is needed at all.
|
||||
*
|
||||
* Such an early scan has to know which options take their value as a
|
||||
* separate argument, or it could mistake such a value for an
|
||||
* option. The functions below allow performing such early scans
|
||||
* without being fooled by option values.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Called by early_scan_options() for each argument matching a
|
||||
* `struct option` with PARSE_OPT_EARLY set.
|
||||
*
|
||||
* - `option` is the matching option,
|
||||
* - `value` its value, or NULL if it doesn't take one or if its value
|
||||
* is optional and wasn't given, as with PARSE_OPT_OPTARG, or with
|
||||
* PARSE_OPT_LASTARG_DEFAULT when the option is the last argument,
|
||||
* - `unset` is 'true' for the negated form of the option (like
|
||||
* for parse_opt_cb()), in which case `value` is always NULL, and
|
||||
* - `pos` the index of the option in argv.
|
||||
*
|
||||
* Returning a non-zero value stops the scan right after the option and
|
||||
* its value, if any.
|
||||
*/
|
||||
typedef int early_scan_fn(const struct option *option, const char *value,
|
||||
bool unset, int pos, void *data);
|
||||
|
||||
enum early_scan_flags {
|
||||
EARLY_SCAN_STOP_AT_NON_OPTION = 1 << 0, /* Stop at any non option */
|
||||
};
|
||||
|
||||
/*
|
||||
* Scan `argv` for the options described by `option`, calling `fn` for
|
||||
* each of those that have PARSE_OPT_EARLY set. `argv` is not
|
||||
* modified.
|
||||
*
|
||||
* `fn` may be NULL when no option has PARSE_OPT_EARLY set, which is
|
||||
* useful to only find out where the scan stops.
|
||||
*
|
||||
* The scan always stops at "--" and at "--end-of-options", as
|
||||
* parse_options() always stops parsing options there too, whatever its
|
||||
* flags. PARSE_OPT_KEEP_DASHDASH and PARSE_OPT_KEEP_UNKNOWN_OPT only
|
||||
* decide if the terminator is left in argv, not if it terminates.
|
||||
*
|
||||
* Returns the index of the first argument not consumed by the scan,
|
||||
* which is `argc` when the whole array was scanned.
|
||||
*
|
||||
* Long options are looked up the same way as parse_options() does,
|
||||
* so their negated forms, their unambiguous abbreviations, unless
|
||||
* GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS is set, and the OPTION_ALIAS
|
||||
* entries standing for them are all recognized, and their value is
|
||||
* skipped when they take one.
|
||||
*
|
||||
* To never misinterpret an option value as an option though, the scan
|
||||
* stops at any argument it cannot reliably skip, that is:
|
||||
*
|
||||
* - a short option, or a bundle of them,
|
||||
*
|
||||
* - an unknown option, or an ambiguous or disallowed abbreviation,
|
||||
*
|
||||
* - an option with a low-level callback, as such a callback may
|
||||
* consume any number of arguments,
|
||||
*
|
||||
* - an option with a value stuck to it that parse_options() would
|
||||
* reject, like a negated option or a PARSE_OPT_NOARG one,
|
||||
*
|
||||
* - an option requiring a value when it is the last argument, which
|
||||
* parse_options() would also reject.
|
||||
*
|
||||
* In all these cases, the scan can fail to see an option that
|
||||
* parse_options() would accept, but it doesn't report an option that
|
||||
* parse_options() would not, so that callers err on the safe side.
|
||||
*
|
||||
* This requires the scan to be told about the way the later
|
||||
* parse_options() call treats its arguments though: callers should
|
||||
* pass EARLY_SCAN_STOP_AT_NON_OPTION when parse_options() is called
|
||||
* with PARSE_OPT_STOP_AT_NON_OPTION or with subcommands, and should
|
||||
* not use the scan when parse_options() is called with
|
||||
* PARSE_OPT_KEEP_UNKNOWN_OPT, which disables abbreviations there but
|
||||
* not here.
|
||||
*/
|
||||
int early_scan_options(int argc, const char **argv,
|
||||
const struct option *option,
|
||||
enum early_scan_flags flags,
|
||||
early_scan_fn *fn, void *data);
|
||||
|
||||
/*----- incremental advanced APIs -----*/
|
||||
|
||||
struct parse_opt_cmdmode_list;
|
||||
|
|
|
|||
|
|
@ -385,3 +385,99 @@ int cmd__parse_subcommand(int argc, const char **argv)
|
|||
|
||||
return parse_subcommand__cmd(argc, argv, test_flags);
|
||||
}
|
||||
|
||||
static enum parse_opt_result early_ll_callback(struct parse_opt_ctx_t *ctx UNUSED,
|
||||
const struct option *opt UNUSED,
|
||||
const char *arg UNUSED,
|
||||
int unset UNUSED)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_early_option(const struct option *opt, const char *value,
|
||||
bool unset, int pos, void *data)
|
||||
{
|
||||
int *stop_at_found = data;
|
||||
|
||||
printf("found: %s at %d", opt->long_name, pos);
|
||||
if (unset)
|
||||
printf(" unset");
|
||||
if (value)
|
||||
printf(" value: %s", value);
|
||||
putchar('\n');
|
||||
return *stop_at_found;
|
||||
}
|
||||
|
||||
int cmd__early_scan_options(int argc, const char **argv)
|
||||
{
|
||||
char *a_string = NULL;
|
||||
int an_int = 0, a_bool = 0, a_short = 0;
|
||||
|
||||
const struct option option[] = {
|
||||
OPT_GROUP("early scan test options"),
|
||||
OPT_BOOL_F(0, "wanted", &a_bool,
|
||||
"wanted option taking no value",
|
||||
PARSE_OPT_EARLY),
|
||||
OPT_STRING_F(0, "wanted-value", &a_string, "str",
|
||||
"wanted option taking a value",
|
||||
PARSE_OPT_EARLY),
|
||||
OPT_STRING(0, "skipped-value", &a_string, "str",
|
||||
"option whose value has to be skipped"),
|
||||
OPT_ALIAS(0, "alias-of-skipped", "skipped-value"),
|
||||
OPT_INTEGER(0, "number", &an_int,
|
||||
"option taking an integer value"),
|
||||
OPT_STRING_F(0, "optarg", &a_string, "str",
|
||||
"option with an optional value",
|
||||
PARSE_OPT_OPTARG),
|
||||
OPT_STRING_F(0, "lastarg", &a_string, "str",
|
||||
"option with a last argument default",
|
||||
PARSE_OPT_LASTARG_DEFAULT),
|
||||
OPT_STRING_F(0, "early-optarg", &a_string, "str",
|
||||
"early option with an optional value",
|
||||
PARSE_OPT_EARLY | PARSE_OPT_OPTARG),
|
||||
OPT_STRING_F(0, "early-lastarg", &a_string, "str",
|
||||
"early option with a last argument default",
|
||||
PARSE_OPT_EARLY | PARSE_OPT_LASTARG_DEFAULT),
|
||||
OPT_BOOL_F(0, "no-early", &a_bool,
|
||||
"early option whose name starts with 'no-'",
|
||||
PARSE_OPT_EARLY),
|
||||
OPT_BOOL('s', NULL, &a_short, "short only option"),
|
||||
{
|
||||
.type = OPTION_LOWLEVEL_CALLBACK,
|
||||
.long_name = "low-level",
|
||||
.help = "option with a low-level callback",
|
||||
.flags = PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
||||
.ll_callback = early_ll_callback,
|
||||
},
|
||||
{
|
||||
.type = OPTION_CALLBACK,
|
||||
.long_name = "ll-value",
|
||||
.argh = "str",
|
||||
.help = "option with a value and a low-level callback",
|
||||
.flags = PARSE_OPT_NONEG,
|
||||
.ll_callback = early_ll_callback,
|
||||
},
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
enum early_scan_flags flags = 0;
|
||||
int stop_at_found = 0;
|
||||
int stopped;
|
||||
|
||||
while (argc > 1 && *argv[1] == '-') {
|
||||
if (!strcmp(argv[1], "--stop-at-non-option"))
|
||||
flags |= EARLY_SCAN_STOP_AT_NON_OPTION;
|
||||
else if (!strcmp(argv[1], "--stop-at-found"))
|
||||
stop_at_found = 1;
|
||||
else
|
||||
break;
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
|
||||
stopped = early_scan_options(argc - 1, argv + 1, option, flags,
|
||||
show_early_option, &stop_at_found);
|
||||
printf("stopped at: %d of %d\n", stopped, argc - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ static struct test_cmd cmds[] = {
|
|||
{ "pack-mtimes", cmd__pack_mtimes },
|
||||
{ "parse-options", cmd__parse_options },
|
||||
{ "parse-options-flags", cmd__parse_options_flags },
|
||||
{ "early-scan-options", cmd__early_scan_options },
|
||||
{ "parse-pathspec-file", cmd__parse_pathspec_file },
|
||||
{ "parse-subcommand", cmd__parse_subcommand },
|
||||
{ "partial-clone", cmd__partial_clone },
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ int cmd__pack_deltas(int argc, const char **argv);
|
|||
int cmd__pack_mtimes(int argc, const char **argv);
|
||||
int cmd__parse_options(int argc, const char **argv);
|
||||
int cmd__parse_options_flags(int argc, const char **argv);
|
||||
int cmd__early_scan_options(int argc, const char **argv);
|
||||
int cmd__parse_pathspec_file(int argc, const char** argv);
|
||||
int cmd__parse_subcommand(int argc, const char **argv);
|
||||
int cmd__partial_clone(int argc, const char **argv);
|
||||
|
|
|
|||
|
|
@ -861,4 +861,325 @@ test_expect_success 'u16 limits range' '
|
|||
test_grep "value 65536 for option .u16. not in range \[0,65535\]" err
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() finds a wanted option' '
|
||||
test-tool early-scan-options --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 0
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() reads a stuck or separate value' '
|
||||
test-tool early-scan-options --wanted-value=one >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 value: one
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --wanted-value two >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 value: two
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() skips the value of other options' '
|
||||
test-tool early-scan-options --skipped-value --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --skipped-value one --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 2
|
||||
stopped at: 3 of 3
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --number --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at a last option missing its value' '
|
||||
test-tool early-scan-options --wanted-value >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --wanted --skipped-value >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 0
|
||||
stopped at: 1 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() always stops at "--"' '
|
||||
test-tool early-scan-options -- --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --stop-at-non-option -- --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --skipped-value -- --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 2
|
||||
stopped at: 3 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() always stops at "--end-of-options"' '
|
||||
test-tool early-scan-options --end-of-options --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --stop-at-non-option \
|
||||
--end-of-options --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() can stop at a non-option' '
|
||||
test-tool early-scan-options --stop-at-non-option \
|
||||
arg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --stop-at-non-option \
|
||||
--skipped-value arg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 2
|
||||
stopped at: 3 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() matches unambiguous abbreviations' '
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
test-tool early-scan-options --wanted-v=x >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 value: x
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
test-tool early-scan-options --wanted-v x --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 value: x
|
||||
found: wanted at 2
|
||||
stopped at: 3 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() skips the value of an abbreviation' '
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
test-tool early-scan-options --skipped --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at an ambiguous abbreviation' '
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
test-tool early-scan-options --want --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at disallowed abbreviations' '
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=true \
|
||||
test-tool early-scan-options --wanted-v=x >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=true \
|
||||
test-tool early-scan-options --skipped --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at an unknown option' '
|
||||
test-tool early-scan-options --bogus --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() resolves aliases' '
|
||||
test-tool early-scan-options --alias-of-skipped --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --alias-of-skipped x --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 2
|
||||
stopped at: 3 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() does not skip an optional value' '
|
||||
test-tool early-scan-options --optarg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 1
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --optarg=5 --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 1
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() skips a last argument default value' '
|
||||
test-tool early-scan-options --lastarg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --wanted --lastarg >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 0
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() matches a stuck optional value' '
|
||||
test-tool early-scan-options --early-optarg=one >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: early-optarg at 0 value: one
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --early-lastarg=two >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: early-lastarg at 0 value: two
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() does not take a separate optional value' '
|
||||
test-tool early-scan-options --early-optarg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: early-optarg at 0
|
||||
found: wanted at 1
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() takes a last argument default value' '
|
||||
test-tool early-scan-options --early-lastarg --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: early-lastarg at 0 value: --wanted
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --early-lastarg >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: early-lastarg at 0
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at short options' '
|
||||
test-tool early-scan-options -s --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops at low-level callbacks' '
|
||||
test-tool early-scan-options --low-level --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --ll-value x --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() stops after what the callback consumed' '
|
||||
test-tool early-scan-options --stop-at-found --wanted --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 0
|
||||
stopped at: 1 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --stop-at-found \
|
||||
--wanted-value x --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 value: x
|
||||
stopped at: 2 of 3
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() reports negated options' '
|
||||
test-tool early-scan-options --no-wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted at 0 unset
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --early >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: no-early at 0 unset
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --no-early >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: no-early at 0
|
||||
stopped at: 1 of 1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'early_scan_options() does not take a value for negated options' '
|
||||
test-tool early-scan-options --no-wanted-value --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
found: wanted-value at 0 unset
|
||||
found: wanted at 1
|
||||
stopped at: 2 of 2
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
test-tool early-scan-options --no-wanted-value=x --wanted >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
stopped at: 0 of 2
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
|||
|
|
@ -2344,6 +2344,35 @@ test_expect_success 'R: export-marks options can be overridden by commandline op
|
|||
test_path_is_missing feature-sub
|
||||
'
|
||||
|
||||
test_expect_success 'R: --allow-unsafe-features found after a value' '
|
||||
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
|
||||
git fast-import --allow-unsafe-features <input &&
|
||||
git fast-import --depth=5 --allow-unsafe-features <input &&
|
||||
git fast-import --depth 5 --allow-unsafe-features <input &&
|
||||
git fast-import --date-format raw --allow-unsafe-features <input
|
||||
'
|
||||
|
||||
test_expect_success 'R: --allow-unsafe-features can be abbreviated' '
|
||||
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
|
||||
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
git fast-import --allow-unsafe <input
|
||||
'
|
||||
|
||||
test_expect_success 'R: disallowed abbreviation of --allow-unsafe-features' '
|
||||
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
|
||||
test_must_fail env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=true \
|
||||
git fast-import --allow-unsafe <input 2>err &&
|
||||
test_grep "forbidden in input without --allow-unsafe-features" err
|
||||
'
|
||||
|
||||
test_expect_success 'R: --allow-unsafe-features as the value of an abbreviation' '
|
||||
echo "feature import-marks-if-exists=nonexistent.marks" >input &&
|
||||
test_must_fail env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
|
||||
git fast-import --quiet --export-pack \
|
||||
--allow-unsafe-features <input 2>err &&
|
||||
test_grep "forbidden in input without --allow-unsafe-features" err
|
||||
'
|
||||
|
||||
test_expect_success 'R: catch typo in marks file name' '
|
||||
test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
|
||||
echo "feature import-marks=nonexistent.marks" |
|
||||
|
|
|
|||
Loading…
Reference in New Issue