Skip to content

Commit

Permalink
Merge branch 'ar/autospell'
Browse files Browse the repository at this point in the history
* ar/autospell:
  Add help.autocorrect to enable/disable autocorrecting
  git wrapper: DWIM mistyped commands
  • Loading branch information
Junio C Hamano committed Sep 8, 2008
2 parents cd50988 + f0e9071 commit 277cd4c
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 4 deletions.
9 changes: 9 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,15 @@ help.format::
Values 'man', 'info', 'web' and 'html' are supported. 'man' is
the default. 'web' and 'html' are the same.

help.autocorrect::
Automatically correct and execute mistyped commands after
waiting for the given number of deciseconds (0.1 sec). If more
than one command can be deduced from the entered text, nothing
will be executed. If the value of this option is negative,
the corrected command will be executed immediately. If the
value is 0 - the command will be just shown but not executed.
This is the default.

http.proxy::
Override the HTTP proxy, normally configured using the 'http_proxy'
environment variable (see linkgit:curl[1]). This can be overridden
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
LIB_H += help.h
LIB_H += levenshtein.h
LIB_H += list-objects.h
LIB_H += ll-merge.h
LIB_H += log-tree.h
Expand Down Expand Up @@ -434,6 +435,7 @@ LIB_OBJS += hash.o
LIB_OBJS += help.o
LIB_OBJS += ident.o
LIB_OBJS += interpolate.o
LIB_OBJS += levenshtein.o
LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o
Expand Down
2 changes: 1 addition & 1 deletion builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern const char git_usage_string[];
extern const char git_more_info_string[];

extern void list_common_cmds_help(void);
extern void help_unknown_cmd(const char *cmd);
extern const char *help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
Expand Down
4 changes: 3 additions & 1 deletion git.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,9 @@ int main(int argc, const char **argv)
cmd, argv[0]);
exit(1);
}
help_unknown_cmd(cmd);
argv[0] = help_unknown_cmd(cmd);
handle_internal_command(argc, argv);
execv_dashed_external(argv);
}

fprintf(stderr, "Failed to run command '%s': %s\n",
Expand Down
89 changes: 88 additions & 1 deletion help.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "cache.h"
#include "builtin.h"
#include "exec_cmd.h"
#include "levenshtein.h"
#include "help.h"

/* most GUI terminals set COLUMNS (although some don't export it) */
Expand Down Expand Up @@ -37,6 +38,16 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len)
cmds->names[cmds->cnt++] = ent;
}

static void clean_cmdnames(struct cmdnames *cmds)
{
int i;
for (i = 0; i < cmds->cnt; ++i)
free(cmds->names[i]);
free(cmds->names);
cmds->cnt = 0;
cmds->alloc = 0;
}

static int cmdname_compare(const void *a_, const void *b_)
{
struct cmdname *a = *(struct cmdname **)a_;
Expand Down Expand Up @@ -250,9 +261,85 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
return 0;
}

void help_unknown_cmd(const char *cmd)
static int autocorrect;

static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "help.autocorrect"))
autocorrect = git_config_int(var,value);

return git_default_config(var, value, cb);
}

static int levenshtein_compare(const void *p1, const void *p2)
{
const struct cmdname *const *c1 = p1, *const *c2 = p2;
const char *s1 = (*c1)->name, *s2 = (*c2)->name;
int l1 = (*c1)->len;
int l2 = (*c2)->len;
return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
}

const char *help_unknown_cmd(const char *cmd)
{
int i, n, best_similarity = 0;
struct cmdnames main_cmds, other_cmds;

memset(&main_cmds, 0, sizeof(main_cmds));
memset(&other_cmds, 0, sizeof(main_cmds));

git_config(git_unknown_cmd_config, NULL);

load_command_list("git-", &main_cmds, &other_cmds);

ALLOC_GROW(main_cmds.names, main_cmds.cnt + other_cmds.cnt,
main_cmds.alloc);
memcpy(main_cmds.names + main_cmds.cnt, other_cmds.names,
other_cmds.cnt * sizeof(other_cmds.names[0]));
main_cmds.cnt += other_cmds.cnt;
free(other_cmds.names);

/* This reuses cmdname->len for similarity index */
for (i = 0; i < main_cmds.cnt; ++i)
main_cmds.names[i]->len =
levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);

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;
if (autocorrect && n == 1) {
const char *assumed = main_cmds.names[0]->name;
main_cmds.names[0] = NULL;
clean_cmdnames(&main_cmds);
fprintf(stderr, "WARNING: You called a Git program named '%s', "
"which does not exist.\n"
"Continuing under the assumption that you meant '%s'\n",
cmd, assumed);
if (autocorrect > 0) {
fprintf(stderr, "in %0.1f seconds automatically...\n",
(float)autocorrect/10.0);
poll(NULL, 0, autocorrect * 100);
}
return assumed;
}

fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);

if (best_similarity < 6) {
fprintf(stderr, "\nDid you mean %s?\n",
n < 2 ? "this": "one of these");

for (i = 0; i < n; i++)
fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
}

exit(1);
}

Expand Down
2 changes: 1 addition & 1 deletion help.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct cmdnames {
int alloc;
int cnt;
struct cmdname {
size_t len;
size_t len; /* also used for similarity index in help.c */
char name[FLEX_ARRAY];
} **names;
};
Expand Down
47 changes: 47 additions & 0 deletions levenshtein.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "cache.h"
#include "levenshtein.h"

int levenshtein(const char *string1, const char *string2,
int w, int s, int a, int d)
{
int len1 = strlen(string1), len2 = strlen(string2);
int *row0 = xmalloc(sizeof(int) * (len2 + 1));
int *row1 = xmalloc(sizeof(int) * (len2 + 1));
int *row2 = xmalloc(sizeof(int) * (len2 + 1));
int i, j;

for (j = 0; j <= len2; j++)
row1[j] = j * a;
for (i = 0; i < len1; i++) {
int *dummy;

row2[0] = (i + 1) * d;
for (j = 0; j < len2; j++) {
/* substitution */
row2[j + 1] = row1[j] + s * (string1[i] != string2[j]);
/* swap */
if (i > 0 && j > 0 && string1[i - 1] == string2[j] &&
string1[i] == string2[j - 1] &&
row2[j + 1] > row0[j - 1] + w)
row2[j + 1] = row0[j - 1] + w;
/* deletion */
if (j + 1 < len2 && row2[j + 1] > row1[j + 1] + d)
row2[j + 1] = row1[j + 1] + d;
/* insertion */
if (row2[j + 1] > row2[j] + a)
row2[j + 1] = row2[j] + a;
}

dummy = row0;
row0 = row1;
row1 = row2;
row2 = dummy;
}

i = row1[len2];
free(row0);
free(row1);
free(row2);

return i;
}
8 changes: 8 additions & 0 deletions levenshtein.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef LEVENSHTEIN_H
#define LEVENSHTEIN_H

int levenshtein(const char *string1, const char *string2,
int swap_penalty, int substition_penalty,
int insertion_penalty, int deletion_penalty);

#endif

0 comments on commit 277cd4c

Please sign in to comment.