From: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com> To: gitster@pobox.com Cc: Johannes.Schindelin@gmx.de, avarab@gmail.com, git@vger.kernel.org, liu.denton@gmail.com, pclouds@gmail.com Subject: [PATCH] parse-options: don't emit "ambiguous option" for aliases Date: Mon, 22 Apr 2019 19:22:50 +0700 Message-ID: <20190422122250.15248-1-pclouds@gmail.com> (raw) In-Reply-To: <xmqq1s1yfw24.fsf@gitster-ct.c.googlers.com> Change the option parsing machinery so that e.g. "clone --recurs ..." doesn't error out because "clone" understands both "--recursive" and "--recurse-submodules" to mean the same thing. Initially "clone" just understood --recursive until the --recurses-submodules alias was added in ccdd3da652 ("clone: Add the --recurse-submodules option as alias for --recursive", 2010-11-04). Since bb62e0a99f ("clone: teach --recurse-submodules to optionally take a pathspec", 2017-03-17) the longer form has been promoted to the default. But due to the way the options parsing machinery works this resulted in the rather absurd situation of: $ git clone --recurs [...] error: ambiguous option: recurs (could be --recursive or --recurse-submodules) Add OPT_ALIAS() to express this linke between two options and use it in git-clone. A big chunk of code is actually from Junio C Hamano. The test case (and most of the commit message) is from Ævar Arnfjörð Bjarmason. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> --- I'm not sure if Junio is an evil genius who lures me with his helpful suggestions. Either way I ended up spending a couple hours writing and rewriting and finally decided all other options were too ugly. And his suggestions were mostly there already. So here's OPT_ALIAS(). I avoid rewriting all the option iteration code by simply making a copy of options[] array to use, then free it afterwards. Not the best, but at least it could be easily removed if we end up not want OPT_ALIAS() after all. This probably needs signoff from both Ævar and Junio, if accepted. builtin/clone.c | 5 +- parse-options.c | 130 +++++++++++++++++++++++++++++++++++++-- parse-options.h | 6 ++ t/t0040-parse-options.sh | 5 ++ 4 files changed, 136 insertions(+), 10 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 50bde99618..703b7247ad 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -98,10 +98,7 @@ static struct option builtin_clone_options[] = { N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules, - N_("pathspec"), N_("initialize submodules in the clone"), - PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb, - (intptr_t)"." }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, N_("pathspec"), N_("initialize submodules in the clone"), PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, diff --git a/parse-options.c b/parse-options.c index acc3a93660..7ec768e376 100644 --- a/parse-options.c +++ b/parse-options.c @@ -261,6 +261,35 @@ static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p, return PARSE_OPT_UNKNOWN; } +static int has_string(const char *it, const char **array) +{ + while (*array) + if (!strcmp(it, *(array++))) + return 1; + return 0; +} + +static int is_alias(struct parse_opt_ctx_t *ctx, + const struct option *one_opt, + const struct option *another_opt) +{ + const char **group; + + if (!ctx->alias_groups) + return 0; + + if (!one_opt->long_name || !another_opt->long_name) + return 0; + + for (group = ctx->alias_groups; *group; group += 3) { + /* it and other are from the same family? */ + if (has_string(one_opt->long_name, group) && + has_string(another_opt->long_name, group)) + return 1; + } + return 0; +} + static enum parse_opt_result parse_long_opt( struct parse_opt_ctx_t *p, const char *arg, const struct option *options) @@ -296,7 +325,8 @@ static enum parse_opt_result parse_long_opt( if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && !strncmp(long_name, arg, arg_end - arg)) { is_abbreviated: - if (abbrev_option) { + if (abbrev_option && + !is_alias(p, abbrev_option, options)) { /* * If this is abbreviated, it is * ambiguous. So when there is no @@ -445,6 +475,10 @@ static void parse_options_check(const struct option *opts) if (opts->callback) BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback"); break; + case OPTION_ALIAS: + BUG("OPT_ALIAS() should not remain at this point. " + "Are you using parse_options_step() directly?\n" + "That case is not supported yet."); default: ; /* ok. (usually accepts an argument) */ } @@ -456,11 +490,10 @@ static void parse_options_check(const struct option *opts) exit(128); } -void parse_options_start(struct parse_opt_ctx_t *ctx, - int argc, const char **argv, const char *prefix, - const struct option *options, int flags) +static void parse_options_start_1(struct parse_opt_ctx_t *ctx, + int argc, const char **argv, const char *prefix, + const struct option *options, int flags) { - memset(ctx, 0, sizeof(*ctx)); ctx->argc = argc; ctx->argv = argv; if (!(flags & PARSE_OPT_ONE_SHOT)) { @@ -482,6 +515,14 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, parse_options_check(options); } +void parse_options_start(struct parse_opt_ctx_t *ctx, + int argc, const char **argv, const char *prefix, + const struct option *options, int flags) +{ + memset(ctx, 0, sizeof(*ctx)); + parse_options_start_1(ctx, argc, argv, prefix, options, flags); +} + static void show_negated_gitcomp(const struct option *opts, int nr_noopts) { int printed_dashdash = 0; @@ -574,6 +615,70 @@ static int show_gitcomp(struct parse_opt_ctx_t *ctx, return PARSE_OPT_COMPLETE; } +/* + * Scan and may produce a new option[] array, which should be used + * instead of the original 'options'. + * + * Right now this is only used to preprocess and substitue + * OPTION_ALIAS. + */ +static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, + const struct option *options) +{ + struct option *newopt; + int i, nr, alias; + int nr_aliases = 0; + + for (nr = 0; options[nr].type != OPTION_END; nr++) { + if (options[nr].type == OPTION_ALIAS) + nr_aliases++; + } + + if (!nr_aliases) + return NULL; + + ALLOC_ARRAY(newopt, nr + 1); + COPY_ARRAY(newopt, options, nr + 1); + + /* each alias has two string pointers and NULL */ + CALLOC_ARRAY(ctx->alias_groups, 3 * (nr_aliases + 1)); + + for (alias = 0, i = 0; i < nr; i++) { + const char *source; + int j; + + if (newopt[i].type != OPTION_ALIAS) + continue; + + if (!newopt[i].long_name) + BUG("An alias must have long option name"); + + source = newopt[i].value; + + for (j = 0; j < nr; j++) { + const char *name = options[j].long_name; + + if (!name || strcmp(name, source)) + continue; + + if (options[j].type == OPTION_ALIAS) + BUG("No please. Nested aliases are not supported."); + + memcpy(newopt + i, options + j, sizeof(*newopt)); + break; + } + + if (j == nr) + BUG("could not find source option '%s' of alias '%s'", + source, newopt[i].long_name); + ctx->alias_groups[alias * 3 + 0] = newopt[i].long_name; + ctx->alias_groups[alias * 3 + 1] = options[j].long_name; + alias++; + } + + return newopt; +} + static int usage_with_options_internal(struct parse_opt_ctx_t *, const char * const *, const struct option *, int, int); @@ -713,11 +818,16 @@ int parse_options(int argc, const char **argv, const char *prefix, int flags) { struct parse_opt_ctx_t ctx; + struct option *real_options; disallow_abbreviated_options = git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0); - parse_options_start(&ctx, argc, argv, prefix, options, flags); + memset(&ctx, 0, sizeof(ctx)); + real_options = preprocess_options(&ctx, options); + if (real_options) + options = real_options; + parse_options_start_1(&ctx, argc, argv, prefix, options, flags); switch (parse_options_step(&ctx, options, usagestr)) { case PARSE_OPT_HELP: case PARSE_OPT_ERROR: @@ -740,6 +850,8 @@ int parse_options(int argc, const char **argv, const char *prefix, } precompose_argv(argc, argv); + free(real_options); + free(ctx.alias_groups); return parse_options_end(&ctx); } @@ -834,6 +946,12 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, fputc('\n', outfile); pad = USAGE_OPTS_WIDTH; } + if (opts->type == OPTION_ALIAS) { + fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + fprintf_ln(outfile, _("alias of --%s"), + (const char *)opts->value); + continue; + } fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help)); } fputc('\n', outfile); diff --git a/parse-options.h b/parse-options.h index 74cce4e7fc..e6b4a8d1c7 100644 --- a/parse-options.h +++ b/parse-options.h @@ -7,6 +7,7 @@ enum parse_opt_type { OPTION_ARGUMENT, OPTION_GROUP, OPTION_NUMBER, + OPTION_ALIAS, /* options with no arguments */ OPTION_BIT, OPTION_NEGBIT, @@ -181,6 +182,9 @@ struct option { N_("no-op (backward compatibility)"), \ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb } +#define OPT_ALIAS(s, l, alias_source) \ + { OPTION_ALIAS, (s), (l), (alias_source) } + /* * parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. argv0 is assumed program name and @@ -256,6 +260,8 @@ struct parse_opt_ctx_t { const char *opt; int flags; const char *prefix; + const char **alias_groups; /* must be in groups of 3 elements! */ + struct option *updated_options; }; void parse_options_start(struct parse_opt_ctx_t *ctx, diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 800b3ea5f5..14e6664240 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -224,6 +224,11 @@ test_expect_success 'non ambiguous option (after two options it abbreviates)' ' test-tool parse-options --expect="string: 123" --st 123 ' +test_expect_success 'NOCOMPLETE options do not contribute to abbreviation' ' + test_when_finished "rm -rf A" && + GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false git clone --recurs . A +' + cat >typo.err <<\EOF error: did you mean `--boolean` (with two dashes ?) EOF -- 2.21.0.854.ge34a79f761
next prev parent reply other threads:[~2019-04-22 12:23 UTC|newest] Thread overview: 52+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-03-25 18:14 [PATCH 0/8] Do not use abbreviated options in tests Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 1/8] tests (rebase): spell out the `--keep-empty` option Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 2/8] tests (rebase): spell out the `--force-rebase` option Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 3/8] t7810: do not abbreviate `--no-exclude-standard` nor `--invert-match` Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 4/8] t5531: avoid using an abbreviated option Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 5/8] tests (push): do not abbreviate the `--follow-tags` option Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 6/8] tests (status): spell out the `--find-renames` option in full Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 7/8] tests (pack-objects): use the full, unabbreviated `--revs` option Johannes Schindelin via GitGitGadget 2019-03-25 18:14 ` [PATCH 8/8] tests: disallow the use of abbreviated options (by default) Johannes Schindelin via GitGitGadget 2019-03-25 18:35 ` Denton Liu 2019-03-25 20:26 ` Ævar Arnfjörð Bjarmason 2019-04-12 8:48 ` Johannes Schindelin 2019-04-12 8:50 ` Johannes Schindelin 2019-03-25 19:47 ` Ævar Arnfjörð Bjarmason 2019-04-12 8:59 ` Johannes Schindelin 2019-03-25 20:23 ` [PATCH 0/2] allow for configuring option abbreviation + fix Ævar Arnfjörð Bjarmason 2019-03-25 20:23 ` [PATCH 1/2] parse-options: allow for configuring option abbreviation Ævar Arnfjörð Bjarmason 2019-03-25 21:23 ` Eric Sunshine 2019-03-25 22:47 ` Ævar Arnfjörð Bjarmason 2019-03-26 4:14 ` Duy Nguyen 2019-03-26 6:28 ` Ævar Arnfjörð Bjarmason 2019-03-26 7:13 ` Duy Nguyen 2019-03-26 11:00 ` Ævar Arnfjörð Bjarmason 2019-04-01 10:47 ` Junio C Hamano 2019-04-12 9:06 ` Johannes Schindelin 2019-03-25 20:23 ` [PATCH 2/2] parse-options: don't emit "ambiguous option" for aliases Ævar Arnfjörð Bjarmason 2019-04-17 12:44 ` [PATCH v2] " Ævar Arnfjörð Bjarmason 2019-04-17 16:04 ` Duy Nguyen 2019-04-18 0:48 ` Junio C Hamano 2019-04-18 9:29 ` Duy Nguyen 2019-04-19 4:39 ` Junio C Hamano 2019-04-22 12:22 ` Nguyễn Thái Ngọc Duy [this message] 2019-04-22 12:34 ` [PATCH] " Duy Nguyen 2019-04-29 10:05 ` [PATCH v2] " Nguyễn Thái Ngọc Duy 2019-05-07 3:43 ` Junio C Hamano 2019-05-07 11:58 ` Duy Nguyen 2019-04-02 0:58 ` [PATCH 0/8] Do not use abbreviated options in tests Junio C Hamano 2019-04-12 9:37 ` [PATCH v2 " Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 1/8] tests (rebase): spell out the `--keep-empty` option Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 2/8] tests (rebase): spell out the `--force-rebase` option Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 3/8] t7810: do not abbreviate `--no-exclude-standard` nor `--invert-match` Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 4/8] t5531: avoid using an abbreviated option Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 5/8] tests (push): do not abbreviate the `--follow-tags` option Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 6/8] tests (status): spell out the `--find-renames` option in full Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 7/8] tests (pack-objects): use the full, unabbreviated `--revs` option Johannes Schindelin via GitGitGadget 2019-04-12 9:37 ` [PATCH v2 8/8] tests: disallow the use of abbreviated options (by default) Johannes Schindelin via GitGitGadget 2019-04-14 2:35 ` Junio C Hamano 2019-04-15 2:55 ` Junio C Hamano 2019-04-15 13:09 ` Johannes Schindelin 2019-04-15 13:45 ` Junio C Hamano 2019-04-17 12:07 ` Johannes Schindelin 2019-04-15 13:08 ` Johannes Schindelin
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style List information: http://vger.kernel.org/majordomo-info.html * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20190422122250.15248-1-pclouds@gmail.com \ --to=pclouds@gmail.com \ --cc=Johannes.Schindelin@gmx.de \ --cc=avarab@gmail.com \ --cc=git@vger.kernel.org \ --cc=gitster@pobox.com \ --cc=liu.denton@gmail.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
git@vger.kernel.org list mirror (unofficial, one of many) This inbox may be cloned and mirrored by anyone: git clone --mirror https://public-inbox.org/git git clone --mirror http://ou63pmih66umazou.onion/git git clone --mirror http://czquwvybam4bgbro.onion/git git clone --mirror http://hjrcffqmbrq6wope.onion/git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V1 git git/ https://public-inbox.org/git \ git@vger.kernel.org public-inbox-index git Example config snippet for mirrors. Newsgroups are available over NNTP: nntp://news.public-inbox.org/inbox.comp.version-control.git nntp://ou63pmih66umazou.onion/inbox.comp.version-control.git nntp://czquwvybam4bgbro.onion/inbox.comp.version-control.git nntp://hjrcffqmbrq6wope.onion/inbox.comp.version-control.git nntp://news.gmane.io/gmane.comp.version-control.git note: .onion URLs require Tor: https://www.torproject.org/ code repositories for the project(s) associated with this inbox: https://80x24.org/mirrors/git.git AGPL code for this site: git clone https://public-inbox.org/public-inbox.git