Merge branch 'jk/pathspec-magic-disambiguation'
authorJunio C Hamano <gitster@pobox.com>
Mon, 19 Jun 2017 19:38:42 +0000 (12:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 19 Jun 2017 19:38:43 +0000 (12:38 -0700)
The convention for a command line is to follow "git cmdname
--options" with revisions followed by an optional "--"
disambiguator and then finally pathspecs. When "--" is not there,
we make sure early ones are all interpretable as revs (and do not
look like paths) and later ones are the other way around. A
pathspec with "magic" (e.g. ":/p/a/t/h" that matches p/a/t/h from
the top-level of the working tree, no matter what subdirectory you
are working from) are conservatively judged as "not a path", which
required disambiguation more often. The command line parser
learned to say "it's a pathspec" a bit more often when the syntax
looks like so.

* jk/pathspec-magic-disambiguation:
verify_filename(): flip order of checks
verify_filename(): treat ":(magic)" as a pathspec
check_filename(): handle ":^" path magic
check_filename(): use skip_prefix
check_filename(): refactor ":/" handling
t4208: add check for ":/" without matching file

1  2 
setup.c
diff --combined setup.c
index ba6e855178847a37044eacceae0ebcb17d8e990b,1de87ed842ad60449836736e6380cea50381aab6..751d02b9be627176009cdda4f1b20a3674b0dc95
+++ b/setup.c
@@@ -134,23 -134,27 +134,27 @@@ int path_inside_repo(const char *prefix
  
  int check_filename(const char *prefix, const char *arg)
  {
-       const char *name;
        char *to_free = NULL;
        struct stat st;
  
-       if (starts_with(arg, ":/")) {
-               if (arg[2] == '\0') /* ":/" is root dir, always exists */
+       if (skip_prefix(arg, ":/", &arg)) {
+               if (!*arg) /* ":/" is root dir, always exists */
                        return 1;
-               name = arg + 2;
-       } else if (prefix)
-               name = to_free = prefix_filename(prefix, arg);
-       else
-               name = arg;
-       if (!lstat(name, &st)) {
+               prefix = NULL;
+       } else if (skip_prefix(arg, ":!", &arg) ||
+                  skip_prefix(arg, ":^", &arg)) {
+               if (!*arg) /* excluding everything is silly, but allowed */
+                       return 1;
+       }
+       if (prefix)
+               arg = to_free = prefix_filename(prefix, arg);
+       if (!lstat(arg, &st)) {
                free(to_free);
                return 1; /* file exists */
        }
 -      if (errno == ENOENT || errno == ENOTDIR) {
 +      if (is_missing_file_error(errno)) {
                free(to_free);
                return 0; /* file does not exist */
        }
@@@ -181,6 -185,24 +185,24 @@@ static void NORETURN die_verify_filenam
  
  }
  
+ /*
+  * Check for arguments that don't resolve as actual files,
+  * but which look sufficiently like pathspecs that we'll consider
+  * them such for the purposes of rev/pathspec DWIM parsing.
+  */
+ static int looks_like_pathspec(const char *arg)
+ {
+       /* anything with a wildcard character */
+       if (!no_wildcard(arg))
+               return 1;
+       /* long-form pathspec magic */
+       if (starts_with(arg, ":("))
+               return 1;
+       return 0;
+ }
  /*
   * Verify a filename that we got as an argument for a pathspec
   * entry. Note that a filename that begins with "-" never verifies
@@@ -207,7 -229,7 +229,7 @@@ void verify_filename(const char *prefix
  {
        if (*arg == '-')
                die("bad flag '%s' used after filename", arg);
-       if (check_filename(prefix, arg) || !no_wildcard(arg))
+       if (looks_like_pathspec(arg) || check_filename(prefix, arg))
                return;
        die_verify_filename(prefix, arg, diagnose_misspelt_rev);
  }
@@@ -703,16 -725,11 +725,16 @@@ static const char *setup_discovered_git
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 +              char *to_free = NULL;
 +              const char *ret;
 +
                if (offset != cwd->len && !is_absolute_path(gitdir))
 -                      gitdir = real_pathdup(gitdir, 1);
 +                      gitdir = to_free = real_pathdup(gitdir, 1);
                if (chdir(cwd->buf))
                        die_errno("Could not come back to cwd");
 -              return setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok);
 +              free(to_free);
 +              return ret;
        }
  
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
@@@ -753,7 -770,7 +775,7 @@@ static const char *setup_bare_git_dir(s
  
        /* --work-tree is set without --git-dir; use discovered one */
        if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
 -              const char *gitdir;
 +              static const char *gitdir;
  
                gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset);
                if (chdir(cwd->buf))