Skip to content

Commit be9df6d

Browse files
barroitgitster
authored andcommitted
parseopt: autocorrect mistyped subcommands
Try to autocorrect the mistyped mandatory subcommand before showing an error and exiting. Subcommands parsed with PARSE_OPT_SUBCOMMAND_OPTIONAL are skipped. Use standard Damerau-Levenshtein distance (weights 1, 1, 1, 1) to establish a predictable, mathematically sound baseline. Scale the allowed edit distance based on input length to prevent false positives on short commands, following common practice for fuzziness thresholds (e.g., Elasticsearch's AUTO fuzziness): - Length 0-2: 0 edits allowed - Length 3-5: 1 edit allowed - Length 6+: 2 edits allowed Signed-off-by: Jiamu Sun <39@barroit.sh> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 7cd07f1 commit be9df6d

1 file changed

Lines changed: 78 additions & 3 deletions

File tree

parse-options.c

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "strbuf.h"
77
#include "string-list.h"
88
#include "utf8.h"
9+
#include "autocorrect.h"
10+
#include "levenshtein.h"
911

1012
static int disallow_abbreviated_options;
1113

@@ -622,13 +624,77 @@ static int parse_subcommand(const char *arg, const struct option *options)
622624
return -1;
623625
}
624626

627+
static void find_subcommands(struct string_list *list,
628+
const struct option *options)
629+
{
630+
for (; options->type != OPTION_END; options++) {
631+
if (options->type == OPTION_SUBCOMMAND)
632+
string_list_append(list, options->long_name);
633+
}
634+
}
635+
636+
static int similar_enough(const char *cmd, unsigned int edit)
637+
{
638+
size_t len = strlen(cmd);
639+
unsigned int allowed = len < 3 ? 0 : len < 6 ? 1 : 2;
640+
641+
return edit <= allowed;
642+
}
643+
644+
static const char *autocorrect_subcommand(const char *cmd,
645+
struct string_list *cmds)
646+
{
647+
struct autocorrect autocorrect = { 0 };
648+
unsigned int min = UINT_MAX;
649+
unsigned int ties = 0;
650+
struct string_list_item *cand;
651+
struct string_list_item *best = NULL;
652+
653+
autocorrect_resolve(&autocorrect);
654+
655+
/*
656+
* Builtin subcommands are small enough that printing them all via
657+
* usage_with_options() is sufficient. Therefore, AUTOCORRECT_HINT
658+
* acts like AUTOCORRECT_NEVER.
659+
*/
660+
if (autocorrect.mode == AUTOCORRECT_HINT ||
661+
autocorrect.mode == AUTOCORRECT_NEVER)
662+
return NULL;
663+
664+
for_each_string_list_item(cand, cmds) {
665+
unsigned int edit = levenshtein(cmd, cand->string, 1, 1, 1, 1);
666+
667+
if (edit < min) {
668+
min = edit;
669+
best = cand;
670+
ties = 0;
671+
} else if (edit == min) {
672+
ties++;
673+
}
674+
}
675+
676+
if (!ties && similar_enough(cmd, min)) {
677+
fprintf_ln(stderr,
678+
_("WARNING: You called a subcommand named '%s', which does not exist."),
679+
cmd);
680+
681+
autocorrect_confirm(&autocorrect, best->string);
682+
return best->string;
683+
}
684+
685+
return NULL;
686+
}
687+
625688
static enum parse_opt_result handle_subcommand(struct parse_opt_ctx_t *ctx,
626689
const char *arg,
627690
const struct option *options,
628691
const char * const usagestr[])
629692
{
630-
int err = parse_subcommand(arg, options);
693+
int err;
694+
const char *assumed;
695+
struct string_list cmds = STRING_LIST_INIT_NODUP;
631696

697+
err = parse_subcommand(arg, options);
632698
if (!err)
633699
return PARSE_OPT_SUBCOMMAND;
634700

@@ -641,8 +707,17 @@ static enum parse_opt_result handle_subcommand(struct parse_opt_ctx_t *ctx,
641707
if (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)
642708
return PARSE_OPT_DONE;
643709

644-
error(_("unknown subcommand: `%s'"), arg);
645-
usage_with_options(usagestr, options);
710+
find_subcommands(&cmds, options);
711+
assumed = autocorrect_subcommand(arg, &cmds);
712+
713+
if (!assumed) {
714+
error(_("unknown subcommand: `%s'"), arg);
715+
usage_with_options(usagestr, options);
716+
}
717+
718+
string_list_clear(&cmds, 0);
719+
parse_subcommand(assumed, options);
720+
return PARSE_OPT_SUBCOMMAND;
646721
}
647722

648723
static void check_typos(const char *arg, const struct option *options)

0 commit comments

Comments
 (0)