git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v5 07/10] parseopt: autocorrect mistyped subcommands

From
JSJiamu Sun <39@barroit.sh>
Date
Apr 22, 2026, 12:18 UTC
Message-ID
<SY0P300MB080136CB238AAA21CB36153DCE2D2@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>
In-Reply-To
<SY0P300MB0801AE56F740AD087D22B35ACE2D2@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>

Try to autocorrect the mistyped mandatory subcommand before showing an error and exiting. Subcommands parsed with PARSE_OPT_SUBCOMMAND_OPTIONAL are skipped.

The subcommand autocorrection behaves the same as the command autocorrection.

Signed-off-by: Jiamu Sun <39@barroit.sh>
---
 autocorrect.h   |   4 ++
 help.c          |  13 ++----
 parse-options.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 107 insertions(+), 12 deletions(-)
diff --git a/autocorrect.h b/autocorrect.h
index 0d3e819262ed..14ee7c4548d3 100644
--- a/autocorrect.h
+++ b/autocorrect.h
@@ -1,6 +1,10 @@
 #ifndef AUTOCORRECT_H
 #define AUTOCORRECT_H
 
+/* An empirically derived magic number */
+#define AUTOCORRECT_SIMILARITY_FLOOR 7
+#define AUTOCORRECT_SIMILAR_ENOUGH(x) ((x) < AUTOCORRECT_SIMILARITY_FLOOR)
+
 enum autocorrect_mode {
 	AUTOCORRECT_HINT,
 	AUTOCORRECT_NEVER,
diff --git a/help.c b/help.c
index 0fff43545cc1..ecc5673b7243 100644
--- a/help.c
+++ b/help.c
@@ -580,10 +580,6 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
 	old->cnt = 0;
 }
 
-/* An empirically derived magic number */
-#define SIMILARITY_FLOOR 7
-#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)
-
 static const char bad_interpreter_advice[] =
 	N_("'%s' appears to be a git command, but we were not\n"
 	"able to execute it. Maybe git-%s is broken?");
@@ -659,7 +655,7 @@ char *help_unknown_cmd(const char *cmd)
 
 	if (main_cmds.cnt <= n) {
 		/* prefix matches with everything? that is too ambiguous */
-		best_similarity = SIMILARITY_FLOOR + 1;
+		best_similarity = AUTOCORRECT_SIMILARITY_FLOOR + 1;
 	} else {
 		/* count all the most similar ones */
 		for (best_similarity = main_cmds.names[n++]->len;
@@ -670,7 +666,7 @@ char *help_unknown_cmd(const char *cmd)
 	}
 
 	if (autocorrect.mode != AUTOCORRECT_HINT && n == 1 &&
-	    SIMILAR_ENOUGH(best_similarity)) {
+	    AUTOCORRECT_SIMILAR_ENOUGH(best_similarity)) {
 		char *assumed = xstrdup(main_cmds.names[0]->name);
 
 		fprintf_ln(stderr,
@@ -687,11 +683,10 @@ char *help_unknown_cmd(const char *cmd)
 
 	fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
 
-	if (SIMILAR_ENOUGH(best_similarity)) {
+	if (AUTOCORRECT_SIMILAR_ENOUGH(best_similarity)) {
 		fprintf_ln(stderr,
 			   Q_("\nThe most similar command is",
-			      "\nThe most similar commands are",
-			   n));
+			      "\nThe most similar commands are", n));
 
 		for (i = 0; i < n; i++)
 			fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
diff --git a/parse-options.c b/parse-options.c
index 803ce2ba4443..a488f9a41df8 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -7,6 +7,8 @@
 #include "string-list.h"
 #include "strmap.h"
 #include "utf8.h"
+#include "autocorrect.h"
+#include "levenshtein.h"
 
 static int disallow_abbreviated_options;
 
@@ -623,13 +625,98 @@ static int parse_subcommand(const char *arg, const struct option *options)
 	return -1;
 }
 
+static void find_subcommands(struct string_list *list,
+			     const struct option *options)
+{
+	for (; options->type != OPTION_END; options++) {
+		if (options->type == OPTION_SUBCOMMAND)
+			string_list_append(list, options->long_name);
+	}
+}
+
+static int levenshtein_compare(const void *p1, const void *p2)
+{
+	const struct string_list_item *i1 = p1, *i2 = p2;
+	const char *s1 = i1->string, *s2 = i2->string;
+	int l1 = (intptr_t)i1->util;
+	int l2 = (intptr_t)i2->util;
+
+	return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
+}
+
+static const char *autocorrect_subcommand(const char *cmd,
+					  struct string_list *cmds)
+{
+	struct autocorrect autocorrect = { 0 };
+	unsigned int n = 0, best = 0;
+	struct string_list_item *cand;
+
+	autocorrect_resolve(&autocorrect);
+
+	if (autocorrect.mode == AUTOCORRECT_NEVER)
+		return NULL;
+
+	for_each_string_list_item(cand, cmds) {
+		if (starts_with(cand->string, cmd)) {
+			cand->util = 0;
+		} else {
+			int edit = levenshtein(cmd, cand->string,
+					       0, 2, 1, 3) + 1;
+
+			cand->util = (void *)(intptr_t)edit;
+		}
+	}
+
+	QSORT(cmds->items, cmds->nr, levenshtein_compare);
+
+	/* Match help.c:help_unknown_cmd */
+	for (; n < cmds->nr && !cmds->items[n].util; n++);
+
+	if (n == cmds->nr)
+		/* prefix matches with every subcommands */
+		best = AUTOCORRECT_SIMILARITY_FLOOR + 1;
+	else
+		for (best = (intptr_t)cmds->items[n++].util;
+		     (n < cmds->nr && best == (intptr_t)cmds->items[n].util);
+		     n++);
+
+	if (autocorrect.mode != AUTOCORRECT_HINT &&  n == 1 &&
+	    AUTOCORRECT_SIMILAR_ENOUGH(best)) {
+		fprintf_ln(stderr,
+			   _("WARNING: You called a subcommand named '%s', which does not exist."),
+			   cmd);
+
+		autocorrect_confirm(&autocorrect, cmds->items[0].string);
+		return cmds->items[0].string;
+	}
+
+	if (AUTOCORRECT_SIMILAR_ENOUGH(best)) {
+		error(_("'%s' is not a subcommand."), cmd);
+
+		fprintf_ln(stderr,
+			   Q_("\nThe most similar subcommand is",
+			      "\nThe most similar subcommands are",
+			   n));
+
+		for (unsigned int i = 0; i < n; i++)
+			fprintf(stderr, "\t%s\n", cmds->items[i].string);
+
+		exit(1);
+	}
+
+	return NULL;
+}
+
 static enum parse_opt_result handle_subcommand(struct parse_opt_ctx_t *ctx,
 					       const char *arg,
 					       const struct option *options,
 					       const char * const usagestr[])
 {
-	int err = parse_subcommand(arg, options);
+	int err;
+	const char *assumed;
+	struct string_list cmds = STRING_LIST_INIT_NODUP;
 
+	err = parse_subcommand(arg, options);
 	if (!err)
 		return PARSE_OPT_SUBCOMMAND;
 
@@ -642,8 +729,17 @@ static enum parse_opt_result handle_subcommand(struct parse_opt_ctx_t *ctx,
 	if (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)
 		return PARSE_OPT_DONE;
 
-	error(_("unknown subcommand: `%s'"), arg);
-	usage_with_options(usagestr, options);
+	find_subcommands(&cmds, options);
+	assumed = autocorrect_subcommand(arg, &cmds);
+
+	if (!assumed) {
+		error(_("unknown subcommand: `%s'"), arg);
+		usage_with_options(usagestr, options);
+	}
+
+	string_list_clear(&cmds, 0);
+	parse_subcommand(assumed, options);
+	return PARSE_OPT_SUBCOMMAND;
 }
 
 static void check_typos(const char *arg, const struct option *options)
-- 
2.53.0
Previous: Jiamu SunNext: Jiamu Sun
Message 77 of 96 in “parseopt: add subcommand autocorrection”
  1. 0/5 parseopt: add subcommand autocorrectionJiamu Sun, Mar 8, 2026
  2. 1/5 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Mar 8, 2026
  3. Junio C HamanoMar 8, 2026
  4. Jiamu SunMar 9, 2026
  5. 2/5 help: refactor command autocorrection handlingJiamu Sun, Mar 8, 2026
  6. Junio C HamanoMar 8, 2026
  7. Jiamu SunMar 9, 2026
  8. 3/5 parseopt: autocorrect mistyped subcommandsJiamu Sun, Mar 8, 2026
  9. Junio C HamanoMar 9, 2026
  10. Jiamu SunMar 9, 2026
  11. 4/5 parseopt: enable subcommand autocorrect for remote and notesJiamu Sun, Mar 8, 2026
  12. 5/5 help: add tests for subcommand autocorrectionJiamu Sun, Mar 8, 2026
  13. Aaron PlattnerMar 11, 2026
  14. Jiamu SunMar 11, 2026
  15. Junio C HamanoMar 12, 2026
  16. Junio C HamanoMar 8, 2026
  17. Jiamu SunMar 8, 2026
  18. 0/5 parseopt: add subcommand autocorrectionJiamu Sun, Mar 8, 2026
  19. 1/5 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Mar 8, 2026
  20. 2/5 help: refactor command autocorrection handlingJiamu Sun, Mar 8, 2026
  21. 3/5 parseopt: autocorrect mistyped subcommandsJiamu Sun, Mar 8, 2026
  22. 4/5 parseopt: enable subcommand autocorrect for remote and notesJiamu Sun, Mar 8, 2026
  23. 5/5 help: add tests for subcommand autocorrectionJiamu Sun, Mar 8, 2026
  24. 0/8 parseopt: add subcommand autocorrectionJiamu Sun, Mar 10, 2026
  25. 1/8 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Mar 10, 2026
  26. Karthik NayakMar 10, 2026
  27. Jiamu SunMar 11, 2026
  28. Junio C HamanoMar 11, 2026
  29. Junio C HamanoMar 11, 2026
  30. Jiamu SunMar 11, 2026
  31. 2/8 help: make autocorrect handling reusableJiamu Sun, Mar 10, 2026
  32. Karthik NayakMar 10, 2026
  33. Junio C HamanoMar 10, 2026
  34. Jiamu SunMar 11, 2026
  35. Jiamu SunMar 11, 2026
  36. 3/8 help: move tty check for autocorrection to autocorrect.cJiamu Sun, Mar 10, 2026
  37. Karthik NayakMar 10, 2026
  38. Jiamu SunMar 11, 2026
  39. Jiamu SunMar 12, 2026
  40. 4/8 autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINTONLYJiamu Sun, Mar 10, 2026
  41. Karthik NayakMar 10, 2026
  42. Jiamu SunMar 11, 2026
  43. 5/8 autocorrect: provide config resolution APIJiamu Sun, Mar 10, 2026
  44. Karthik NayakMar 10, 2026
  45. 6/8 parseopt: autocorrect mistyped subcommandsJiamu Sun, Mar 10, 2026
  46. Junio C HamanoMar 10, 2026
  47. Jiamu SunMar 11, 2026
  48. Jiamu SunMar 11, 2026
  49. Junio C HamanoMar 12, 2026
  50. Jiamu SunMar 12, 2026
  51. 7/8 parseopt: enable subcommand autocorrection for git-remote and git-notesJiamu Sun, Mar 10, 2026
  52. 8/8 help: add tests for subcommand autocorrectionJiamu Sun, Mar 10, 2026
  53. Junio C HamanoMar 11, 2026
  54. Jiamu SunMar 11, 2026
  55. 00/10 parseopt: add subcommand autocorrectionJiamu Sun, Mar 16, 2026
  56. 01/10 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Mar 16, 2026
  57. 02/10 help: make autocorrect handling reusableJiamu Sun, Mar 16, 2026
  58. 03/10 help: move tty check for autocorrection to autocorrect.cJiamu Sun, Mar 16, 2026
  59. 04/10 autocorrect: use mode and delay instead of magic numbersJiamu Sun, Mar 16, 2026
  60. 05/10 autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINTJiamu Sun, Mar 16, 2026
  61. 06/10 autocorrect: provide config resolution APIJiamu Sun, Mar 16, 2026
  62. 07/10 parseopt: autocorrect mistyped subcommandsJiamu Sun, Mar 16, 2026
  63. Junio C HamanoMar 16, 2026
  64. Jiamu SunMar 17, 2026
  65. Junio C HamanoApr 15, 2026
  66. Jiamu SunApr 16, 2026
  67. 08/10 parseopt: enable subcommand autocorrection for git-remote and git-notesJiamu Sun, Mar 16, 2026
  68. 09/10 parseopt: add tests for subcommand autocorrectionJiamu Sun, Mar 16, 2026
  69. 10/10 doc: document autocorrect APIJiamu Sun, Mar 16, 2026
  70. 00/10 parseopt: add subcommand autocorrectionJiamu Sun, Apr 22, 2026
  71. 02/10 help: make autocorrect handling reusableJiamu Sun, Apr 22, 2026
  72. 01/10 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Apr 22, 2026
  73. 03/10 help: move tty check for autocorrection to autocorrect.cJiamu Sun, Apr 22, 2026
  74. 04/10 autocorrect: use mode and delay instead of magic numbersJiamu Sun, Apr 22, 2026
  75. 05/10 autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINTJiamu Sun, Apr 22, 2026
  76. 06/10 autocorrect: provide config resolution APIJiamu Sun, Apr 22, 2026
  77. 07/10 parseopt: autocorrect mistyped subcommandsJiamu Sun, Apr 22, 2026
  78. 08/10 parseopt: enable subcommand autocorrection for git-remote and git-notesJiamu Sun, Apr 22, 2026
  79. 09/10 parseopt: add tests for subcommand autocorrectionJiamu Sun, Apr 22, 2026
  80. 10/10 doc: document autocorrect APIJiamu Sun, Apr 22, 2026
  81. Junio C HamanoApr 23, 2026
  82. Jiamu SunApr 23, 2026
  83. 00/10 parseopt: add subcommand autocorrectionJiamu Sun, Apr 23, 2026
  84. 01/10 parseopt: extract subcommand handling from parse_options_step()Jiamu Sun, Apr 23, 2026
  85. 02/10 help: make autocorrect handling reusableJiamu Sun, Apr 23, 2026
  86. 03/10 help: move tty check for autocorrection to autocorrect.cJiamu Sun, Apr 23, 2026
  87. 04/10 autocorrect: use mode and delay instead of magic numbersJiamu Sun, Apr 23, 2026
  88. 06/10 autocorrect: provide config resolution APIJiamu Sun, Apr 23, 2026
  89. 10/10 doc: document autocorrect APIJiamu Sun, Apr 23, 2026
  90. 05/10 autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINTJiamu Sun, Apr 23, 2026
  91. 07/10 parseopt: autocorrect mistyped subcommandsJiamu Sun, Apr 23, 2026
  92. 08/10 parseopt: enable subcommand autocorrection for git-remote and git-notesJiamu Sun, Apr 23, 2026
  93. 09/10 parseopt: add tests for subcommand autocorrectionJiamu Sun, Apr 23, 2026
  94. Junio C HamanoMay 11, 2026
  95. Jiamu SunMay 15, 2026
  96. Junio C HamanoJun 6, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.