fa83cc834d
Several Git commands have subcommands to implement mutually exclusive "operation modes", and they usually parse their subcommand argument with a bunch of if-else if statements. Teach parse-options to handle subcommands as well, which will result in shorter and simpler code with consistent error handling and error messages on unknown or missing subcommand, and it will also make possible for our Bash completion script to handle subcommands programmatically. The approach is guided by the following observations: - Most subcommands [1] are implemented in dedicated functions, and most of those functions [2] either have a signature matching the 'int cmd_foo(int argc, const char **argc, const char *prefix)' signature of builtin commands or can be trivially converted to that signature, because they miss only that last prefix parameter or have no parameters at all. - Subcommand arguments only have long form, and they have no double dash prefix, no negated form, and no description, and they don't take any arguments, and can't be abbreviated. - There must be exactly one subcommand among the arguments, or zero if the command has a default operation mode. - All arguments following the subcommand are considered to be arguments of the subcommand, and, conversely, arguments meant for the subcommand may not preceed the subcommand. So in the end subcommand declaration and parsing would look something like this: parse_opt_subcommand_fn *fn = NULL; struct option builtin_commit_graph_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), N_("the object directory to store the graph")), OPT_SUBCOMMAND("verify", &fn, graph_verify), OPT_SUBCOMMAND("write", &fn, graph_write), OPT_END(), }; argc = parse_options(argc, argv, prefix, options, builtin_commit_graph_usage, 0); return fn(argc, argv, prefix); Here each OPT_SUBCOMMAND specifies the name of the subcommand and the function implementing it, and the address of the same 'fn' subcommand function pointer. parse_options() then processes the arguments until it finds the first argument matching one of the subcommands, sets 'fn' to the function associated with that subcommand, and returns, leaving the rest of the arguments unprocessed. If none of the listed subcommands is found among the arguments, parse_options() will show usage and abort. If a command has a default operation mode, 'fn' should be initialized to the function implementing that mode, and parse_options() should be invoked with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag. In this case parse_options() won't error out when not finding any subcommands, but will return leaving 'fn' unchanged. Note that if that default operation mode has any --options, then the PARSE_OPT_KEEP_UNKNOWN_OPT flag is necessary as well (otherwise parse_options() would error out upon seeing the unknown option meant to the default operation mode). Some thoughts about the implementation: - The same pointer to 'fn' must be specified as 'value' for each OPT_SUBCOMMAND, because there can be only one set of mutually exclusive subcommands; parse_options() will BUG() otherwise. There are other ways to tell parse_options() where to put the function associated with the subcommand given on the command line, but I didn't like them: - Change parse_options()'s signature by adding a pointer to subcommand function to be set to the function associated with the given subcommand, affecting all callsites, even those that don't have subcommands. - Introduce a specific parse_options_and_subcommand() variant with that extra funcion parameter. - I decided against automatically calling the subcommand function from within parse_options(), because: - There are commands that have to perform additional actions after option parsing but before calling the function implementing the specified subcommand. - The return code of the subcommand is usually the return code of the git command, but preserving the return code of the automatically called subcommand function would have made the API awkward. - Also add a OPT_SUBCOMMAND_F() variant to allow specifying an option flag: we have two subcommands that are purposefully excluded from completion ('git remote rm' and 'git stash save'), so they'll have to be specified with the PARSE_OPT_NOCOMPLETE flag. - Some of the 'parse_opt_flags' don't make sense with subcommands, and using them is probably just an oversight or misunderstanding. Therefore parse_options() will BUG() when invoked with any of the following flags while the options array contains at least one OPT_SUBCOMMAND: - PARSE_OPT_KEEP_DASHDASH: parse_options() stops parsing arguments when encountering a "--" argument, so it doesn't make sense to expect and keep one before a subcommand, because it would prevent the parsing of the subcommand. However, this flag is allowed in combination with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag, because the double dash might be meaningful for the command's default operation mode, e.g. to disambiguate refs and pathspecs. - PARSE_OPT_STOP_AT_NON_OPTION: As its name suggests, this flag tells parse_options() to stop as soon as it encouners a non-option argument, but subcommands are by definition not options... so how could they be parsed, then?! - PARSE_OPT_KEEP_UNKNOWN: This flag can be used to collect any unknown --options and then pass them to a different command or subsystem. Surely if a command has subcommands, then this functionality should rather be delegated to one of those subcommands, and not performed by the command itself. However, this flag is allowed in combination with the PARSE_OPT_SUBCOMMAND_OPTIONAL flag, making possible to pass --options to the default operation mode. - If the command with subcommands has a default operation mode, then all arguments to the command must preceed the arguments of the subcommand. AFAICT we don't have any commands where this makes a difference, because in those commands either only the command accepts any arguments ('notes' and 'remote'), or only the default subcommand ('reflog' and 'stash'), but never both. - The 'argv' array passed to subcommand functions currently starts with the name of the subcommand. Keep this behavior. AFAICT no subcommand functions depend on the actual content of 'argv[0]', but the parse_options() call handling their options expects that the options start at argv[1]. - To support handling subcommands programmatically in our Bash completion script, 'git cmd --git-completion-helper' will now list both subcommands and regular --options, if any. This means that the completion script will have to separate subcommands (i.e. words without a double dash prefix) from --options on its own, but that's rather easy to do, and it's not much work either, because the number of subcommands a command might have is rather low, and those commands accept only a single --option or none at all. An alternative would be to introduce a separate option that lists only subcommands, but then the completion script would need not one but two git invocations and command substitutions for commands with subcommands. Note that this change doesn't affect the behavior of our Bash completion script, because when completing the --option of a command with subcommands, e.g. for 'git notes --<TAB>', then all subcommands will be filtered out anyway, as none of them will match the word to be completed starting with that double dash prefix. [1] Except 'git rerere', because many of its subcommands are implemented in the bodies of the if-else if statements parsing the command's subcommand argument. [2] Except 'credential', 'credential-store' and 'fsmonitor--daemon', because some of the functions implementing their subcommands take special parameters. Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
89 lines
4.0 KiB
C
89 lines
4.0 KiB
C
#ifndef TEST_TOOL_H
|
|
#define TEST_TOOL_H
|
|
|
|
#define USE_THE_INDEX_COMPATIBILITY_MACROS
|
|
#include "git-compat-util.h"
|
|
|
|
int cmd__advise_if_enabled(int argc, const char **argv);
|
|
int cmd__bitmap(int argc, const char **argv);
|
|
int cmd__bloom(int argc, const char **argv);
|
|
int cmd__chmtime(int argc, const char **argv);
|
|
int cmd__config(int argc, const char **argv);
|
|
int cmd__crontab(int argc, const char **argv);
|
|
int cmd__csprng(int argc, const char **argv);
|
|
int cmd__ctype(int argc, const char **argv);
|
|
int cmd__date(int argc, const char **argv);
|
|
int cmd__delta(int argc, const char **argv);
|
|
int cmd__dir_iterator(int argc, const char **argv);
|
|
int cmd__drop_caches(int argc, const char **argv);
|
|
int cmd__dump_cache_tree(int argc, const char **argv);
|
|
int cmd__dump_fsmonitor(int argc, const char **argv);
|
|
int cmd__dump_split_index(int argc, const char **argv);
|
|
int cmd__dump_untracked_cache(int argc, const char **argv);
|
|
int cmd__dump_reftable(int argc, const char **argv);
|
|
int cmd__example_decorate(int argc, const char **argv);
|
|
int cmd__fast_rebase(int argc, const char **argv);
|
|
int cmd__fsmonitor_client(int argc, const char **argv);
|
|
int cmd__genrandom(int argc, const char **argv);
|
|
int cmd__genzeros(int argc, const char **argv);
|
|
int cmd__getcwd(int argc, const char **argv);
|
|
int cmd__hashmap(int argc, const char **argv);
|
|
int cmd__hash_speed(int argc, const char **argv);
|
|
int cmd__hexdump(int argc, const char **argv);
|
|
int cmd__index_version(int argc, const char **argv);
|
|
int cmd__json_writer(int argc, const char **argv);
|
|
int cmd__lazy_init_name_hash(int argc, const char **argv);
|
|
int cmd__match_trees(int argc, const char **argv);
|
|
int cmd__mergesort(int argc, const char **argv);
|
|
int cmd__mktemp(int argc, const char **argv);
|
|
int cmd__oidmap(int argc, const char **argv);
|
|
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);
|
|
int cmd__pkt_line(int argc, const char **argv);
|
|
int cmd__prio_queue(int argc, const char **argv);
|
|
int cmd__proc_receive(int argc, const char **argv);
|
|
int cmd__progress(int argc, const char **argv);
|
|
int cmd__reach(int argc, const char **argv);
|
|
int cmd__read_cache(int argc, const char **argv);
|
|
int cmd__read_graph(int argc, const char **argv);
|
|
int cmd__read_midx(int argc, const char **argv);
|
|
int cmd__ref_store(int argc, const char **argv);
|
|
int cmd__reftable(int argc, const char **argv);
|
|
int cmd__regex(int argc, const char **argv);
|
|
int cmd__repository(int argc, const char **argv);
|
|
int cmd__revision_walking(int argc, const char **argv);
|
|
int cmd__run_command(int argc, const char **argv);
|
|
int cmd__scrap_cache_tree(int argc, const char **argv);
|
|
int cmd__serve_v2(int argc, const char **argv);
|
|
int cmd__sha1(int argc, const char **argv);
|
|
int cmd__oid_array(int argc, const char **argv);
|
|
int cmd__sha256(int argc, const char **argv);
|
|
int cmd__sigchain(int argc, const char **argv);
|
|
int cmd__simple_ipc(int argc, const char **argv);
|
|
int cmd__strcmp_offset(int argc, const char **argv);
|
|
int cmd__string_list(int argc, const char **argv);
|
|
int cmd__submodule_config(int argc, const char **argv);
|
|
int cmd__submodule_nested_repo_config(int argc, const char **argv);
|
|
int cmd__subprocess(int argc, const char **argv);
|
|
int cmd__trace2(int argc, const char **argv);
|
|
int cmd__userdiff(int argc, const char **argv);
|
|
int cmd__urlmatch_normalization(int argc, const char **argv);
|
|
int cmd__xml_encode(int argc, const char **argv);
|
|
int cmd__wildmatch(int argc, const char **argv);
|
|
#ifdef GIT_WINDOWS_NATIVE
|
|
int cmd__windows_named_pipe(int argc, const char **argv);
|
|
#endif
|
|
int cmd__write_cache(int argc, const char **argv);
|
|
|
|
int cmd_hash_impl(int ac, const char **av, int algo);
|
|
|
|
#endif
|