Merge branch 'ar/autospell'
authorJunio C Hamano <gitster@pobox.com>
Mon, 8 Sep 2008 06:52:16 +0000 (23:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 8 Sep 2008 06:52:16 +0000 (23:52 -0700)
* ar/autospell:
Add help.autocorrect to enable/disable autocorrecting
git wrapper: DWIM mistyped commands

1  2 
Documentation/config.txt
Makefile
git.c
help.c
help.h
diff --combined Documentation/config.txt
index ed3285f89927e1d988527ae5b67a5bea357d30e0,8c644ab8f6b171b54d2aeb8cafea9ec2344bf319..922ac7b44da1c2cc04bf024307e9ccd54709580f
@@@ -697,7 -697,7 +697,7 @@@ gitcvs.logfile:
        Path to a log file where the CVS server interface well... logs
        various stuff. See linkgit:git-cvsserver[1].
  
 -gitcvs.usecrlfattr
 +gitcvs.usecrlfattr::
        If true, the server will look up the `crlf` attribute for
        files to determine the '-k' modes to use. If `crlf` is set,
        the '-k' mode will be left blank, so cvs clients will
@@@ -790,6 -790,15 +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
diff --combined Makefile
index cd3d621ae8deb8227dd8f9130689778458690245,3daa6dcdb004f06559191bc929176cbda7c84368..deedcf1b86c69ac4f7254a56d174e667e949dbed
+++ b/Makefile
@@@ -358,11 -358,11 +358,12 @@@ LIB_H += graph.
  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
  LIB_H += mailmap.h
 +LIB_H += merge-recursive.h
  LIB_H += object.h
  LIB_H += pack.h
  LIB_H += pack-refs.h
@@@ -434,6 -434,7 +435,7 @@@ LIB_OBJS += hash.
  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
@@@ -697,7 -698,8 +699,7 @@@ ifeq ($(uname_S),NetBSD
                NEEDS_LIBICONV = YesPlease
        endif
        BASIC_CFLAGS += -I/usr/pkg/include
 -      BASIC_LDFLAGS += -L/usr/pkg/lib
 -      ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
 +      BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
        THREADED_DELTA_SEARCH = YesPlease
  endif
  ifeq ($(uname_S),AIX)
@@@ -792,14 -794,12 +794,14 @@@ ifeq ($(uname_S),Darwin
        endif
  endif
  
 -ifdef NO_R_TO_GCC_LINKER
 -      # Some gcc does not accept and pass -R to the linker to specify
 -      # the runtime dynamic library path.
 -      CC_LD_DYNPATH = -Wl,-rpath=
 -else
 -      CC_LD_DYNPATH = -R
 +ifndef CC_LD_DYNPATH
 +      ifdef NO_R_TO_GCC_LINKER
 +              # Some gcc does not accept and pass -R to the linker to specify
 +              # the runtime dynamic library path.
 +              CC_LD_DYNPATH = -Wl,-rpath,
 +      else
 +              CC_LD_DYNPATH = -R
 +      endif
  endif
  
  ifdef NO_CURL
@@@ -1381,7 -1381,7 +1383,7 @@@ endi
        { $(RM) "$$execdir/git-add$X" && \
                ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
                cp git-add$X "$$execdir/git-add$X"; } && \
 -      { $(foreach p,$(filter-out git-add,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
 +      { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
                ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \
                ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \
                cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \
diff --combined git.c
index fdb0f71019a02e310e15734193c7556860956115,54c5bfa69b0696a376828e36c30b957506fce408..adf73524098bdb308dea17b651536517a1588fef
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -286,7 -286,7 +286,7 @@@ static void handle_internal_command(in
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "describe", cmd_describe, RUN_SETUP },
                { "diff", cmd_diff },
 -              { "diff-files", cmd_diff_files, RUN_SETUP },
 +              { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
                { "diff-index", cmd_diff_index, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
                { "fast-export", cmd_fast_export, RUN_SETUP },
@@@ -499,7 -499,9 +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",
diff --combined help.c
index b278257aab15f7a195dd4f3c234b3e6b68aaa8f3,74499bf840b4be6215b064e90dec79ec87e24c82..300cd38a9fc2afa61d4390a8fee590997bb6e590
--- 1/help.c
--- 2/help.c
+++ b/help.c
@@@ -1,6 -1,7 +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) */
@@@ -37,6 -38,16 +38,16 @@@ void add_cmdname(struct cmdnames *cmds
        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_;
@@@ -133,10 -144,11 +144,10 @@@ static int is_executable(const char *na
        return st.st_mode & S_IXUSR;
  }
  
 -static unsigned int list_commands_in_dir(struct cmdnames *cmds,
 +static void list_commands_in_dir(struct cmdnames *cmds,
                                         const char *path,
                                         const char *prefix)
  {
 -      unsigned int longest = 0;
        int prefix_len;
        DIR *dir = opendir(path);
        struct dirent *de;
        int len;
  
        if (!dir)
 -              return 0;
 +              return;
        if (!prefix)
                prefix = "git-";
        prefix_len = strlen(prefix);
                if (has_extension(de->d_name, ".exe"))
                        entlen -= 4;
  
 -              if (longest < entlen)
 -                      longest = entlen;
 -
                add_cmdname(cmds, de->d_name + prefix_len, entlen);
        }
        closedir(dir);
        strbuf_release(&buf);
 -
 -      return longest;
  }
  
 -unsigned int load_command_list(const char *prefix,
 +void load_command_list(const char *prefix,
                struct cmdnames *main_cmds,
                struct cmdnames *other_cmds)
  {
 -      unsigned int longest = 0;
 -      unsigned int len;
        const char *env_path = getenv("PATH");
 -      char *paths, *path, *colon;
        const char *exec_path = git_exec_path();
  
 -      if (exec_path)
 -              longest = list_commands_in_dir(main_cmds, exec_path, prefix);
 -
 -      if (!env_path) {
 -              fprintf(stderr, "PATH not set\n");
 -              exit(1);
 +      if (exec_path) {
 +              list_commands_in_dir(main_cmds, exec_path, prefix);
 +              qsort(main_cmds->names, main_cmds->cnt,
 +                    sizeof(*main_cmds->names), cmdname_compare);
 +              uniq(main_cmds);
        }
  
 -      path = paths = xstrdup(env_path);
 -      while (1) {
 -              if ((colon = strchr(path, PATH_SEP)))
 -                      *colon = 0;
 -
 -              len = list_commands_in_dir(other_cmds, path, prefix);
 -              if (len > longest)
 -                      longest = len;
 +      if (env_path) {
 +              char *paths, *path, *colon;
 +              path = paths = xstrdup(env_path);
 +              while (1) {
 +                      if ((colon = strchr(path, PATH_SEP)))
 +                              *colon = 0;
  
 -              if (!colon)
 -                      break;
 -              path = colon + 1;
 -      }
 -      free(paths);
 +                      list_commands_in_dir(other_cmds, path, prefix);
  
 -      qsort(main_cmds->names, main_cmds->cnt,
 -            sizeof(*main_cmds->names), cmdname_compare);
 -      uniq(main_cmds);
 +                      if (!colon)
 +                              break;
 +                      path = colon + 1;
 +              }
 +              free(paths);
  
 -      qsort(other_cmds->names, other_cmds->cnt,
 -            sizeof(*other_cmds->names), cmdname_compare);
 -      uniq(other_cmds);
 +              qsort(other_cmds->names, other_cmds->cnt,
 +                    sizeof(*other_cmds->names), cmdname_compare);
 +              uniq(other_cmds);
 +      }
        exclude_cmds(other_cmds, main_cmds);
 -
 -      return longest;
  }
  
 -void list_commands(const char *title, unsigned int longest,
 -              struct cmdnames *main_cmds, struct cmdnames *other_cmds)
 +void list_commands(const char *title, struct cmdnames *main_cmds,
 +                 struct cmdnames *other_cmds)
  {
 -      const char *exec_path = git_exec_path();
 +      int i, longest = 0;
 +
 +      for (i = 0; i < main_cmds->cnt; i++)
 +              if (longest < main_cmds->names[i]->len)
 +                      longest = main_cmds->names[i]->len;
 +      for (i = 0; i < other_cmds->cnt; i++)
 +              if (longest < other_cmds->names[i]->len)
 +                      longest = other_cmds->names[i]->len;
  
        if (main_cmds->cnt) {
 +              const char *exec_path = git_exec_path();
                printf("available %s in '%s'\n", title, exec_path);
                printf("----------------");
                mput_char('-', strlen(title) + strlen(exec_path));
@@@ -250,9 -268,85 +261,85 @@@ int is_in_cmdlist(struct cmdnames *c, c
        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);
  }
  
diff --combined help.h
index 2733433bf0c74a4d1297a0b3dd6435ac00c872f2,5fc7892705ef3bd042d63487caa11aba5c0cba3a..56bc15406ffc55115fa1c827e0d1d5a7e74c516d
--- 1/help.h
--- 2/help.h
+++ b/help.h
@@@ -5,7 -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;
  };
@@@ -16,14 -16,14 +16,14 @@@ static inline void mput_char(char c, un
                putchar(c);
  }
  
 -unsigned int load_command_list(const char *prefix,
 +void load_command_list(const char *prefix,
                struct cmdnames *main_cmds,
                struct cmdnames *other_cmds);
  void add_cmdname(struct cmdnames *cmds, const char *name, int len);
  /* Here we require that excludes is a sorted list. */
  void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
  int is_in_cmdlist(struct cmdnames *c, const char *s);
 -void list_commands(const char *title, unsigned int longest,
 -              struct cmdnames *main_cmds, struct cmdnames *other_cmds);
 +void list_commands(const char *title, struct cmdnames *main_cmds,
 +                 struct cmdnames *other_cmds);
  
  #endif /* HELP_H */