Skip to content

Commit d528044

Browse files
committed
Merge branch 'sg/parse-options-subcommand'
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' ...
2 parents 68ef042 + 8f9d80f commit d528044

35 files changed

+875
-335
lines changed

Documentation/technical/api-parse-options.txt

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Basics
88
------
99

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

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

52+
Subcommands are special in a couple of ways:
53+
54+
* Subcommands only have long form, and they have no double dash prefix, no
55+
negated form, and no description, and they don't take any arguments, and
56+
can't be abbreviated.
57+
58+
* There must be exactly one subcommand among the arguments, or zero if the
59+
command has a default operation mode.
60+
61+
* All arguments following the subcommand are considered to be arguments of
62+
the subcommand, and, conversely, arguments meant for the subcommand may
63+
not preceed the subcommand.
64+
65+
Therefore, if the options array contains at least one subcommand and
66+
`parse_options()` encounters the first dashless argument, it will either:
67+
68+
* stop and return, if that dashless argument is a known subcommand, setting
69+
`value` to the function pointer associated with that subcommand, storing
70+
the name of the subcommand in argv[0], and leaving the rest of the
71+
arguments unprocessed, or
72+
73+
* stop and return, if it was invoked with the `PARSE_OPT_SUBCOMMAND_OPTIONAL`
74+
flag and that dashless argument doesn't match any subcommands, leaving
75+
`value` unchanged and the rest of the arguments unprocessed, or
76+
77+
* show error and usage, and abort.
78+
5179
Steps to parse options
5280
----------------------
5381

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

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

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

141+
`PARSE_OPT_SUBCOMMAND_OPTIONAL`::
142+
Don't error out when no subcommand is specified.
143+
144+
Note that `PARSE_OPT_STOP_AT_NON_OPTION` is incompatible with subcommands;
145+
while `PARSE_OPT_KEEP_DASHDASH` and `PARSE_OPT_KEEP_UNKNOWN_OPT` can only be
146+
used with subcommands when combined with `PARSE_OPT_SUBCOMMAND_OPTIONAL`.
147+
111148
Data Structure
112149
--------------
113150

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

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

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

builtin/archive.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static int run_remote_archiver(int argc, const char **argv,
7575

7676
#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \
7777
PARSE_OPT_KEEP_ARGV0 | \
78-
PARSE_OPT_KEEP_UNKNOWN | \
78+
PARSE_OPT_KEEP_UNKNOWN_OPT | \
7979
PARSE_OPT_NO_INTERNAL_HELP )
8080

8181
int cmd_archive(int argc, const char **argv, const char *prefix)

builtin/bisect--helper.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,7 +1324,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
13241324

13251325
argc = parse_options(argc, argv, prefix, options,
13261326
git_bisect_helper_usage,
1327-
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN);
1327+
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN_OPT);
13281328

13291329
if (!cmdmode)
13301330
usage_with_options(git_bisect_helper_usage, options);

builtin/blame.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
920920
break;
921921
case PARSE_OPT_HELP:
922922
case PARSE_OPT_ERROR:
923+
case PARSE_OPT_SUBCOMMAND:
923924
exit(129);
924925
case PARSE_OPT_COMPLETE:
925926
exit(0);

builtin/bundle.c

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,19 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
195195

196196
int cmd_bundle(int argc, const char **argv, const char *prefix)
197197
{
198+
parse_opt_subcommand_fn *fn = NULL;
198199
struct option options[] = {
200+
OPT_SUBCOMMAND("create", &fn, cmd_bundle_create),
201+
OPT_SUBCOMMAND("verify", &fn, cmd_bundle_verify),
202+
OPT_SUBCOMMAND("list-heads", &fn, cmd_bundle_list_heads),
203+
OPT_SUBCOMMAND("unbundle", &fn, cmd_bundle_unbundle),
199204
OPT_END()
200205
};
201-
int result;
202206

203207
argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage,
204-
PARSE_OPT_STOP_AT_NON_OPTION);
208+
0);
205209

206210
packet_trace_identity("bundle");
207211

208-
if (argc < 2)
209-
usage_with_options(builtin_bundle_usage, options);
210-
211-
else if (!strcmp(argv[0], "create"))
212-
result = cmd_bundle_create(argc, argv, prefix);
213-
else if (!strcmp(argv[0], "verify"))
214-
result = cmd_bundle_verify(argc, argv, prefix);
215-
else if (!strcmp(argv[0], "list-heads"))
216-
result = cmd_bundle_list_heads(argc, argv, prefix);
217-
else if (!strcmp(argv[0], "unbundle"))
218-
result = cmd_bundle_unbundle(argc, argv, prefix);
219-
else {
220-
error(_("Unknown subcommand: %s"), argv[0]);
221-
usage_with_options(builtin_bundle_usage, options);
222-
}
223-
return result ? 1 : 0;
212+
return !!fn(argc, argv, prefix);
224213
}

builtin/commit-graph.c

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static struct option *add_common_options(struct option *to)
5858
return parse_options_concat(common_opts, to);
5959
}
6060

61-
static int graph_verify(int argc, const char **argv)
61+
static int graph_verify(int argc, const char **argv, const char *prefix)
6262
{
6363
struct commit_graph *graph = NULL;
6464
struct object_directory *odb = NULL;
@@ -80,7 +80,7 @@ static int graph_verify(int argc, const char **argv)
8080
trace2_cmd_mode("verify");
8181

8282
opts.progress = isatty(2);
83-
argc = parse_options(argc, argv, NULL,
83+
argc = parse_options(argc, argv, prefix,
8484
options,
8585
builtin_commit_graph_verify_usage, 0);
8686
if (argc)
@@ -190,7 +190,7 @@ static int git_commit_graph_write_config(const char *var, const char *value,
190190
return 0;
191191
}
192192

193-
static int graph_write(int argc, const char **argv)
193+
static int graph_write(int argc, const char **argv, const char *prefix)
194194
{
195195
struct string_list pack_indexes = STRING_LIST_INIT_DUP;
196196
struct strbuf buf = STRBUF_INIT;
@@ -241,7 +241,7 @@ static int graph_write(int argc, const char **argv)
241241

242242
git_config(git_commit_graph_write_config, &opts);
243243

244-
argc = parse_options(argc, argv, NULL,
244+
argc = parse_options(argc, argv, prefix,
245245
options,
246246
builtin_commit_graph_write_usage, 0);
247247
if (argc)
@@ -307,26 +307,22 @@ static int graph_write(int argc, const char **argv)
307307

308308
int cmd_commit_graph(int argc, const char **argv, const char *prefix)
309309
{
310-
struct option *builtin_commit_graph_options = common_opts;
310+
parse_opt_subcommand_fn *fn = NULL;
311+
struct option builtin_commit_graph_options[] = {
312+
OPT_SUBCOMMAND("verify", &fn, graph_verify),
313+
OPT_SUBCOMMAND("write", &fn, graph_write),
314+
OPT_END(),
315+
};
316+
struct option *options = parse_options_concat(builtin_commit_graph_options, common_opts);
311317

312318
git_config(git_default_config, NULL);
313-
argc = parse_options(argc, argv, prefix,
314-
builtin_commit_graph_options,
315-
builtin_commit_graph_usage,
316-
PARSE_OPT_STOP_AT_NON_OPTION);
317-
if (!argc)
318-
goto usage;
319319

320320
read_replace_refs = 0;
321321
save_commit_buffer = 0;
322322

323-
if (!strcmp(argv[0], "verify"))
324-
return graph_verify(argc, argv);
325-
else if (argc && !strcmp(argv[0], "write"))
326-
return graph_write(argc, argv);
323+
argc = parse_options(argc, argv, prefix, options,
324+
builtin_commit_graph_usage, 0);
325+
FREE_AND_NULL(options);
327326

328-
error(_("unrecognized subcommand: %s"), argv[0]);
329-
usage:
330-
usage_with_options(builtin_commit_graph_usage,
331-
builtin_commit_graph_options);
327+
return fn(argc, argv, prefix);
332328
}

builtin/difftool.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
716716
symlinks = has_symlinks;
717717

718718
argc = parse_options(argc, argv, prefix, builtin_difftool_options,
719-
builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
719+
builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN_OPT |
720720
PARSE_OPT_KEEP_DASHDASH);
721721

722722
if (tool_help)

builtin/env--helper.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix)
5050
};
5151

5252
argc = parse_options(argc, argv, prefix, opts, env__helper_usage,
53-
PARSE_OPT_KEEP_UNKNOWN);
53+
PARSE_OPT_KEEP_UNKNOWN_OPT);
5454
if (env_default && !*env_default)
5555
usage_with_options(env__helper_usage, opts);
5656
if (!cmdmode)

builtin/fast-export.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
12211221
revs.sources = &revision_sources;
12221222
revs.rewrite_parents = 1;
12231223
argc = parse_options(argc, argv, prefix, options, fast_export_usage,
1224-
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
1224+
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
12251225
argc = setup_revisions(argc, argv, &revs, NULL);
12261226
if (argc > 1)
12271227
usage_with_options (fast_export_usage, options);

builtin/gc.c

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,14 +1459,28 @@ static char *get_maintpath(void)
14591459
return strbuf_detach(&sb, NULL);
14601460
}
14611461

1462-
static int maintenance_register(void)
1462+
static char const * const builtin_maintenance_register_usage[] = {
1463+
N_("git maintenance register"),
1464+
NULL
1465+
};
1466+
1467+
static int maintenance_register(int argc, const char **argv, const char *prefix)
14631468
{
1469+
struct option options[] = {
1470+
OPT_END(),
1471+
};
14641472
int rc;
14651473
char *config_value;
14661474
struct child_process config_set = CHILD_PROCESS_INIT;
14671475
struct child_process config_get = CHILD_PROCESS_INIT;
14681476
char *maintpath = get_maintpath();
14691477

1478+
argc = parse_options(argc, argv, prefix, options,
1479+
builtin_maintenance_register_usage, 0);
1480+
if (argc)
1481+
usage_with_options(builtin_maintenance_register_usage,
1482+
options);
1483+
14701484
/* Disable foreground maintenance */
14711485
git_config_set("maintenance.auto", "false");
14721486

@@ -1503,12 +1517,26 @@ static int maintenance_register(void)
15031517
return rc;
15041518
}
15051519

1506-
static int maintenance_unregister(void)
1520+
static char const * const builtin_maintenance_unregister_usage[] = {
1521+
N_("git maintenance unregister"),
1522+
NULL
1523+
};
1524+
1525+
static int maintenance_unregister(int argc, const char **argv, const char *prefix)
15071526
{
1527+
struct option options[] = {
1528+
OPT_END(),
1529+
};
15081530
int rc;
15091531
struct child_process config_unset = CHILD_PROCESS_INIT;
15101532
char *maintpath = get_maintpath();
15111533

1534+
argc = parse_options(argc, argv, prefix, options,
1535+
builtin_maintenance_unregister_usage, 0);
1536+
if (argc)
1537+
usage_with_options(builtin_maintenance_unregister_usage,
1538+
options);
1539+
15121540
config_unset.git_cmd = 1;
15131541
strvec_pushl(&config_unset.args, "config", "--global", "--unset",
15141542
"--fixed-value", "maintenance.repo", maintpath, NULL);
@@ -2490,6 +2518,7 @@ static int maintenance_start(int argc, const char **argv, const char *prefix)
24902518
PARSE_OPT_NONEG, maintenance_opt_scheduler),
24912519
OPT_END()
24922520
};
2521+
const char *register_args[] = { "register", NULL };
24932522

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

2502-
if (maintenance_register())
2531+
if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL))
25032532
warning(_("failed to add repo to global config"));
25042533
return update_background_schedule(&opts, 1);
25052534
}
25062535

2507-
static int maintenance_stop(void)
2536+
static const char *const builtin_maintenance_stop_usage[] = {
2537+
N_("git maintenance stop"),
2538+
NULL
2539+
};
2540+
2541+
static int maintenance_stop(int argc, const char **argv, const char *prefix)
25082542
{
2543+
struct option options[] = {
2544+
OPT_END()
2545+
};
2546+
argc = parse_options(argc, argv, prefix, options,
2547+
builtin_maintenance_stop_usage, 0);
2548+
if (argc)
2549+
usage_with_options(builtin_maintenance_stop_usage, options);
25092550
return update_background_schedule(NULL, 0);
25102551
}
25112552

2512-
static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
2553+
static const char * const builtin_maintenance_usage[] = {
2554+
N_("git maintenance <subcommand> [<options>]"),
2555+
NULL,
2556+
};
25132557

25142558
int cmd_maintenance(int argc, const char **argv, const char *prefix)
25152559
{
2516-
if (argc < 2 ||
2517-
(argc == 2 && !strcmp(argv[1], "-h")))
2518-
usage(builtin_maintenance_usage);
2519-
2520-
if (!strcmp(argv[1], "run"))
2521-
return maintenance_run(argc - 1, argv + 1, prefix);
2522-
if (!strcmp(argv[1], "start"))
2523-
return maintenance_start(argc - 1, argv + 1, prefix);
2524-
if (!strcmp(argv[1], "stop"))
2525-
return maintenance_stop();
2526-
if (!strcmp(argv[1], "register"))
2527-
return maintenance_register();
2528-
if (!strcmp(argv[1], "unregister"))
2529-
return maintenance_unregister();
2530-
2531-
die(_("invalid subcommand: %s"), argv[1]);
2560+
parse_opt_subcommand_fn *fn = NULL;
2561+
struct option builtin_maintenance_options[] = {
2562+
OPT_SUBCOMMAND("run", &fn, maintenance_run),
2563+
OPT_SUBCOMMAND("start", &fn, maintenance_start),
2564+
OPT_SUBCOMMAND("stop", &fn, maintenance_stop),
2565+
OPT_SUBCOMMAND("register", &fn, maintenance_register),
2566+
OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister),
2567+
OPT_END(),
2568+
};
2569+
2570+
argc = parse_options(argc, argv, prefix, builtin_maintenance_options,
2571+
builtin_maintenance_usage, 0);
2572+
return fn(argc, argv, prefix);
25322573
}

0 commit comments

Comments
 (0)