Skip to content

Commit

Permalink
Merge branch 'sg/parse-options-subcommand'
Browse files Browse the repository at this point in the history
Introduce the "subcommand" mode to parse-options API and update the
command line parser of Git commands with subcommands.

* sg/parse-options-subcommand: (23 commits)
  remote: run "remote rm" argv through parse_options()
  maintenance: add parse-options boilerplate for subcommands
  pass subcommand "prefix" arguments to parse_options()
  builtin/worktree.c: let parse-options parse subcommands
  builtin/stash.c: let parse-options parse subcommands
  builtin/sparse-checkout.c: let parse-options parse subcommands
  builtin/remote.c: let parse-options parse subcommands
  builtin/reflog.c: let parse-options parse subcommands
  builtin/notes.c: let parse-options parse subcommands
  builtin/multi-pack-index.c: let parse-options parse subcommands
  builtin/hook.c: let parse-options parse subcommands
  builtin/gc.c: let parse-options parse 'git maintenance's subcommands
  builtin/commit-graph.c: let parse-options parse subcommands
  builtin/bundle.c: let parse-options parse subcommands
  parse-options: add support for parsing subcommands
  parse-options: drop leading space from '--git-completion-helper' output
  parse-options: clarify the limitations of PARSE_OPT_NODASH
  parse-options: PARSE_OPT_KEEP_UNKNOWN only applies to --options
  api-parse-options.txt: fix description of OPT_CMDMODE
  t0040-parse-options: test parse_options() with various 'parse_opt_flags'
  ...
  • Loading branch information
gitster committed Sep 1, 2022
2 parents 68ef042 + 8f9d80f commit d528044
Show file tree
Hide file tree
Showing 35 changed files with 875 additions and 335 deletions.
49 changes: 45 additions & 4 deletions Documentation/technical/api-parse-options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Basics
------

The argument vector `argv[]` may usually contain mandatory or optional
'non-option arguments', e.g. a filename or a branch, and 'options'.
'non-option arguments', e.g. a filename or a branch, 'options', and
'subcommands'.
Options are optional arguments that start with a dash and
that allow to change the behavior of a command.

Expand Down Expand Up @@ -48,6 +49,33 @@ The parse-options API allows:
option, e.g. `-a -b --option -- --this-is-a-file` indicates that
`--this-is-a-file` must not be processed as an option.

Subcommands are special in a couple of ways:

* Subcommands 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.

Therefore, if the options array contains at least one subcommand and
`parse_options()` encounters the first dashless argument, it will either:

* stop and return, if that dashless argument is a known subcommand, setting
`value` to the function pointer associated with that subcommand, storing
the name of the subcommand in argv[0], and leaving the rest of the
arguments unprocessed, or

* stop and return, if it was invoked with the `PARSE_OPT_SUBCOMMAND_OPTIONAL`
flag and that dashless argument doesn't match any subcommands, leaving
`value` unchanged and the rest of the arguments unprocessed, or

* show error and usage, and abort.

Steps to parse options
----------------------

Expand Down Expand Up @@ -90,8 +118,8 @@ Flags are the bitwise-or of:
Keep the first argument, which contains the program name. It's
removed from argv[] by default.

`PARSE_OPT_KEEP_UNKNOWN`::
Keep unknown arguments instead of erroring out. This doesn't
`PARSE_OPT_KEEP_UNKNOWN_OPT`::
Keep unknown options instead of erroring out. This doesn't
work for all combinations of arguments as users might expect
it to do. E.g. if the first argument in `--unknown --known`
takes a value (which we can't know), the second one is
Expand All @@ -101,13 +129,22 @@ Flags are the bitwise-or of:
non-option, not as a value belonging to the unknown option,
the parser early. That's why parse_options() errors out if
both options are set.
Note that non-option arguments are always kept, even without
this flag.

`PARSE_OPT_NO_INTERNAL_HELP`::
By default, parse_options() handles `-h`, `--help` and
`--help-all` internally, by showing a help screen. This option
turns it off and allows one to add custom handlers for these
options, or to just leave them unknown.

`PARSE_OPT_SUBCOMMAND_OPTIONAL`::
Don't error out when no subcommand is specified.

Note that `PARSE_OPT_STOP_AT_NON_OPTION` is incompatible with subcommands;
while `PARSE_OPT_KEEP_DASHDASH` and `PARSE_OPT_KEEP_UNKNOWN_OPT` can only be
used with subcommands when combined with `PARSE_OPT_SUBCOMMAND_OPTIONAL`.

Data Structure
--------------

Expand Down Expand Up @@ -236,10 +273,14 @@ There are some macros to easily define options:
`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
Define an "operation mode" option, only one of which in the same
group of "operating mode" options that share the same `int_var`
can be given by the user. `enum_val` is set to `int_var` when the
can be given by the user. `int_var` is set to `enum_val` when the
option is used, but an error is reported if other "operating mode"
option has already set its value to the same `int_var`.
In new commands consider using subcommands instead.

`OPT_SUBCOMMAND(long, &fn_ptr, subcommand_fn)`::
Define a subcommand. `subcommand_fn` is put into `fn_ptr` when
this subcommand is used.

The last element of the array must be `OPT_END()`.

Expand Down
2 changes: 1 addition & 1 deletion builtin/archive.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static int run_remote_archiver(int argc, const char **argv,

#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
PARSE_OPT_KEEP_ARGV0 | \
PARSE_OPT_KEEP_UNKNOWN | \
PARSE_OPT_KEEP_UNKNOWN_OPT | \
PARSE_OPT_NO_INTERNAL_HELP )

int cmd_archive(int argc, const char **argv, const char *prefix)
Expand Down
2 changes: 1 addition & 1 deletion builtin/bisect--helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)

argc = parse_options(argc, argv, prefix, options,
git_bisect_helper_usage,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN_OPT);

if (!cmdmode)
usage_with_options(git_bisect_helper_usage, options);
Expand Down
1 change: 1 addition & 0 deletions builtin/blame.c
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
break;
case PARSE_OPT_HELP:
case PARSE_OPT_ERROR:
case PARSE_OPT_SUBCOMMAND:
exit(129);
case PARSE_OPT_COMPLETE:
exit(0);
Expand Down
25 changes: 7 additions & 18 deletions builtin/bundle.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,30 +195,19 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)

int cmd_bundle(int argc, const char **argv, const char *prefix)
{
parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
OPT_SUBCOMMAND("create", &fn, cmd_bundle_create),
OPT_SUBCOMMAND("verify", &fn, cmd_bundle_verify),
OPT_SUBCOMMAND("list-heads", &fn, cmd_bundle_list_heads),
OPT_SUBCOMMAND("unbundle", &fn, cmd_bundle_unbundle),
OPT_END()
};
int result;

argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
0);

packet_trace_identity("bundle");

if (argc < 2)
usage_with_options(builtin_bundle_usage, options);

else if (!strcmp(argv[0], "create"))
result = cmd_bundle_create(argc, argv, prefix);
else if (!strcmp(argv[0], "verify"))
result = cmd_bundle_verify(argc, argv, prefix);
else if (!strcmp(argv[0], "list-heads"))
result = cmd_bundle_list_heads(argc, argv, prefix);
else if (!strcmp(argv[0], "unbundle"))
result = cmd_bundle_unbundle(argc, argv, prefix);
else {
error(_("Unknown subcommand: %s"), argv[0]);
usage_with_options(builtin_bundle_usage, options);
}
return result ? 1 : 0;
return !!fn(argc, argv, prefix);
}
34 changes: 15 additions & 19 deletions builtin/commit-graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static struct option *add_common_options(struct option *to)
return parse_options_concat(common_opts, to);
}

static int graph_verify(int argc, const char **argv)
static int graph_verify(int argc, const char **argv, const char *prefix)
{
struct commit_graph *graph = NULL;
struct object_directory *odb = NULL;
Expand All @@ -80,7 +80,7 @@ static int graph_verify(int argc, const char **argv)
trace2_cmd_mode("verify");

opts.progress = isatty(2);
argc = parse_options(argc, argv, NULL,
argc = parse_options(argc, argv, prefix,
options,
builtin_commit_graph_verify_usage, 0);
if (argc)
Expand Down Expand Up @@ -190,7 +190,7 @@ static int git_commit_graph_write_config(const char *var, const char *value,
return 0;
}

static int graph_write(int argc, const char **argv)
static int graph_write(int argc, const char **argv, const char *prefix)
{
struct string_list pack_indexes = STRING_LIST_INIT_DUP;
struct strbuf buf = STRBUF_INIT;
Expand Down Expand Up @@ -241,7 +241,7 @@ static int graph_write(int argc, const char **argv)

git_config(git_commit_graph_write_config, &opts);

argc = parse_options(argc, argv, NULL,
argc = parse_options(argc, argv, prefix,
options,
builtin_commit_graph_write_usage, 0);
if (argc)
Expand Down Expand Up @@ -307,26 +307,22 @@ static int graph_write(int argc, const char **argv)

int cmd_commit_graph(int argc, const char **argv, const char *prefix)
{
struct option *builtin_commit_graph_options = common_opts;
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_commit_graph_options[] = {
OPT_SUBCOMMAND("verify", &fn, graph_verify),
OPT_SUBCOMMAND("write", &fn, graph_write),
OPT_END(),
};
struct option *options = parse_options_concat(builtin_commit_graph_options, common_opts);

git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_commit_graph_options,
builtin_commit_graph_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
if (!argc)
goto usage;

read_replace_refs = 0;
save_commit_buffer = 0;

if (!strcmp(argv[0], "verify"))
return graph_verify(argc, argv);
else if (argc && !strcmp(argv[0], "write"))
return graph_write(argc, argv);
argc = parse_options(argc, argv, prefix, options,
builtin_commit_graph_usage, 0);
FREE_AND_NULL(options);

error(_("unrecognized subcommand: %s"), argv[0]);
usage:
usage_with_options(builtin_commit_graph_usage,
builtin_commit_graph_options);
return fn(argc, argv, prefix);
}
2 changes: 1 addition & 1 deletion builtin/difftool.c
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
symlinks = has_symlinks;

argc = parse_options(argc, argv, prefix, builtin_difftool_options,
builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_KEEP_DASHDASH);

if (tool_help)
Expand Down
2 changes: 1 addition & 1 deletion builtin/env--helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix)
};

argc = parse_options(argc, argv, prefix, opts, env__helper_usage,
PARSE_OPT_KEEP_UNKNOWN);
PARSE_OPT_KEEP_UNKNOWN_OPT);
if (env_default && !*env_default)
usage_with_options(env__helper_usage, opts);
if (!cmdmode)
Expand Down
2 changes: 1 addition & 1 deletion builtin/fast-export.c
Original file line number Diff line number Diff line change
Expand Up @@ -1221,7 +1221,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
revs.sources = &revision_sources;
revs.rewrite_parents = 1;
argc = parse_options(argc, argv, prefix, options, fast_export_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
usage_with_options (fast_export_usage, options);
Expand Down
83 changes: 62 additions & 21 deletions builtin/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1459,14 +1459,28 @@ static char *get_maintpath(void)
return strbuf_detach(&sb, NULL);
}

static int maintenance_register(void)
static char const * const builtin_maintenance_register_usage[] = {
N_("git maintenance register"),
NULL
};

static int maintenance_register(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END(),
};
int rc;
char *config_value;
struct child_process config_set = CHILD_PROCESS_INIT;
struct child_process config_get = CHILD_PROCESS_INIT;
char *maintpath = get_maintpath();

argc = parse_options(argc, argv, prefix, options,
builtin_maintenance_register_usage, 0);
if (argc)
usage_with_options(builtin_maintenance_register_usage,
options);

/* Disable foreground maintenance */
git_config_set("maintenance.auto", "false");

Expand Down Expand Up @@ -1503,12 +1517,26 @@ static int maintenance_register(void)
return rc;
}

static int maintenance_unregister(void)
static char const * const builtin_maintenance_unregister_usage[] = {
N_("git maintenance unregister"),
NULL
};

static int maintenance_unregister(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END(),
};
int rc;
struct child_process config_unset = CHILD_PROCESS_INIT;
char *maintpath = get_maintpath();

argc = parse_options(argc, argv, prefix, options,
builtin_maintenance_unregister_usage, 0);
if (argc)
usage_with_options(builtin_maintenance_unregister_usage,
options);

config_unset.git_cmd = 1;
strvec_pushl(&config_unset.args, "config", "--global", "--unset",
"--fixed-value", "maintenance.repo", maintpath, NULL);
Expand Down Expand Up @@ -2490,6 +2518,7 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
PARSE_OPT_NONEG, maintenance_opt_scheduler),
OPT_END()
};
const char *register_args[] = { "register", NULL };

argc = parse_options(argc, argv, prefix, options,
builtin_maintenance_start_usage, 0);
Expand All @@ -2499,34 +2528,46 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
opts.scheduler = resolve_scheduler(opts.scheduler);
validate_scheduler(opts.scheduler);

if (maintenance_register())
if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
warning(_("failed to add repo to global config"));
return update_background_schedule(&opts, 1);
}

static int maintenance_stop(void)
static const char *const builtin_maintenance_stop_usage[] = {
N_("git maintenance stop"),
NULL
};

static int maintenance_stop(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END()
};
argc = parse_options(argc, argv, prefix, options,
builtin_maintenance_stop_usage, 0);
if (argc)
usage_with_options(builtin_maintenance_stop_usage, options);
return update_background_schedule(NULL, 0);
}

static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
static const char * const builtin_maintenance_usage[] = {
N_("git maintenance <subcommand> [<options>]"),
NULL,
};

int cmd_maintenance(int argc, const char **argv, const char *prefix)
{
if (argc < 2 ||
(argc == 2 && !strcmp(argv[1], "-h")))
usage(builtin_maintenance_usage);

if (!strcmp(argv[1], "run"))
return maintenance_run(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "start"))
return maintenance_start(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "stop"))
return maintenance_stop();
if (!strcmp(argv[1], "register"))
return maintenance_register();
if (!strcmp(argv[1], "unregister"))
return maintenance_unregister();

die(_("invalid subcommand: %s"), argv[1]);
parse_opt_subcommand_fn *fn = NULL;
struct option builtin_maintenance_options[] = {
OPT_SUBCOMMAND("run", &fn, maintenance_run),
OPT_SUBCOMMAND("start", &fn, maintenance_start),
OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
OPT_SUBCOMMAND("register", &fn, maintenance_register),
OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
OPT_END(),
};

argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
builtin_maintenance_usage, 0);
return fn(argc, argv, prefix);
}

0 comments on commit d528044

Please sign in to comment.