@matheusbsbs / matheus-bagliano / commits / 8af84dadb1
Fork 0

git wrapper: DWIM mistyped commands

This patch introduces a modified Damerau-Levenshtein algorithm into Git's code base, and uses it with the following penalties to show some similar commands when an unknown command was encountered: swap = 0, insertion = 1, substitution = 2, deletion = 4 A typical output would now look like this: $ git sm git: 'sm' is not a git-command. See 'git --help'. Did you mean one of these? am rm The cut-off is at similarity rating 6, which was empirically determined to give sensible results. As a convenience, if there is only one candidate, Git continues under the assumption that the user mistyped it. Example: $ git reabse WARNING: You called a Git program named 'reabse', which does not exist. Continuing under the assumption that you meant 'rebase' [...] Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Alex Riesen <raa.lkml@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Aug 31, 2008 at 15:50 UTC 8af84dadb142f7321ff0ce8690385e99da8ede2f
7 files changed +133 -4
Makefile
+2
@@ -358,6 +358,7 @@ LIB_H += graph.h
358 LIB_H += grep.h
359 LIB_H += hash.h
360 LIB_H += help.h
361 +LIB_H += levenshtein.h
362 LIB_H += list-objects.h
363 LIB_H += ll-merge.h
364 LIB_H += log-tree.h
@@ -433,6 +434,7 @@ LIB_OBJS += hash.o
434 LIB_OBJS += help.o
435 LIB_OBJS += ident.o
436 LIB_OBJS += interpolate.o
437 +LIB_OBJS += levenshtein.o
438 LIB_OBJS += list-objects.o
439 LIB_OBJS += ll-merge.o
440 LIB_OBJS += lockfile.o
builtin.h
+1 -1
@@ -11,7 +11,7 @@ extern const char git_usage_string[];
11 extern const char git_more_info_string[];
12
13 extern void list_common_cmds_help(void);
14 -extern void help_unknown_cmd(const char *cmd);
14 +extern const char *help_unknown_cmd(const char *cmd);
15 extern void prune_packed_objects(int);
16 extern int read_line_with_nul(char *buf, int size, FILE *file);
17 extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
git.c
+3 -1
@@ -499,7 +499,9 @@ int main(int argc, const char **argv)
499 cmd, argv[0]);
500 exit(1);
501 }
502 - help_unknown_cmd(cmd);
502 + argv[0] = help_unknown_cmd(cmd);
503 + handle_internal_command(argc, argv);
504 + execv_dashed_external(argv);
505 }
506
507 fprintf(stderr, "Failed to run command '%s': %s\n",
help.c
+71 -1
@@ -1,6 +1,7 @@
1 #include "cache.h"
2 #include "builtin.h"
3 #include "exec_cmd.h"
4 +#include "levenshtein.h"
5 #include "help.h"
6
7 /* most GUI terminals set COLUMNS (although some don't export it) */
@@ -37,6 +38,16 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len)
38 cmds->names[cmds->cnt++] = ent;
39 }
40
41 +static void clean_cmdnames(struct cmdnames *cmds)
42 +{
43 + int i;
44 + for (i = 0; i < cmds->cnt; ++i)
45 + free(cmds->names[i]);
46 + free(cmds->names);
47 + cmds->cnt = 0;
48 + cmds->alloc = 0;
49 +}
50 +
51 static int cmdname_compare(const void *a_, const void *b_)
52 {
53 struct cmdname *a = *(struct cmdname **)a_;
@@ -257,9 +268,68 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
268 return 0;
269 }
270
260 -void help_unknown_cmd(const char *cmd)
271 +static int levenshtein_compare(const void *p1, const void *p2)
272 +{
273 + const struct cmdname *const *c1 = p1, *const *c2 = p2;
274 + const char *s1 = (*c1)->name, *s2 = (*c2)->name;
275 + int l1 = (*c1)->len;
276 + int l2 = (*c2)->len;
277 + return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
278 +}
279 +
280 +const char *help_unknown_cmd(const char *cmd)
281 {
282 + int i, n, best_similarity = 0;
283 + struct cmdnames main_cmds, other_cmds;
284 +
285 + memset(&main_cmds, 0, sizeof(main_cmds));
286 + memset(&other_cmds, 0, sizeof(main_cmds));
287 +
288 + load_command_list("git-", &main_cmds, &other_cmds);
289 +
290 + ALLOC_GROW(main_cmds.names, main_cmds.cnt + other_cmds.cnt,
291 + main_cmds.alloc);
292 + memcpy(main_cmds.names + main_cmds.cnt, other_cmds.names,
293 + other_cmds.cnt * sizeof(other_cmds.names[0]));
294 + main_cmds.cnt += other_cmds.cnt;
295 + free(other_cmds.names);
296 +
297 + /* This reuses cmdname->len for similarity index */
298 + for (i = 0; i < main_cmds.cnt; ++i)
299 + main_cmds.names[i]->len =
300 + levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
301 +
302 + qsort(main_cmds.names, main_cmds.cnt,
303 + sizeof(*main_cmds.names), levenshtein_compare);
304 +
305 + if (!main_cmds.cnt)
306 + die ("Uh oh. Your system reports no Git commands at all.");
307 +
308 + best_similarity = main_cmds.names[0]->len;
309 + n = 1;
310 + while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
311 + ++n;
312 + if (n == 1) {
313 + const char *assumed = main_cmds.names[0]->name;
314 + main_cmds.names[0] = NULL;
315 + clean_cmdnames(&main_cmds);
316 + fprintf(stderr, "WARNING: You called a Git program named '%s', "
317 + "which does not exist.\n"
318 + "Continuing under the assumption that you meant '%s'\n",
319 + cmd, assumed);
320 + return assumed;
321 + }
322 +
323 fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
324 +
325 + if (best_similarity < 6) {
326 + fprintf(stderr, "\nDid you mean %s?\n",
327 + n < 2 ? "this": "one of these");
328 +
329 + for (i = 0; i < n; i++)
330 + fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
331 + }
332 +
333 exit(1);
334 }
335
help.h
+1 -1
@@ -5,7 +5,7 @@ struct cmdnames {
5 int alloc;
6 int cnt;
7 struct cmdname {
8 - size_t len;
8 + size_t len; /* also used for similarity index in help.c */
9 char name[FLEX_ARRAY];
10 } **names;
11 };
levenshtein.c new
+47
@@ -0,0 +1,47 @@
1 +#include "cache.h"
2 +#include "levenshtein.h"
3 +
4 +int levenshtein(const char *string1, const char *string2,
5 + int w, int s, int a, int d)
6 +{
7 + int len1 = strlen(string1), len2 = strlen(string2);
8 + int *row0 = xmalloc(sizeof(int) * (len2 + 1));
9 + int *row1 = xmalloc(sizeof(int) * (len2 + 1));
10 + int *row2 = xmalloc(sizeof(int) * (len2 + 1));
11 + int i, j;
12 +
13 + for (j = 0; j <= len2; j++)
14 + row1[j] = j * a;
15 + for (i = 0; i < len1; i++) {
16 + int *dummy;
17 +
18 + row2[0] = (i + 1) * d;
19 + for (j = 0; j < len2; j++) {
20 + /* substitution */
21 + row2[j + 1] = row1[j] + s * (string1[i] != string2[j]);
22 + /* swap */
23 + if (i > 0 && j > 0 && string1[i - 1] == string2[j] &&
24 + string1[i] == string2[j - 1] &&
25 + row2[j + 1] > row0[j - 1] + w)
26 + row2[j + 1] = row0[j - 1] + w;
27 + /* deletion */
28 + if (j + 1 < len2 && row2[j + 1] > row1[j + 1] + d)
29 + row2[j + 1] = row1[j + 1] + d;
30 + /* insertion */
31 + if (row2[j + 1] > row2[j] + a)
32 + row2[j + 1] = row2[j] + a;
33 + }
34 +
35 + dummy = row0;
36 + row0 = row1;
37 + row1 = row2;
38 + row2 = dummy;
39 + }
40 +
41 + i = row1[len2];
42 + free(row0);
43 + free(row1);
44 + free(row2);
45 +
46 + return i;
47 +}
levenshtein.h new
+8
@@ -0,0 +1,8 @@
1 +#ifndef LEVENSHTEIN_H
2 +#define LEVENSHTEIN_H
3 +
4 +int levenshtein(const char *string1, const char *string2,
5 + int swap_penalty, int substition_penalty,
6 + int insertion_penalty, int deletion_penalty);
7 +
8 +#endif