From: Johannes Schindelin <hidden> Date: 2016-06-15 22:44:59
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 <redacted>
---
So I mistyped 'reabse' for the hundred trillionth time, but I
will never have to correct my mistakes again.
Note: this patch is _not_ meant for inclusion.
Makefile | 2 +
builtin.h | 2 +-
git.c | 4 ++-
help.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
levenshtein.c | 47 +++++++++++++++++++++++++++++++++++++++++++
levenshtein.h | 8 +++++++
6 files changed, 121 insertions(+), 3 deletions(-)
create mode 100644 levenshtein.c
create mode 100644 levenshtein.h
@@ -500,7 +500,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",
@@ -666,9 +667,67 @@ static void show_html_page(const char *git_cmd)open_html(page_path.buf);}-voidhelp_unknown_cmd(constchar*cmd)+staticconstchar*levenshtein_cmd;+staticintsimilarity(constchar*cmd){+returnlevenshtein(levenshtein_cmd,cmd,0,2,1,4);+}++staticintlevenshtein_compare(constvoid*p1,constvoid*p2)+{+conststructcmdname*const*c1=p1,*const*c2=p2;+constchar*s1=(*c1)->name,*s2=(*c2)->name;+intl1=similarity(s1);+intl2=similarity(s2);+returnl1!=l2?l1-l2:strcmp(s1,s2);+}++constchar*help_unknown_cmd(constchar*cmd){+inti,best_similarity=0;+charcwd[PATH_MAX];++if(!getcwd(cwd,sizeof(cwd))){+error("Could not get current working directory");+cwd[0]='\0';+}++load_command_list();+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;++levenshtein_cmd=cmd;+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=similarity(main_cmds.names[0]->name);+if(main_cmds.cnt<2||best_similarity<+similarity(main_cmds.names[1]->name)){+if(!*cwd)+exit(1);+if(chdir(cwd))+die("Could not change directory back to '%s'",cwd);+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,main_cmds.names[0]->name);+returnmain_cmds.names[0]->name;+}+fprintf(stderr,"git: '%s' is not a git-command. See 'git --help'.\n",cmd);++if(best_similarity<6){+fprintf(stderr,"\nDid you mean one of these?\n");++for(i=0;i<main_cmds.cnt&&best_similarity==+similarity(main_cmds.names[i]->name);i++)+fprintf(stderr,"\t%s\n",main_cmds.names[i]->name);+}+exit(1);}
From: Pierre Habouzit <hidden> Date: 2016-06-15 22:44:59
On Tue, Jul 22, 2008 at 08:01:29PM +0000, Johannes Schindelin wrote:
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'
[...]
<SCNR>
Or use a decent shell:
When typing e.g.: git tsa<tab>, it yields:
$ git status
---- corrections (errors 1)
status -- show working-tree's status
tag -- create tag object signed with GPG
tar-tree -- create tar archive of the files in the named tree
---- original
tsa
and it even works for non git commands ;)
</SCNR>
Despite that, I really like your idea. **hint hint** One could even hook that
for long options into parse-options.
--
·O· Pierre Habouzit
··O madcoder@debian.org
OOO http://www.madism.org
From: Pierre Habouzit <hidden> Date: 2016-06-15 22:44:59
On Tue, Jul 22, 2008 at 08:19:13PM +0000, Johannes Schindelin wrote:
Hi,
On Tue, 22 Jul 2008, Pierre Habouzit wrote:
quoted
<SCNR>
Or use a decent shell:
I tried that:
git reab<tab><tab><TAB><TTAAABBB!>
It yields the following here:
$ git read-tree
---- corrections (errors 1)
read-tree -- read tree information into the directory index
rebase -- rebase local commits to new upstream head
---- original
reab
and indeed, it should really suggest rebase first, I suppose I should reorder
my zsh completion error weights. but oh well...
quoted
Despite that, I really like your idea. **hint hint**
I said that _I_ did not mean it for inclusion. **hint hint**
From: Alex Riesen <hidden> Date: 2016-06-15 22:44:59
Johannes Schindelin, Tue, Jul 22, 2008 22:01:29 +0200:
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'
[...]
Oh, that would make me suspicios (and I hit Ctrl-C fast when I get
suspicios about what happens to my precious data). Could it be
configurable? For example, BASH's cdspell is configurable and even off
by default.
P.S. I'm still using your first patch and am forced to like it every day :)
From: Alex Riesen <hidden> Date: 2016-06-15 22:44:59
It is off by default, to avoid scaring people unless they asked to.
---
Alex Riesen, Tue, Jul 22, 2008 22:37:30 +0200:
Johannes Schindelin, Tue, Jul 22, 2008 22:01:29 +0200:
quoted
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'
[...]
Oh, that would make me suspicios (and I hit Ctrl-C fast when I get
suspicios about what happens to my precious data). Could it be
configurable? For example, BASH's cdspell is configurable and even off
by default.
@@ -704,9 +707,10 @@ const char *help_unknown_cmd(const char *cmd)if(!main_cmds.cnt)die("Uh oh. Your system reports no Git commands at all.");+git_config(git_help_config,NULL);best_similarity=similarity(main_cmds.names[0]->name);-if(main_cmds.cnt<2||best_similarity<-similarity(main_cmds.names[1]->name)){+if(autocorrect&&(main_cmds.cnt<2||+best_similarity<similarity(main_cmds.names[1]->name))){if(!*cwd)exit(1);if(chdir(cwd))
@@ -704,9 +707,10 @@ const char *help_unknown_cmd(const char *cmd)if(!main_cmds.cnt)die("Uh oh. Your system reports no Git commands at all.");+git_config(git_help_config,NULL);best_similarity=similarity(main_cmds.names[0]->name);-if(main_cmds.cnt<2||best_similarity<-similarity(main_cmds.names[1]->name)){+if(autocorrect&&(main_cmds.cnt<2||+best_similarity<similarity(main_cmds.names[1]->name))){if(!*cwd)exit(1);if(chdir(cwd))
@@ -721,10 +725,14 @@ const char *help_unknown_cmd(const char *cmd)fprintf(stderr,"git: '%s' is not a git-command. See 'git --help'.\n",cmd);if(best_similarity<6){-fprintf(stderr,"\nDid you mean one of these?\n");--for(i=0;i<main_cmds.cnt&&best_similarity==-similarity(main_cmds.names[i]->name);i++)+intn=0;+while(n<main_cmds.cnt&&+best_similarity==similarity(main_cmds.names[n]->name))+++n;+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);}
From: Johannes Schindelin <hidden> Date: 2016-06-15 22:44:59
Hi,
On Tue, 22 Jul 2008, Alex Riesen wrote:
quoted hunk
@@ -704,9 +707,10 @@ const char *help_unknown_cmd(const char *cmd) if (!main_cmds.cnt) die ("Uh oh. Your system reports no Git commands at all.");+ git_config(git_help_config, NULL); best_similarity = similarity(main_cmds.names[0]->name);- if (main_cmds.cnt < 2 || best_similarity <- similarity(main_cmds.names[1]->name)) {+ if (autocorrect && (main_cmds.cnt < 2 ||+ best_similarity < similarity(main_cmds.names[1]->name))) { if (!*cwd) exit(1); if (chdir(cwd))
This "if" already checks if there is only one candidate. So you should
just add an inner "if (autocorrect) ... else single = 1;" or some such.
However, I think that the intention of this patch is too much DWIMery,
which might be good for me (just like my "git add remote" patch), but not
for the general audience.
Ciao,
Dscho
From: Alex Riesen <hidden> Date: 2016-06-15 22:45:00
It is off by default, to avoid scaring people unless they asked to.
---
Johannes Schindelin, Tue, Jul 22, 2008 23:44:50 +0200:
On Tue, 22 Jul 2008, Alex Riesen wrote:
quoted
@@ -704,9 +707,10 @@ const char *help_unknown_cmd(const char *cmd) if (!main_cmds.cnt) die ("Uh oh. Your system reports no Git commands at all.");+ git_config(git_help_config, NULL); best_similarity = similarity(main_cmds.names[0]->name);- if (main_cmds.cnt < 2 || best_similarity <- similarity(main_cmds.names[1]->name)) {+ if (autocorrect && (main_cmds.cnt < 2 ||+ best_similarity < similarity(main_cmds.names[1]->name))) { if (!*cwd) exit(1); if (chdir(cwd))
This "if" already checks if there is only one candidate. So you should
just add an inner "if (autocorrect) ... else single = 1;" or some such.
Oh right, stupid me.
However, I think that the intention of this patch is too much DWIMery,
which might be good for me (just like my "git add remote" patch), but not
for the general audience.
Mustn't be good for all (for the "general audience" it is even common
practice to forget to thank. It may be even a sign of bad manners for
it). It is good for me though. And thanks for sharing.
Moved git_config before the calls where current directory is changed:
so that it has the same filesystem context as in normal case. Less
surprises.
help.c | 19 +++++++++++++------
1 files changed, 13 insertions(+), 6 deletions(-)
@@ -705,8 +709,11 @@ const char *help_unknown_cmd(const char *cmd)if(!main_cmds.cnt)die("Uh oh. Your system reports no Git commands at all.");best_similarity=similarity(main_cmds.names[0]->name);-if(main_cmds.cnt<2||best_similarity<-similarity(main_cmds.names[1]->name)){+n=1;+while(n<main_cmds.cnt&&+best_similarity==similarity(main_cmds.names[n]->name))+++n;+if(autocorrect&&n==1){if(!*cwd)exit(1);if(chdir(cwd))
@@ -721,10 +728,10 @@ const char *help_unknown_cmd(const char *cmd)fprintf(stderr,"git: '%s' is not a git-command. See 'git --help'.\n",cmd);if(best_similarity<6){-fprintf(stderr,"\nDid you mean one of these?\n");+fprintf(stderr,"\nDid you mean %s?\n",+n<2?"this":"one of these");-for(i=0;i<main_cmds.cnt&&best_similarity==-similarity(main_cmds.names[i]->name);i++)+for(i=0;i<n;i++)fprintf(stderr,"\t%s\n",main_cmds.names[i]->name);}
From: Johannes Schindelin <hidden> Date: 2016-06-15 22:45:00
Hi,
On Wed, 23 Jul 2008, Alex Riesen wrote:
Johannes Schindelin, Tue, Jul 22, 2008 23:44:50 +0200:
quoted
However, I think that the intention of this patch is too much DWIMery,
which might be good for me (just like my "git add remote" patch), but
not for the general audience.
Mustn't be good for all
You meant "needn't"? It is good for me ;-)
And thanks for sharing.
You're welcome.
+ n = 1;
+ while (n < main_cmds.cnt &&
+ best_similarity == similarity(main_cmds.names[n]->name))
+ ++n;
Mini-nit: you never ask for the value of n, only if it is 1 or larger. So
you do not need to count...
Ciao,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-06-15 22:45:00
Hi,
On Wed, 23 Jul 2008, Alex Riesen wrote:
Johannes Schindelin, Wed, Jul 23, 2008 18:44:49 +0200:
quoted
quoted
+ n = 1;
+ while (n < main_cmds.cnt &&
+ best_similarity == similarity(main_cmds.names[n]->name))
+ ++n;
Mini-nit: you never ask for the value of n, only if it is 1 or larger. So
you do not need to count...
But I do, don't I? AFAICS, I use 0, 1 and >1 (this-these).
Yes. So check cnt > 0 && best_similarity > 5 says if it is 0, and
cnt > 1 && best_similarity < similarity(...[1]...) says if it is 1.
Ergo: no need to count,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-06-15 22:45:00
Hi,
On Wed, 23 Jul 2008, Johannes Schindelin wrote:
On Wed, 23 Jul 2008, Alex Riesen wrote:
quoted
Johannes Schindelin, Wed, Jul 23, 2008 18:44:49 +0200:
quoted
quoted
+ n = 1;
+ while (n < main_cmds.cnt &&
+ best_similarity == similarity(main_cmds.names[n]->name))
+ ++n;
Mini-nit: you never ask for the value of n, only if it is 1 or larger. So
you do not need to count...
But I do, don't I? AFAICS, I use 0, 1 and >1 (this-these).
Yes. So check cnt > 0 && best_similarity > 5 says if it is 0,
Oh, I just realized that my patch is bogus anyway. It only checks for
best_similarity > 5 in the case that the first two commands have equal
similarity. D'oh.
Ciao,
Dscho