Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/technical/api-parse-options.txt49
-rw-r--r--builtin/archive.c2
-rw-r--r--builtin/bisect--helper.c2
-rw-r--r--builtin/blame.c1
-rw-r--r--builtin/bundle.c25
-rw-r--r--builtin/commit-graph.c34
-rw-r--r--builtin/difftool.c2
-rw-r--r--builtin/env--helper.c2
-rw-r--r--builtin/fast-export.c2
-rw-r--r--builtin/gc.c83
-rw-r--r--builtin/hook.c12
-rw-r--r--builtin/log.c4
-rw-r--r--builtin/multi-pack-index.c59
-rw-r--r--builtin/notes.c43
-rw-r--r--builtin/reflog.c43
-rw-r--r--builtin/remote.c106
-rw-r--r--builtin/revert.c2
-rw-r--r--builtin/shortlog.c1
-rw-r--r--builtin/sparse-checkout.c56
-rw-r--r--builtin/stash.c59
-rw-r--r--builtin/worktree.c31
-rw-r--r--diff.c2
-rw-r--r--git.c14
-rw-r--r--parse-options.c118
-rw-r--r--parse-options.h29
-rw-r--r--t/helper/test-parse-options.c127
-rw-r--r--t/helper/test-serve-v2.c2
-rw-r--r--t/helper/test-tool.c2
-rw-r--r--t/helper/test-tool.h2
-rwxr-xr-xt/t0040-parse-options.sh255
-rwxr-xr-xt/t3301-notes.sh5
-rwxr-xr-xt/t3903-stash.sh2
-rwxr-xr-xt/t5318-commit-graph.sh4
-rwxr-xr-xt/t5505-remote.sh20
-rwxr-xr-xt/t7900-maintenance.sh10
35 files changed, 875 insertions, 335 deletions
diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt
index acfd5dc1d8..c2a5e42914 100644
--- a/Documentation/technical/api-parse-options.txt
+++ b/Documentation/technical/api-parse-options.txt
@@ -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.
@@ -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
----------------------
@@ -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
@@ -101,6 +129,8 @@ 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
@@ -108,6 +138,13 @@ Flags are the bitwise-or of:
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
--------------
@@ -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()`.
diff --git a/builtin/archive.c b/builtin/archive.c
index 7176b041b6..f094390ee0 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -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)
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 8a052c7111..7097750fc6 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -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);
diff --git a/builtin/blame.c b/builtin/blame.c
index 02e39420b6..a9fe8cf7a6 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -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);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 2adad545a2..e80efce3a4 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -195,30 +195,19 @@ cleanup:
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);
}
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 51c4040ea6..dc3cc35394 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -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;
@@ -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)
@@ -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;
@@ -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)
@@ -307,26 +307,22 @@ cleanup:
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);
}
diff --git a/builtin/difftool.c b/builtin/difftool.c
index b3c509b8de..8706f68492 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -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)
diff --git a/builtin/env--helper.c b/builtin/env--helper.c
index 27349098b0..ea04c16636 100644
--- a/builtin/env--helper.c
+++ b/builtin/env--helper.c
@@ -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)
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index e1748fb98b..bf3c20dea2 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -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);
diff --git a/builtin/gc.c b/builtin/gc.c
index 6c22205217..eb567110e7 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -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");
@@ -1503,12 +1517,26 @@ done:
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);
@@ -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);
@@ -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);
}
diff --git a/builtin/hook.c b/builtin/hook.c
index 54e5c6ec93..b6530d189a 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -67,18 +67,14 @@ usage:
int cmd_hook(int argc, const char **argv, const char *prefix)
{
+ parse_opt_subcommand_fn *fn = NULL;
struct option builtin_hook_options[] = {
+ OPT_SUBCOMMAND("run", &fn, run),
OPT_END(),
};
argc = parse_options(argc, argv, NULL, builtin_hook_options,
- builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
- if (!argc)
- goto usage;
+ builtin_hook_usage, 0);
- if (!strcmp(argv[0], "run"))
- return run(argc, argv, prefix);
-
-usage:
- usage_with_options(builtin_hook_usage, builtin_hook_options);
+ return fn(argc, argv, prefix);
}
diff --git a/builtin/log.c b/builtin/log.c
index fd209725e5..56e2d95e86 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -260,7 +260,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
- PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_KEEP_DASHDASH);
if (quiet)
@@ -1989,7 +1989,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
*/
argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
builtin_format_patch_usage,
- PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_KEEP_DASHDASH);
/* Make sure "0000-$sub.patch" gives non-negative length for $sub */
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 8f24d59a75..248929f2e6 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -104,7 +104,8 @@ static void read_packs_from_stdin(struct string_list *to)
strbuf_release(&buf);
}
-static int cmd_multi_pack_index_write(int argc, const char **argv)
+static int cmd_multi_pack_index_write(int argc, const char **argv,
+ const char *prefix)
{
struct option *options;
static struct option builtin_multi_pack_index_write_options[] = {
@@ -132,7 +133,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
if (isatty(2))
opts.flags |= MIDX_PROGRESS;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
options, builtin_multi_pack_index_write_usage,
0);
if (argc)
@@ -160,7 +161,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
opts.refs_snapshot, opts.flags);
}
-static int cmd_multi_pack_index_verify(int argc, const char **argv)
+static int cmd_multi_pack_index_verify(int argc, const char **argv,
+ const char *prefix)
{
struct option *options;
static struct option builtin_multi_pack_index_verify_options[] = {
@@ -174,7 +176,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv)
if (isatty(2))
opts.flags |= MIDX_PROGRESS;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
options, builtin_multi_pack_index_verify_usage,
0);
if (argc)
@@ -186,7 +188,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv)
return verify_midx_file(the_repository, opts.object_dir, opts.flags);
}
-static int cmd_multi_pack_index_expire(int argc, const char **argv)
+static int cmd_multi_pack_index_expire(int argc, const char **argv,
+ const char *prefix)
{
struct option *options;
static struct option builtin_multi_pack_index_expire_options[] = {
@@ -200,7 +203,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv)
if (isatty(2))
opts.flags |= MIDX_PROGRESS;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
options, builtin_multi_pack_index_expire_usage,
0);
if (argc)
@@ -212,7 +215,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv)
return expire_midx_packs(the_repository, opts.object_dir, opts.flags);
}
-static int cmd_multi_pack_index_repack(int argc, const char **argv)
+static int cmd_multi_pack_index_repack(int argc, const char **argv,
+ const char *prefix)
{
struct option *options;
static struct option builtin_multi_pack_index_repack_options[] = {
@@ -229,7 +233,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
if (isatty(2))
opts.flags |= MIDX_PROGRESS;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
options,
builtin_multi_pack_index_repack_usage,
0);
@@ -247,7 +251,15 @@ int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
int res;
- struct option *builtin_multi_pack_index_options = common_opts;
+ parse_opt_subcommand_fn *fn = NULL;
+ struct option builtin_multi_pack_index_options[] = {
+ OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack),
+ OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write),
+ OPT_SUBCOMMAND("verify", &fn, cmd_multi_pack_index_verify),
+ OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire),
+ OPT_END(),
+ };
+ struct option *options = parse_options_concat(builtin_multi_pack_index_options, common_opts);
git_config(git_default_config, NULL);
@@ -256,31 +268,12 @@ int cmd_multi_pack_index(int argc, const char **argv,
the_repository->objects->odb)
opts.object_dir = xstrdup(the_repository->objects->odb->path);
- argc = parse_options(argc, argv, prefix,
- builtin_multi_pack_index_options,
- builtin_multi_pack_index_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
-
- if (!argc)
- goto usage;
-
- if (!strcmp(argv[0], "repack"))
- res = cmd_multi_pack_index_repack(argc, argv);
- else if (!strcmp(argv[0], "write"))
- res = cmd_multi_pack_index_write(argc, argv);
- else if (!strcmp(argv[0], "verify"))
- res = cmd_multi_pack_index_verify(argc, argv);
- else if (!strcmp(argv[0], "expire"))
- res = cmd_multi_pack_index_expire(argc, argv);
- else {
- error(_("unrecognized subcommand: %s"), argv[0]);
- goto usage;
- }
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_multi_pack_index_usage, 0);
+ FREE_AND_NULL(options);
+
+ res = fn(argc, argv, prefix);
free(opts.object_dir);
return res;
-
-usage:
- usage_with_options(builtin_multi_pack_index_usage,
- builtin_multi_pack_index_options);
}
diff --git a/builtin/notes.c b/builtin/notes.c
index a3d0d15a22..42cbae4659 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -994,17 +994,31 @@ static int get_ref(int argc, const char **argv, const char *prefix)
int cmd_notes(int argc, const char **argv, const char *prefix)
{
- int result;
const char *override_notes_ref = NULL;
+ parse_opt_subcommand_fn *fn = list;
struct option options[] = {
OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"),
N_("use notes from <notes-ref>")),
+ OPT_SUBCOMMAND("list", &fn, list),
+ OPT_SUBCOMMAND("add", &fn, add),
+ OPT_SUBCOMMAND("copy", &fn, copy),
+ OPT_SUBCOMMAND("append", &fn, append_edit),
+ OPT_SUBCOMMAND("edit", &fn, append_edit),
+ OPT_SUBCOMMAND("show", &fn, show),
+ OPT_SUBCOMMAND("merge", &fn, merge),
+ OPT_SUBCOMMAND("remove", &fn, remove_cmd),
+ OPT_SUBCOMMAND("prune", &fn, prune),
+ OPT_SUBCOMMAND("get-ref", &fn, get_ref),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_notes_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
+ PARSE_OPT_SUBCOMMAND_OPTIONAL);
+ if (fn == list && argc && strcmp(argv[0], "list")) {
+ error(_("unknown subcommand: %s"), argv[0]);
+ usage_with_options(git_notes_usage, options);
+ }
if (override_notes_ref) {
struct strbuf sb = STRBUF_INIT;
@@ -1014,28 +1028,5 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
strbuf_release(&sb);
}
- if (argc < 1 || !strcmp(argv[0], "list"))
- result = list(argc, argv, prefix);
- else if (!strcmp(argv[0], "add"))
- result = add(argc, argv, prefix);
- else if (!strcmp(argv[0], "copy"))
- result = copy(argc, argv, prefix);
- else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
- result = append_edit(argc, argv, prefix);
- else if (!strcmp(argv[0], "show"))
- result = show(argc, argv, prefix);
- else if (!strcmp(argv[0], "merge"))
- result = merge(argc, argv, prefix);
- else if (!strcmp(argv[0], "remove"))
- result = remove_cmd(argc, argv, prefix);
- else if (!strcmp(argv[0], "prune"))
- result = prune(argc, argv, prefix);
- else if (!strcmp(argv[0], "get-ref"))
- result = get_ref(argc, argv, prefix);
- else {
- result = error(_("unknown subcommand: %s"), argv[0]);
- usage_with_options(git_notes_usage, options);
- }
-
- return result ? 1 : 0;
+ return !!fn(argc, argv, prefix);
}
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 8123956847..461c94f1b2 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -227,7 +227,7 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix)
parse_options(argc, argv, prefix, options, reflog_show_usage,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
return cmd_log_reflog(argc, argv, prefix);
}
@@ -408,40 +408,21 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
+ parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
+ OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
+ OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
+ OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
+ OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, reflog_usage,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL |
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
- PARSE_OPT_KEEP_UNKNOWN |
- PARSE_OPT_NO_INTERNAL_HELP);
-
- /*
- * With "git reflog" we default to showing it. !argc is
- * impossible with PARSE_OPT_KEEP_ARGV0.
- */
- if (argc == 1)
- goto log_reflog;
-
- if (!strcmp(argv[1], "-h"))
- usage_with_options(reflog_usage, options);
- else if (*argv[1] == '-')
- goto log_reflog;
-
- if (!strcmp(argv[1], "show"))
- return cmd_reflog_show(argc - 1, argv + 1, prefix);
- else if (!strcmp(argv[1], "expire"))
- return cmd_reflog_expire(argc - 1, argv + 1, prefix);
- else if (!strcmp(argv[1], "delete"))
- return cmd_reflog_delete(argc - 1, argv + 1, prefix);
- else if (!strcmp(argv[1], "exists"))
- return cmd_reflog_exists(argc - 1, argv + 1, prefix);
-
- /*
- * Fall-through for e.g. "git reflog -1", "git reflog master",
- * as well as the plain "git reflog" above goto above.
- */
-log_reflog:
- return cmd_log_reflog(argc, argv, prefix);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
+ if (fn)
+ return fn(argc - 1, argv + 1, prefix);
+ else
+ return cmd_log_reflog(argc, argv, prefix);
}
diff --git a/builtin/remote.c b/builtin/remote.c
index c713463d89..272c7b8d9e 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -150,7 +150,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
return 0;
}
-static int add(int argc, const char **argv)
+static int add(int argc, const char **argv, const char *prefix)
{
int fetch = 0, fetch_tags = TAGS_DEFAULT;
unsigned mirror = MIRROR_NONE;
@@ -177,8 +177,8 @@ static int add(int argc, const char **argv)
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
- 0);
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_add_usage, 0);
if (argc != 2)
usage_with_options(builtin_remote_add_usage, options);
@@ -680,7 +680,7 @@ static void handle_push_default(const char* old_name, const char* new_name)
}
-static int mv(int argc, const char **argv)
+static int mv(int argc, const char **argv, const char *prefix)
{
int show_progress = isatty(2);
struct option options[] = {
@@ -695,7 +695,7 @@ static int mv(int argc, const char **argv)
int i, refs_renamed_nr = 0, refspec_updated = 0;
struct progress *progress = NULL;
- argc = parse_options(argc, argv, NULL, options,
+ argc = parse_options(argc, argv, prefix, options,
builtin_remote_rename_usage, 0);
if (argc != 2)
@@ -844,7 +844,7 @@ static int mv(int argc, const char **argv)
return 0;
}
-static int rm(int argc, const char **argv)
+static int rm(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
OPT_END()
@@ -862,12 +862,14 @@ static int rm(int argc, const char **argv)
cb_data.skipped = &skipped;
cb_data.keep = &known_remotes;
- if (argc != 2)
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_rm_usage, 0);
+ if (argc != 1)
usage_with_options(builtin_remote_rm_usage, options);
- remote = remote_get(argv[1]);
+ remote = remote_get(argv[0]);
if (!remote_is_configured(remote, 1)) {
- error(_("No such remote: '%s'"), argv[1]);
+ error(_("No such remote: '%s'"), argv[0]);
exit(2);
}
@@ -1254,7 +1256,7 @@ static int show_all(void)
return result;
}
-static int show(int argc, const char **argv)
+static int show(int argc, const char **argv, const char *prefix)
{
int no_query = 0, result = 0, query_flag = 0;
struct option options[] = {
@@ -1263,7 +1265,8 @@ static int show(int argc, const char **argv)
};
struct show_info info = SHOW_INFO_INIT;
- argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_show_usage,
0);
if (argc < 1)
@@ -1357,7 +1360,7 @@ static int show(int argc, const char **argv)
return result;
}
-static int set_head(int argc, const char **argv)
+static int set_head(int argc, const char **argv, const char *prefix)
{
int i, opt_a = 0, opt_d = 0, result = 0;
struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
@@ -1370,8 +1373,8 @@ static int set_head(int argc, const char **argv)
N_("delete refs/remotes/<name>/HEAD")),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
- 0);
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_sethead_usage, 0);
if (argc)
strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
@@ -1462,7 +1465,7 @@ static int prune_remote(const char *remote, int dry_run)
return result;
}
-static int prune(int argc, const char **argv)
+static int prune(int argc, const char **argv, const char *prefix)
{
int dry_run = 0, result = 0;
struct option options[] = {
@@ -1470,8 +1473,8 @@ static int prune(int argc, const char **argv)
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
- 0);
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_prune_usage, 0);
if (argc < 1)
usage_with_options(builtin_remote_prune_usage, options);
@@ -1491,7 +1494,7 @@ static int get_remote_default(const char *key, const char *value, void *priv)
return 0;
}
-static int update(int argc, const char **argv)
+static int update(int argc, const char **argv, const char *prefix)
{
int i, prune = -1;
struct option options[] = {
@@ -1503,7 +1506,8 @@ static int update(int argc, const char **argv)
int default_defined = 0;
int retval;
- argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_update_usage,
PARSE_OPT_KEEP_ARGV0);
strvec_push(&fetch_argv, "fetch");
@@ -1574,7 +1578,7 @@ static int set_remote_branches(const char *remotename, const char **branches,
return 0;
}
-static int set_branches(int argc, const char **argv)
+static int set_branches(int argc, const char **argv, const char *prefix)
{
int add_mode = 0;
struct option options[] = {
@@ -1582,7 +1586,7 @@ static int set_branches(int argc, const char **argv)
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options,
+ argc = parse_options(argc, argv, prefix, options,
builtin_remote_setbranches_usage, 0);
if (argc == 0) {
error(_("no remote specified"));
@@ -1593,7 +1597,7 @@ static int set_branches(int argc, const char **argv)
return set_remote_branches(argv[0], argv + 1, add_mode);
}
-static int get_url(int argc, const char **argv)
+static int get_url(int argc, const char **argv, const char *prefix)
{
int i, push_mode = 0, all_mode = 0;
const char *remotename = NULL;
@@ -1607,7 +1611,8 @@ static int get_url(int argc, const char **argv)
N_("return all URLs")),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0);
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_geturl_usage, 0);
if (argc != 1)
usage_with_options(builtin_remote_geturl_usage, options);
@@ -1646,7 +1651,7 @@ static int get_url(int argc, const char **argv)
return 0;
}
-static int set_url(int argc, const char **argv)
+static int set_url(int argc, const char **argv, const char *prefix)
{
int i, push_mode = 0, add_mode = 0, delete_mode = 0;
int matches = 0, negative_matches = 0;
@@ -1667,7 +1672,8 @@ static int set_url(int argc, const char **argv)
N_("delete URLs")),
OPT_END()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage,
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_remote_seturl_usage,
PARSE_OPT_KEEP_ARGV0);
if (add_mode && delete_mode)
@@ -1738,41 +1744,33 @@ out:
int cmd_remote(int argc, const char **argv, const char *prefix)
{
+ parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
+ OPT_SUBCOMMAND("add", &fn, add),
+ OPT_SUBCOMMAND("rename", &fn, mv),
+ OPT_SUBCOMMAND_F("rm", &fn, rm, PARSE_OPT_NOCOMPLETE),
+ OPT_SUBCOMMAND("remove", &fn, rm),
+ OPT_SUBCOMMAND("set-head", &fn, set_head),
+ OPT_SUBCOMMAND("set-branches", &fn, set_branches),
+ OPT_SUBCOMMAND("get-url", &fn, get_url),
+ OPT_SUBCOMMAND("set-url", &fn, set_url),
+ OPT_SUBCOMMAND("show", &fn, show),
+ OPT_SUBCOMMAND("prune", &fn, prune),
+ OPT_SUBCOMMAND("update", &fn, update),
OPT_END()
};
- int result;
argc = parse_options(argc, argv, prefix, options, builtin_remote_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
+ PARSE_OPT_SUBCOMMAND_OPTIONAL);
- if (argc < 1)
- result = show_all();
- else if (!strcmp(argv[0], "add"))
- result = add(argc, argv);
- else if (!strcmp(argv[0], "rename"))
- result = mv(argc, argv);
- else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove"))
- result = rm(argc, argv);
- else if (!strcmp(argv[0], "set-head"))
- result = set_head(argc, argv);
- else if (!strcmp(argv[0], "set-branches"))
- result = set_branches(argc, argv);
- else if (!strcmp(argv[0], "get-url"))
- result = get_url(argc, argv);
- else if (!strcmp(argv[0], "set-url"))
- result = set_url(argc, argv);
- else if (!strcmp(argv[0], "show"))
- result = show(argc, argv);
- else if (!strcmp(argv[0], "prune"))
- result = prune(argc, argv);
- else if (!strcmp(argv[0], "update"))
- result = update(argc, argv);
- else {
- error(_("Unknown subcommand: %s"), argv[0]);
- usage_with_options(builtin_remote_usage, options);
+ if (fn) {
+ return !!fn(argc, argv, prefix);
+ } else {
+ if (argc) {
+ error(_("unknown subcommand: %s"), argv[0]);
+ usage_with_options(builtin_remote_usage, options);
+ }
+ return !!show_all();
}
-
- return result ? 1 : 0;
}
diff --git a/builtin/revert.c b/builtin/revert.c
index 2554f9099c..ee2a0807f0 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -141,7 +141,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
argc = parse_options(argc, argv, NULL, options, usage_str,
PARSE_OPT_KEEP_ARGV0 |
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 086dfee45a..7a1e1fe7c0 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -381,6 +381,7 @@ int cmd_shortlog(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);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index f91e29b56a..287716db68 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -48,7 +48,7 @@ static char const * const builtin_sparse_checkout_list_usage[] = {
NULL
};
-static int sparse_checkout_list(int argc, const char **argv)
+static int sparse_checkout_list(int argc, const char **argv, const char *prefix)
{
static struct option builtin_sparse_checkout_list_options[] = {
OPT_END(),
@@ -60,7 +60,7 @@ static int sparse_checkout_list(int argc, const char **argv)
if (!core_apply_sparse_checkout)
die(_("this worktree is not sparse"));
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_list_options,
builtin_sparse_checkout_list_usage, 0);
@@ -431,7 +431,7 @@ static struct sparse_checkout_init_opts {
int sparse_index;
} init_opts;
-static int sparse_checkout_init(int argc, const char **argv)
+static int sparse_checkout_init(int argc, const char **argv, const char *prefix)
{
struct pattern_list pl;
char *sparse_filename;
@@ -452,7 +452,7 @@ static int sparse_checkout_init(int argc, const char **argv)
init_opts.cone_mode = -1;
init_opts.sparse_index = -1;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_init_options,
builtin_sparse_checkout_init_usage, 0);
@@ -767,7 +767,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_add_options,
builtin_sparse_checkout_add_usage,
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
sanitize_paths(argc, argv, prefix, add_opts.skip_checks);
@@ -813,7 +813,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_set_options,
builtin_sparse_checkout_set_usage,
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index))
return 1;
@@ -843,7 +843,8 @@ static struct sparse_checkout_reapply_opts {
int sparse_index;
} reapply_opts;
-static int sparse_checkout_reapply(int argc, const char **argv)
+static int sparse_checkout_reapply(int argc, const char **argv,
+ const char *prefix)
{
static struct option builtin_sparse_checkout_reapply_options[] = {
OPT_BOOL(0, "cone", &reapply_opts.cone_mode,
@@ -859,7 +860,7 @@ static int sparse_checkout_reapply(int argc, const char **argv)
reapply_opts.cone_mode = -1;
reapply_opts.sparse_index = -1;
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_reapply_options,
builtin_sparse_checkout_reapply_usage, 0);
@@ -876,7 +877,8 @@ static char const * const builtin_sparse_checkout_disable_usage[] = {
NULL
};
-static int sparse_checkout_disable(int argc, const char **argv)
+static int sparse_checkout_disable(int argc, const char **argv,
+ const char *prefix)
{
static struct option builtin_sparse_checkout_disable_options[] = {
OPT_END(),
@@ -895,7 +897,7 @@ static int sparse_checkout_disable(int argc, const char **argv)
* forcibly return to a dense checkout regardless of initial state.
*/
- argc = parse_options(argc, argv, NULL,
+ argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_disable_options,
builtin_sparse_checkout_disable_usage, 0);
@@ -922,39 +924,25 @@ static int sparse_checkout_disable(int argc, const char **argv)
int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
{
- static struct option builtin_sparse_checkout_options[] = {
+ parse_opt_subcommand_fn *fn = NULL;
+ struct option builtin_sparse_checkout_options[] = {
+ OPT_SUBCOMMAND("list", &fn, sparse_checkout_list),
+ OPT_SUBCOMMAND("init", &fn, sparse_checkout_init),
+ OPT_SUBCOMMAND("set", &fn, sparse_checkout_set),
+ OPT_SUBCOMMAND("add", &fn, sparse_checkout_add),
+ OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply),
+ OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable),
OPT_END(),
};
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(builtin_sparse_checkout_usage,
- builtin_sparse_checkout_options);
-
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_options,
- builtin_sparse_checkout_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
+ builtin_sparse_checkout_usage, 0);
git_config(git_default_config, NULL);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
- if (argc > 0) {
- if (!strcmp(argv[0], "list"))
- return sparse_checkout_list(argc, argv);
- if (!strcmp(argv[0], "init"))
- return sparse_checkout_init(argc, argv);
- if (!strcmp(argv[0], "set"))
- return sparse_checkout_set(argc, argv, prefix);
- if (!strcmp(argv[0], "add"))
- return sparse_checkout_add(argc, argv, prefix);
- if (!strcmp(argv[0], "reapply"))
- return sparse_checkout_reapply(argc, argv);
- if (!strcmp(argv[0], "disable"))
- return sparse_checkout_disable(argc, argv);
- }
-
- usage_with_options(builtin_sparse_checkout_usage,
- builtin_sparse_checkout_options);
+ return fn(argc, argv, prefix);
}
diff --git a/builtin/stash.c b/builtin/stash.c
index 30fa101460..1ba24c1173 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -782,7 +782,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
git_stash_list_usage,
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
if (!ref_exists(ref_stash))
return 0;
@@ -873,7 +873,7 @@ static int show_stash(int argc, const char **argv, const char *prefix)
init_revisions(&rev, prefix);
argc = parse_options(argc, argv, prefix, options, git_stash_show_usage,
- PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_KEEP_DASHDASH);
strvec_push(&revision_args, argv[0]);
@@ -979,7 +979,7 @@ static int store_stash(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
git_stash_store_usage,
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
if (argc != 1) {
if (!quiet)
@@ -1739,6 +1739,11 @@ static int push_stash(int argc, const char **argv, const char *prefix,
include_untracked, only_staged);
}
+static int push_stash_unassumed(int argc, const char **argv, const char *prefix)
+{
+ return push_stash(argc, argv, prefix, 0);
+}
+
static int save_stash(int argc, const char **argv, const char *prefix)
{
int keep_index = -1;
@@ -1787,15 +1792,28 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
pid_t pid = getpid();
const char *index_file;
struct strvec args = STRVEC_INIT;
-
+ parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
+ OPT_SUBCOMMAND("apply", &fn, apply_stash),
+ OPT_SUBCOMMAND("clear", &fn, clear_stash),
+ OPT_SUBCOMMAND("drop", &fn, drop_stash),
+ OPT_SUBCOMMAND("pop", &fn, pop_stash),
+ OPT_SUBCOMMAND("branch", &fn, branch_stash),
+ OPT_SUBCOMMAND("list", &fn, list_stash),
+ OPT_SUBCOMMAND("show", &fn, show_stash),
+ OPT_SUBCOMMAND("store", &fn, store_stash),
+ OPT_SUBCOMMAND("create", &fn, create_stash),
+ OPT_SUBCOMMAND("push", &fn, push_stash_unassumed),
+ OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE),
OPT_END()
};
git_config(git_stash_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
- PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
+ PARSE_OPT_SUBCOMMAND_OPTIONAL |
+ PARSE_OPT_KEEP_UNKNOWN_OPT |
+ PARSE_OPT_KEEP_DASHDASH);
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
@@ -1804,33 +1822,10 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
(uintmax_t)pid);
- if (!argc)
- return !!push_stash(0, NULL, prefix, 0);
- else if (!strcmp(argv[0], "apply"))
- return !!apply_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "clear"))
- return !!clear_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "drop"))
- return !!drop_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "pop"))
- return !!pop_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "branch"))
- return !!branch_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "list"))
- return !!list_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "show"))
- return !!show_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "store"))
- return !!store_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "create"))
- return !!create_stash(argc, argv, prefix);
- else if (!strcmp(argv[0], "push"))
- return !!push_stash(argc, argv, prefix, 0);
- else if (!strcmp(argv[0], "save"))
- return !!save_stash(argc, argv, prefix);
- else if (*argv[0] != '-')
- usage_msg_optf(_("unknown subcommand: %s"),
- git_stash_usage, options, argv[0]);
+ if (fn)
+ return !!fn(argc, argv, prefix);
+ else if (!argc)
+ return !!push_stash_unassumed(0, NULL, prefix);
/* Assume 'stash push' */
strvec_push(&args, "push");
diff --git a/builtin/worktree.c b/builtin/worktree.c
index cd62eef240..c6710b2552 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -1112,31 +1112,24 @@ static int repair(int ac, const char **av, const char *prefix)
int cmd_worktree(int ac, const char **av, const char *prefix)
{
+ parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
+ OPT_SUBCOMMAND("add", &fn, add),
+ OPT_SUBCOMMAND("prune", &fn, prune),
+ OPT_SUBCOMMAND("list", &fn, list),
+ OPT_SUBCOMMAND("lock", &fn, lock_worktree),
+ OPT_SUBCOMMAND("unlock", &fn, unlock_worktree),
+ OPT_SUBCOMMAND("move", &fn, move_worktree),
+ OPT_SUBCOMMAND("remove", &fn, remove_worktree),
+ OPT_SUBCOMMAND("repair", &fn, repair),
OPT_END()
};
git_config(git_worktree_config, NULL);
- if (ac < 2)
- usage_with_options(worktree_usage, options);
if (!prefix)
prefix = "";
- if (!strcmp(av[1], "add"))
- return add(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "prune"))
- return prune(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "list"))
- return list(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "lock"))
- return lock_worktree(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "unlock"))
- return unlock_worktree(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "move"))
- return move_worktree(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "remove"))
- return remove_worktree(ac - 1, av + 1, prefix);
- if (!strcmp(av[1], "repair"))
- return repair(ac - 1, av + 1, prefix);
- usage_with_options(worktree_usage, options);
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ return fn(ac, av, prefix);
}
diff --git a/diff.c b/diff.c
index 974626a621..dd68281ba4 100644
--- a/diff.c
+++ b/diff.c
@@ -5661,7 +5661,7 @@ int diff_opt_parse(struct diff_options *options,
ac = parse_options(ac, av, prefix, options->parseopts, NULL,
PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_UNKNOWN_OPT |
PARSE_OPT_NO_INTERNAL_HELP |
PARSE_OPT_ONE_SHOT |
PARSE_OPT_STOP_AT_NON_OPTION);
diff --git a/git.c b/git.c
index 0b9d8ef767..da411c5382 100644
--- a/git.c
+++ b/git.c
@@ -489,14 +489,14 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
static struct cmd_struct commands[] = {
{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "am", cmd_am, RUN_SETUP | NEED_WORK_TREE },
- { "annotate", cmd_annotate, RUN_SETUP | NO_PARSEOPT },
+ { "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive, RUN_SETUP_GENTLY },
{ "bisect--helper", cmd_bisect__helper, RUN_SETUP },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
{ "bugreport", cmd_bugreport, RUN_SETUP_GENTLY },
- { "bundle", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },
+ { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "check-attr", cmd_check_attr, RUN_SETUP },
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
@@ -514,7 +514,7 @@ static struct cmd_struct commands[] = {
{ "column", cmd_column, RUN_SETUP_GENTLY },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-graph", cmd_commit_graph, RUN_SETUP },
- { "commit-tree", cmd_commit_tree, RUN_SETUP | NO_PARSEOPT },
+ { "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG },
{ "count-objects", cmd_count_objects, RUN_SETUP },
{ "credential", cmd_credential, RUN_SETUP_GENTLY | NO_PARSEOPT },
@@ -554,9 +554,9 @@ static struct cmd_struct commands[] = {
{ "ls-files", cmd_ls_files, RUN_SETUP },
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
- { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },
+ { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY },
{ "mailsplit", cmd_mailsplit, NO_PARSEOPT },
- { "maintenance", cmd_maintenance, RUN_SETUP | NO_PARSEOPT },
+ { "maintenance", cmd_maintenance, RUN_SETUP },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
@@ -567,7 +567,7 @@ static struct cmd_struct commands[] = {
{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
{ "merge-tree", cmd_merge_tree, RUN_SETUP },
- { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
+ { "mktag", cmd_mktag, RUN_SETUP },
{ "mktree", cmd_mktree, RUN_SETUP },
{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
@@ -628,7 +628,7 @@ static struct cmd_struct commands[] = {
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
{ "version", cmd_version },
{ "whatchanged", cmd_whatchanged, RUN_SETUP },
- { "worktree", cmd_worktree, RUN_SETUP | NO_PARSEOPT },
+ { "worktree", cmd_worktree, RUN_SETUP },
{ "write-tree", cmd_write_tree, RUN_SETUP },
};
diff --git a/parse-options.c b/parse-options.c
index edf55d3ef5..a1ec932f0f 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -324,6 +324,8 @@ static enum parse_opt_result parse_long_opt(
const char *rest, *long_name = options->long_name;
enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
+ if (options->type == OPTION_SUBCOMMAND)
+ continue;
if (!long_name)
continue;
@@ -332,7 +334,7 @@ again:
rest = NULL;
if (!rest) {
/* abbreviated? */
- if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option &&
@@ -419,6 +421,19 @@ static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
return PARSE_OPT_ERROR;
}
+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;
+}
+
static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
@@ -442,6 +457,7 @@ static void check_typos(const char *arg, const struct option *options)
static void parse_options_check(const struct option *opts)
{
char short_opts[128];
+ void *subcommand_value = NULL;
memset(short_opts, '\0', sizeof(short_opts));
for (; opts->type != OPTION_END; opts++) {
@@ -489,6 +505,14 @@ static void parse_options_check(const struct option *opts)
"Are you using parse_options_step() directly?\n"
"That case is not supported yet.");
break;
+ 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;
default:
; /* ok. (usually accepts an argument) */
}
@@ -499,6 +523,14 @@ static void parse_options_check(const struct option *opts)
BUG_if_bug("invalid 'struct option'");
}
+static int has_subcommands(const struct option *options)
+{
+ for (; options->type != OPTION_END; options++)
+ if (options->type == OPTION_SUBCOMMAND)
+ return 1;
+ return 0;
+}
+
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
int argc, const char **argv, const char *prefix,
const struct option *options,
@@ -515,7 +547,20 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
- if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ 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");
+ }
+ }
+ if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
!(flags & PARSE_OPT_ONE_SHOT))
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
@@ -589,6 +634,7 @@ static int show_gitcomp(const struct option *opts, int show_all)
int nr_noopts = 0;
for (; opts->type != OPTION_END; opts++) {
+ const char *prefix = "--";
const char *suffix = "";
if (!opts->long_name)
@@ -598,6 +644,9 @@ static int show_gitcomp(const struct option *opts, int show_all)
continue;
switch (opts->type) {
+ case OPTION_SUBCOMMAND:
+ prefix = "";
+ break;
case OPTION_GROUP:
continue;
case OPTION_STRING:
@@ -620,7 +669,8 @@ static int show_gitcomp(const struct option *opts, int show_all)
suffix = "=";
if (starts_with(opts->long_name, "no-"))
nr_noopts++;
- printf(" --%s%s", opts->long_name, suffix);
+ printf("%s%s%s%s", opts == original_opts ? "" : " ",
+ prefix, opts->long_name, suffix);
}
show_negated_gitcomp(original_opts, show_all, -1);
show_negated_gitcomp(original_opts, show_all, nr_noopts);
@@ -743,10 +793,38 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
- if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
- return PARSE_OPT_NON_OPTION;
- ctx->out[ctx->cpidx++] = ctx->argv[0];
- continue;
+ 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");
+ }
}
/* lone -h asks for help */
@@ -774,6 +852,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
goto show_usage;
goto unknown;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_HELP:
case PARSE_OPT_COMPLETE:
BUG("parse_short_opt() cannot return these");
@@ -799,6 +878,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
*(char *)ctx->argv[0] = '-';
goto unknown;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_COMPLETE:
case PARSE_OPT_HELP:
BUG("parse_short_opt() cannot return these");
@@ -830,6 +910,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
case PARSE_OPT_HELP:
goto show_usage;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_COMPLETE:
BUG("parse_long_opt() cannot return these");
case PARSE_OPT_DONE:
@@ -839,7 +920,19 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
unknown:
if (ctx->flags & PARSE_OPT_ONE_SHOT)
break;
- if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
+ 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;
+ }
+ if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
ctx->opt = NULL;
@@ -884,7 +977,14 @@ int parse_options(int argc, const char **argv,
case PARSE_OPT_COMPLETE:
exit(0);
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
+ break;
case PARSE_OPT_DONE:
+ if (ctx.has_subcommands &&
+ !(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
+ error(_("need a subcommand"));
+ usage_with_options(usagestr, options);
+ }
break;
case PARSE_OPT_UNKNOWN:
if (ctx.argv[0][1] == '-') {
@@ -1009,6 +1109,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
size_t pos;
int pad;
+ if (opts->type == OPTION_SUBCOMMAND)
+ continue;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
need_newline = 0;
diff --git a/parse-options.h b/parse-options.h
index 685fccac13..b6ef86e0d1 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -11,6 +11,7 @@ enum parse_opt_type {
OPTION_GROUP,
OPTION_NUMBER,
OPTION_ALIAS,
+ OPTION_SUBCOMMAND,
/* options with no arguments */
OPTION_BIT,
OPTION_NEGBIT,
@@ -30,10 +31,11 @@ enum parse_opt_flags {
PARSE_OPT_KEEP_DASHDASH = 1 << 0,
PARSE_OPT_STOP_AT_NON_OPTION = 1 << 1,
PARSE_OPT_KEEP_ARGV0 = 1 << 2,
- PARSE_OPT_KEEP_UNKNOWN = 1 << 3,
+ PARSE_OPT_KEEP_UNKNOWN_OPT = 1 << 3,
PARSE_OPT_NO_INTERNAL_HELP = 1 << 4,
PARSE_OPT_ONE_SHOT = 1 << 5,
PARSE_OPT_SHELL_EVAL = 1 << 6,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL = 1 << 7,
};
enum parse_opt_option_flags {
@@ -56,6 +58,7 @@ enum parse_opt_result {
PARSE_OPT_ERROR = -1, /* must be the same as error() */
PARSE_OPT_DONE = 0, /* fixed so that "return 0" works */
PARSE_OPT_NON_OPTION,
+ PARSE_OPT_SUBCOMMAND,
PARSE_OPT_UNKNOWN
};
@@ -67,6 +70,9 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
const struct option *opt,
const char *arg, int unset);
+typedef int parse_opt_subcommand_fn(int argc, const char **argv,
+ const char *prefix);
+
/*
* `type`::
* holds the type of the option, you must have an OPTION_END last in your
@@ -76,7 +82,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* the character to use as a short option name, '\0' if none.
*
* `long_name`::
- * the long option name, without the leading dashes, NULL if none.
+ * the long option (without the leading dashes) or subcommand name,
+ * NULL if none.
*
* `value`::
* stores pointers to the values to be filled.
@@ -93,7 +100,7 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
*
* `help`::
* the short help associated to what the option does.
- * Must never be NULL (except for OPTION_END).
+ * Must never be NULL (except for OPTION_END and OPTION_SUBCOMMAND).
* OPTION_GROUP uses this pointer to store the group header.
* Should be wrapped by N_() for translation.
*
@@ -109,7 +116,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* is last on the command line. If the option is
* not last it will require an argument.
* Should not be used with PARSE_OPT_OPTARG.
- * PARSE_OPT_NODASH: this option doesn't start with a dash.
+ * PARSE_OPT_NODASH: this option doesn't start with a dash; can only be a
+ * short option and can't accept arguments.
* PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
* (i.e. '<argh>') in the help message.
* Useful for options with multiple parameters.
@@ -130,6 +138,9 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* `ll_callback`::
* pointer to the callback to use for OPTION_LOWLEVEL_CALLBACK
*
+ * `subcommand_fn`::
+ * pointer to a function to use for OPTION_SUBCOMMAND.
+ * It will be put in value when the subcommand is given on the command line.
*/
struct option {
enum parse_opt_type type;
@@ -144,6 +155,7 @@ struct option {
intptr_t defval;
parse_opt_ll_cb *ll_callback;
intptr_t extra;
+ parse_opt_subcommand_fn *subcommand_fn;
};
#define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \
@@ -205,6 +217,14 @@ struct option {
#define OPT_ALIAS(s, l, source_long_name) \
{ OPTION_ALIAS, (s), (l), (source_long_name) }
+#define OPT_SUBCOMMAND_F(l, v, fn, f) { \
+ .type = OPTION_SUBCOMMAND, \
+ .long_name = (l), \
+ .value = (v), \
+ .flags = (f), \
+ .subcommand_fn = (fn) }
+#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0)
+
/*
* parse_options() will filter out the processed options and leave the
* non-option arguments in argv[]. argv0 is assumed program name and
@@ -294,6 +314,7 @@ struct parse_opt_ctx_t {
int argc, cpidx, total;
const char *opt;
enum parse_opt_flags flags;
+ unsigned has_subcommands;
const char *prefix;
const char **alias_groups; /* must be in groups of 3 elements! */
struct option *updated_options;
diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c
index 48d3cf6692..aa0ad45851 100644
--- a/t/helper/test-parse-options.c
+++ b/t/helper/test-parse-options.c
@@ -192,3 +192,130 @@ int cmd__parse_options(int argc, const char **argv)
return ret;
}
+
+static void print_args(int argc, const char **argv)
+{
+ for (int i = 0; i < argc; i++)
+ printf("arg %02d: %s\n", i, argv[i]);
+}
+
+static int parse_options_flags__cmd(int argc, const char **argv,
+ enum parse_opt_flags test_flags)
+{
+ const char *usage[] = {
+ "<...> cmd [options]",
+ NULL
+ };
+ int opt = 0;
+ const struct option options[] = {
+ OPT_INTEGER('o', "opt", &opt, "an integer option"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, NULL, options, usage, test_flags);
+
+ printf("opt: %d\n", opt);
+ print_args(argc, argv);
+
+ return 0;
+}
+
+static enum parse_opt_flags test_flags = 0;
+static const struct option test_flag_options[] = {
+ OPT_GROUP("flag-options:"),
+ OPT_BIT(0, "keep-dashdash", &test_flags,
+ "pass PARSE_OPT_KEEP_DASHDASH to parse_options()",
+ PARSE_OPT_KEEP_DASHDASH),
+ OPT_BIT(0, "stop-at-non-option", &test_flags,
+ "pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()",
+ PARSE_OPT_STOP_AT_NON_OPTION),
+ OPT_BIT(0, "keep-argv0", &test_flags,
+ "pass PARSE_OPT_KEEP_ARGV0 to parse_options()",
+ PARSE_OPT_KEEP_ARGV0),
+ OPT_BIT(0, "keep-unknown-opt", &test_flags,
+ "pass PARSE_OPT_KEEP_UNKNOWN_OPT to parse_options()",
+ PARSE_OPT_KEEP_UNKNOWN_OPT),
+ OPT_BIT(0, "no-internal-help", &test_flags,
+ "pass PARSE_OPT_NO_INTERNAL_HELP to parse_options()",
+ PARSE_OPT_NO_INTERNAL_HELP),
+ OPT_BIT(0, "subcommand-optional", &test_flags,
+ "pass PARSE_OPT_SUBCOMMAND_OPTIONAL to parse_options()",
+ PARSE_OPT_SUBCOMMAND_OPTIONAL),
+ OPT_END()
+};
+
+int cmd__parse_options_flags(int argc, const char **argv)
+{
+ const char *usage[] = {
+ "test-tool parse-options-flags [flag-options] cmd [options]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, NULL, test_flag_options, usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc == 0 || strcmp(argv[0], "cmd")) {
+ error("'cmd' is mandatory");
+ usage_with_options(usage, test_flag_options);
+ }
+
+ return parse_options_flags__cmd(argc, argv, test_flags);
+}
+
+static int subcmd_one(int argc, const char **argv, const char *prefix)
+{
+ printf("fn: subcmd_one\n");
+ print_args(argc, argv);
+ return 0;
+}
+
+static int subcmd_two(int argc, const char **argv, const char *prefix)
+{
+ printf("fn: subcmd_two\n");
+ print_args(argc, argv);
+ return 0;
+}
+
+static int parse_subcommand__cmd(int argc, const char **argv,
+ enum parse_opt_flags test_flags)
+{
+ const char *usage[] = {
+ "<...> cmd subcmd-one",
+ "<...> cmd subcmd-two",
+ NULL
+ };
+ parse_opt_subcommand_fn *fn = NULL;
+ int opt = 0;
+ struct option options[] = {
+ OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one),
+ OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two),
+ OPT_INTEGER('o', "opt", &opt, "an integer option"),
+ OPT_END()
+ };
+
+ if (test_flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)
+ fn = subcmd_one;
+ argc = parse_options(argc, argv, NULL, options, usage, test_flags);
+
+ printf("opt: %d\n", opt);
+
+ return fn(argc, argv, NULL);
+}
+
+int cmd__parse_subcommand(int argc, const char **argv)
+{
+ const char *usage[] = {
+ "test-tool parse-subcommand [flag-options] cmd <subcommand>",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, NULL, test_flag_options, usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc == 0 || strcmp(argv[0], "cmd")) {
+ error("'cmd' is mandatory");
+ usage_with_options(usage, test_flag_options);
+ }
+
+ return parse_subcommand__cmd(argc, argv, test_flags);
+}
diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c
index 28e905afc3..824e5c0a95 100644
--- a/t/helper/test-serve-v2.c
+++ b/t/helper/test-serve-v2.c
@@ -24,7 +24,7 @@ int cmd__serve_v2(int argc, const char **argv)
/* ignore all unknown cmdline switches for now */
argc = parse_options(argc, argv, prefix, options, serve_usage,
PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_UNKNOWN_OPT);
if (advertise_capabilities)
protocol_v2_advertise_capabilities();
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index d6a560f832..8005588679 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -51,7 +51,9 @@ static struct test_cmd cmds[] = {
{ "online-cpus", cmd__online_cpus },
{ "pack-mtimes", cmd__pack_mtimes },
{ "parse-options", cmd__parse_options },
+ { "parse-options-flags", cmd__parse_options_flags },
{ "parse-pathspec-file", cmd__parse_pathspec_file },
+ { "parse-subcommand", cmd__parse_subcommand },
{ "partial-clone", cmd__partial_clone },
{ "path-utils", cmd__path_utils },
{ "pcre2-config", cmd__pcre2_config },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 21a91b1019..a432cc77d9 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -41,7 +41,9 @@ int cmd__oidtree(int argc, const char **argv);
int cmd__online_cpus(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__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);
int cmd__path_utils(int argc, const char **argv);
int cmd__pcre2_config(int argc, const char **argv);
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index ed2fb620a9..b19b8d3486 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -456,4 +456,259 @@ test_expect_success '--end-of-options treats remainder as args' '
--end-of-options --verbose
'
+test_expect_success 'KEEP_DASHDASH works' '
+ test-tool parse-options-flags --keep-dashdash cmd --opt=1 -- --opt=2 --unknown >actual &&
+ cat >expect <<-\EOF &&
+ opt: 1
+ arg 00: --
+ arg 01: --opt=2
+ arg 02: --unknown
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'KEEP_ARGV0 works' '
+ test-tool parse-options-flags --keep-argv0 cmd arg0 --opt=3 >actual &&
+ cat >expect <<-\EOF &&
+ opt: 3
+ arg 00: cmd
+ arg 01: arg0
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'STOP_AT_NON_OPTION works' '
+ test-tool parse-options-flags --stop-at-non-option cmd --opt=4 arg0 --opt=5 --unknown >actual &&
+ cat >expect <<-\EOF &&
+ opt: 4
+ arg 00: arg0
+ arg 01: --opt=5
+ arg 02: --unknown
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'KEEP_UNKNOWN_OPT works' '
+ test-tool parse-options-flags --keep-unknown-opt cmd --unknown=1 --opt=6 -u2 >actual &&
+ cat >expect <<-\EOF &&
+ opt: 6
+ arg 00: --unknown=1
+ arg 01: -u2
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'NO_INTERNAL_HELP works for -h' '
+ test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd -h 2>err &&
+ cat err &&
+ grep "^error: unknown switch \`h$SQ" err &&
+ grep "^usage: " err
+'
+
+for help_opt in help help-all
+do
+ test_expect_success "NO_INTERNAL_HELP works for --$help_opt" "
+ test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd --$help_opt 2>err &&
+ cat err &&
+ grep '^error: unknown option \`'$help_opt\' err &&
+ grep '^usage: ' err
+ "
+done
+
+test_expect_success 'KEEP_UNKNOWN_OPT | NO_INTERNAL_HELP works' '
+ test-tool parse-options-flags --keep-unknown-opt --no-internal-help cmd -h --help --help-all >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ arg 00: -h
+ arg 01: --help
+ arg 02: --help-all
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - no subcommand shows error and usage' '
+ test_expect_code 129 test-tool parse-subcommand cmd 2>err &&
+ grep "^error: need a subcommand" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - subcommand after -- shows error and usage' '
+ test_expect_code 129 test-tool parse-subcommand cmd -- subcmd-one 2>err &&
+ grep "^error: need a subcommand" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - subcommand after --end-of-options shows error and usage' '
+ test_expect_code 129 test-tool parse-subcommand cmd --end-of-options subcmd-one 2>err &&
+ grep "^error: need a subcommand" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - unknown subcommand shows error and usage' '
+ test_expect_code 129 test-tool parse-subcommand cmd nope 2>err &&
+ grep "^error: unknown subcommand: \`nope$SQ" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - subcommands cannot be abbreviated' '
+ test_expect_code 129 test-tool parse-subcommand cmd subcmd-o 2>err &&
+ grep "^error: unknown subcommand: \`subcmd-o$SQ$" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - no negated subcommands' '
+ test_expect_code 129 test-tool parse-subcommand cmd no-subcmd-one 2>err &&
+ grep "^error: unknown subcommand: \`no-subcmd-one$SQ" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - simple' '
+ test-tool parse-subcommand cmd subcmd-two >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_two
+ arg 00: subcmd-two
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - stop parsing at the first subcommand' '
+ test-tool parse-subcommand cmd --opt=1 subcmd-two subcmd-one --opt=2 >actual &&
+ cat >expect <<-\EOF &&
+ opt: 1
+ fn: subcmd_two
+ arg 00: subcmd-two
+ arg 01: subcmd-one
+ arg 02: --opt=2
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - KEEP_ARGV0' '
+ test-tool parse-subcommand --keep-argv0 cmd subcmd-two >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_two
+ arg 00: cmd
+ arg 01: subcmd-two
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given' '
+ test-tool parse-subcommand --subcommand-optional cmd >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + given subcommand' '
+ test-tool parse-subcommand --subcommand-optional cmd subcmd-two branch file >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_two
+ arg 00: subcmd-two
+ arg 01: branch
+ arg 02: file
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + unknown dashless args' '
+ test-tool parse-subcommand --subcommand-optional cmd branch file >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: branch
+ arg 01: file
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + unknown option' '
+ test_expect_code 129 test-tool parse-subcommand --subcommand-optional cmd --subcommand-opt 2>err &&
+ grep "^error: unknown option" err &&
+ grep ^usage: err
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + subcommand not given + unknown option' '
+ test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: --subcommand-opt
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + subcommand ignored after unknown option' '
+ test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt subcmd-two >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: --subcommand-opt
+ arg 01: subcmd-two
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + command and subcommand options cannot be mixed' '
+ test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt branch --opt=1 >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: --subcommand-opt
+ arg 01: branch
+ arg 02: --opt=1
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT | KEEP_ARGV0' '
+ test-tool parse-subcommand --subcommand-optional --keep-unknown-opt --keep-argv0 cmd --subcommand-opt branch >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: cmd
+ arg 01: --subcommand-opt
+ arg 02: branch
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT | KEEP_DASHDASH' '
+ test-tool parse-subcommand --subcommand-optional --keep-unknown-opt --keep-dashdash cmd -- --subcommand-opt file >actual &&
+ cat >expect <<-\EOF &&
+ opt: 0
+ fn: subcmd_one
+ arg 00: --
+ arg 01: --subcommand-opt
+ arg 02: file
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommand - completion helper' '
+ test-tool parse-subcommand cmd --git-completion-helper >actual &&
+ echo "subcmd-one subcmd-two --opt= --no-opt" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'subcommands are incompatible with STOP_AT_NON_OPTION' '
+ test_must_fail test-tool parse-subcommand --stop-at-non-option cmd subcmd-one 2>err &&
+ grep ^BUG err
+'
+
+test_expect_success 'subcommands are incompatible with KEEP_UNKNOWN_OPT unless in combination with SUBCOMMAND_OPTIONAL' '
+ test_must_fail test-tool parse-subcommand --keep-unknown-opt cmd subcmd-two 2>err &&
+ grep ^BUG err
+'
+
+test_expect_success 'subcommands are incompatible with KEEP_DASHDASH unless in combination with SUBCOMMAND_OPTIONAL' '
+ test_must_fail test-tool parse-subcommand --keep-dashdash cmd subcmd-two 2>err &&
+ grep ^BUG err
+'
+
test_done
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index d742be8840..3288aaec7d 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -505,6 +505,11 @@ test_expect_success 'list notes with "git notes"' '
test_cmp expect actual
'
+test_expect_success '"git notes" without subcommand does not take arguments' '
+ test_expect_code 129 git notes HEAD^^ 2>err &&
+ grep "^error: unknown subcommand" err
+'
+
test_expect_success 'list specific note with "git notes list <object>"' '
git rev-parse refs/notes/commits:$commit_3 >expect &&
git notes list HEAD^^ >actual &&
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 2a4c3fd61c..376cc8f4ab 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -25,7 +25,7 @@ test_expect_success 'usage on main command -h emits a summary of subcommands' '
grep -F "or: git stash show" usage
'
-test_expect_failure 'usage for subcommands should emit subcommand usage' '
+test_expect_success 'usage for subcommands should emit subcommand usage' '
test_expect_code 129 git stash push -h >usage &&
grep -F "usage: git stash [push" usage
'
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 1b0cd82359..049c5fc8ea 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -12,12 +12,12 @@ test_expect_success 'usage' '
test_expect_success 'usage shown without sub-command' '
test_expect_code 129 git commit-graph 2>err &&
- ! grep error: err
+ grep usage: err
'
test_expect_success 'usage shown with an error on unknown sub-command' '
cat >expect <<-\EOF &&
- error: unrecognized subcommand: unknown
+ error: unknown subcommand: `unknown'\''
EOF
test_expect_code 129 git commit-graph unknown 2>stderr &&
grep error stderr >actual &&
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 6c7370f87f..9006196ac6 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -241,6 +241,26 @@ test_expect_success 'add invalid foreign_vcs remote' '
test_cmp expect actual
'
+test_expect_success 'without subcommand' '
+ echo origin >expect &&
+ git -C test remote >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'without subcommand accepts -v' '
+ cat >expect <<-EOF &&
+ origin $(pwd)/one (fetch)
+ origin $(pwd)/one (push)
+ EOF
+ git -C test remote -v >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'without subcommand does not take arguments' '
+ test_expect_code 129 git -C test remote origin 2>err &&
+ grep "^error: unknown subcommand:" err
+'
+
cat >test/expect <<EOF
* remote origin
Fetch URL: $(pwd)/one
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 62ed694a40..2724a44fe3 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -32,11 +32,13 @@ test_systemd_analyze_verify () {
}
test_expect_success 'help text' '
- test_expect_code 129 git maintenance -h 2>err &&
- test_i18ngrep "usage: git maintenance <subcommand>" err &&
- test_expect_code 128 git maintenance barf 2>err &&
- test_i18ngrep "invalid subcommand: barf" err &&
+ test_expect_code 129 git maintenance -h >actual &&
+ test_i18ngrep "usage: git maintenance <subcommand>" actual &&
+ test_expect_code 129 git maintenance barf 2>err &&
+ test_i18ngrep "unknown subcommand: \`barf'\''" err &&
+ test_i18ngrep "usage: git maintenance" err &&
test_expect_code 129 git maintenance 2>err &&
+ test_i18ngrep "error: need a subcommand" err &&
test_i18ngrep "usage: git maintenance" err
'