Skip to content

Commit

Permalink
help: always suggest common-cmds if prefix of cmd
Browse files Browse the repository at this point in the history
If someone runs "git st", the command "git status" is not suggested
because it's not one of the closest levenshtein-neighbour.

Reserve the distance of 0 for common commands where the entered command
is a prefixe, as these are often more likely to be what the user meant.

This way, "git status" is the first suggestion, while a list of possible
typos are still suggested as well.

Signed-off-by: Erik Faye-Lund <kusmabite@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
kusma authored and gitster committed Dec 5, 2010
1 parent 7d43de9 commit 6612b9e
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Makefile
Expand Up @@ -1620,6 +1620,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)

help.o: common-cmds.h

builtin/help.o: common-cmds.h
builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
Expand Down
47 changes: 39 additions & 8 deletions help.c
Expand Up @@ -3,6 +3,7 @@
#include "exec_cmd.h"
#include "levenshtein.h"
#include "help.h"
#include "common-cmds.h"

/* most GUI terminals set COLUMNS (although some don't export it) */
static int term_columns(void)
Expand Down Expand Up @@ -298,7 +299,8 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
}

/* An empirically derived magic number */
#define SIMILAR_ENOUGH(x) ((x) < 6)
#define SIMILARITY_FLOOR 7
#define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR)

const char *help_unknown_cmd(const char *cmd)
{
Expand All @@ -319,21 +321,50 @@ const char *help_unknown_cmd(const char *cmd)
sizeof(main_cmds.names), cmdname_compare);
uniq(&main_cmds);

/* This reuses cmdname->len for similarity index */
for (i = 0; i < main_cmds.cnt; ++i)
/* This abuses cmdname->len for levenshtein distance */
for (i = 0, n = 0; i < main_cmds.cnt; i++) {
int cmp = 0; /* avoid compiler stupidity */
const char *candidate = main_cmds.names[i]->name;

/* Does the candidate appear in common_cmds list? */
while (n < ARRAY_SIZE(common_cmds) &&
(cmp = strcmp(common_cmds[n].name, candidate)) < 0)
n++;
if ((n < ARRAY_SIZE(common_cmds)) && !cmp) {
/* Yes, this is one of the common commands */
n++; /* use the entry from common_cmds[] */
if (!prefixcmp(candidate, cmd)) {
/* Give prefix match a very good score */
main_cmds.names[i]->len = 0;
continue;
}
}

main_cmds.names[i]->len =
levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
levenshtein(cmd, candidate, 0, 2, 1, 4) + 1;
}

qsort(main_cmds.names, main_cmds.cnt,
sizeof(*main_cmds.names), levenshtein_compare);

if (!main_cmds.cnt)
die ("Uh oh. Your system reports no Git commands at all.");

best_similarity = main_cmds.names[0]->len;
n = 1;
while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
++n;
/* skip and count prefix matches */
for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
; /* still counting */

if (main_cmds.cnt <= n) {
/* prefix matches with everything? that is too ambiguous */
best_similarity = SIMILARITY_FLOOR + 1;
} else {
/* count all the most similar ones */
for (best_similarity = main_cmds.names[n++]->len;
(n < main_cmds.cnt &&
best_similarity == main_cmds.names[n]->len);
n++)
; /* still counting */
}
if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
Expand Down

0 comments on commit 6612b9e

Please sign in to comment.