Sync with i18n-po updates in maint
[gitweb.git] / setup.c
diff --git a/setup.c b/setup.c
index e7a37863ccdfe8c38e079f4d971f76a523bfd83b..994976946b4b451ee41508a2649987c9b4e9bdbc 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -40,34 +40,6 @@ char *prefix_path(const char *prefix, int len, const char *path)
        return sanitized;
 }
 
-/*
- * Unlike prefix_path, this should be used if the named file does
- * not have to interact with index entry; i.e. name of a random file
- * on the filesystem.
- */
-const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
-{
-       static char path[PATH_MAX];
-#ifndef WIN32
-       if (!pfx_len || is_absolute_path(arg))
-               return arg;
-       memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-#else
-       char *p;
-       /* don't add prefix to absolute paths, but still replace '\' by '/' */
-       if (is_absolute_path(arg))
-               pfx_len = 0;
-       else if (pfx_len)
-               memcpy(path, pfx, pfx_len);
-       strcpy(path + pfx_len, arg);
-       for (p = path + pfx_len; *p; p++)
-               if (*p == '\\')
-                       *p = '/';
-#endif
-       return path;
-}
-
 int check_filename(const char *prefix, const char *arg)
 {
        const char *name;
@@ -81,12 +53,27 @@ int check_filename(const char *prefix, const char *arg)
        die_errno("failed to stat '%s'", arg);
 }
 
-static void NORETURN die_verify_filename(const char *prefix, const char *arg)
+static void NORETURN die_verify_filename(const char *prefix,
+                                        const char *arg,
+                                        int diagnose_misspelt_rev)
 {
        unsigned char sha1[20];
        unsigned mode;
-       /* try a detailed diagnostic ... */
-       get_sha1_with_mode_1(arg, sha1, &mode, 0, prefix);
+
+       if (!diagnose_misspelt_rev)
+               die("%s: no such path in the working tree.\n"
+                   "Use '-- <path>...' to specify paths that do not exist locally.",
+                   arg);
+       /*
+        * Saying "'(icase)foo' does not exist in the index" when the
+        * user gave us ":(icase)foo" is just stupid.  A magic pathspec
+        * begins with a colon and is followed by a non-alnum; do not
+        * let get_sha1_with_mode_1(only_to_die=1) to even trigger.
+        */
+       if (!(arg[0] == ':' && !isalnum(arg[1])))
+               /* try a detailed diagnostic ... */
+               get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);
+
        /* ... or fall back the most general message. */
        die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
            "Use '--' to separate paths from revisions", arg);
@@ -99,14 +86,29 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg)
  * as true, because even if such a filename were to exist, we want
  * it to be preceded by the "--" marker (or we want the user to
  * use a format like "./-filename")
+ *
+ * The "diagnose_misspelt_rev" is used to provide a user-friendly
+ * diagnosis when dying upon finding that "name" is not a pathname.
+ * If set to 1, the diagnosis will try to diagnose "name" as an
+ * invalid object name (e.g. HEAD:foo). If set to 0, the diagnosis
+ * will only complain about an inexisting file.
+ *
+ * This function is typically called to check that a "file or rev"
+ * argument is unambiguous. In this case, the caller will want
+ * diagnose_misspelt_rev == 1 when verifying the first non-rev
+ * argument (which could have been a revision), and
+ * diagnose_misspelt_rev == 0 for the next ones (because we already
+ * saw a filename, there's not ambiguity anymore).
  */
-void verify_filename(const char *prefix, const char *arg)
+void verify_filename(const char *prefix,
+                    const char *arg,
+                    int diagnose_misspelt_rev)
 {
        if (*arg == '-')
                die("bad flag '%s' used after filename", arg);
        if (check_filename(prefix, arg))
                return;
-       die_verify_filename(prefix, arg);
+       die_verify_filename(prefix, arg, diagnose_misspelt_rev);
 }
 
 /*
@@ -126,6 +128,105 @@ void verify_non_filename(const char *prefix, const char *arg)
            "Use '--' to separate filenames from revisions", arg);
 }
 
+/*
+ * Magic pathspec
+ *
+ * NEEDSWORK: These need to be moved to dir.h or even to a new
+ * pathspec.h when we restructure get_pathspec() users to use the
+ * "struct pathspec" interface.
+ *
+ * Possible future magic semantics include stuff like:
+ *
+ *     { PATHSPEC_NOGLOB, '!', "noglob" },
+ *     { PATHSPEC_ICASE, '\0', "icase" },
+ *     { PATHSPEC_RECURSIVE, '*', "recursive" },
+ *     { PATHSPEC_REGEXP, '\0', "regexp" },
+ *
+ */
+#define PATHSPEC_FROMTOP    (1<<0)
+
+static struct pathspec_magic {
+       unsigned bit;
+       char mnemonic; /* this cannot be ':'! */
+       const char *name;
+} pathspec_magic[] = {
+       { PATHSPEC_FROMTOP, '/', "top" },
+};
+
+/*
+ * Take an element of a pathspec and check for magic signatures.
+ * Append the result to the prefix.
+ *
+ * For now, we only parse the syntax and throw out anything other than
+ * "top" magic.
+ *
+ * NEEDSWORK: This needs to be rewritten when we start migrating
+ * get_pathspec() users to use the "struct pathspec" interface.  For
+ * example, a pathspec element may be marked as case-insensitive, but
+ * the prefix part must always match literally, and a single stupid
+ * string cannot express such a case.
+ */
+static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
+{
+       unsigned magic = 0;
+       const char *copyfrom = elt;
+       int i;
+
+       if (elt[0] != ':') {
+               ; /* nothing to do */
+       } else if (elt[1] == '(') {
+               /* longhand */
+               const char *nextat;
+               for (copyfrom = elt + 2;
+                    *copyfrom && *copyfrom != ')';
+                    copyfrom = nextat) {
+                       size_t len = strcspn(copyfrom, ",)");
+                       if (copyfrom[len] == ')')
+                               nextat = copyfrom + len;
+                       else
+                               nextat = copyfrom + len + 1;
+                       if (!len)
+                               continue;
+                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+                               if (strlen(pathspec_magic[i].name) == len &&
+                                   !strncmp(pathspec_magic[i].name, copyfrom, len)) {
+                                       magic |= pathspec_magic[i].bit;
+                                       break;
+                               }
+                       if (ARRAY_SIZE(pathspec_magic) <= i)
+                               die("Invalid pathspec magic '%.*s' in '%s'",
+                                   (int) len, copyfrom, elt);
+               }
+               if (*copyfrom == ')')
+                       copyfrom++;
+       } else {
+               /* shorthand */
+               for (copyfrom = elt + 1;
+                    *copyfrom && *copyfrom != ':';
+                    copyfrom++) {
+                       char ch = *copyfrom;
+
+                       if (!is_pathspec_magic(ch))
+                               break;
+                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+                               if (pathspec_magic[i].mnemonic == ch) {
+                                       magic |= pathspec_magic[i].bit;
+                                       break;
+                               }
+                       if (ARRAY_SIZE(pathspec_magic) <= i)
+                               die("Unimplemented pathspec magic '%c' in '%s'",
+                                   ch, elt);
+               }
+               if (*copyfrom == ':')
+                       copyfrom++;
+       }
+
+       if (magic & PATHSPEC_FROMTOP)
+               return xstrdup(copyfrom);
+       else
+               return prefix_path(prefix, prefixlen, copyfrom);
+}
+
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
@@ -147,8 +248,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        dst = pathspec;
        prefixlen = prefix ? strlen(prefix) : 0;
        while (*src) {
-               const char *p = prefix_path(prefix, prefixlen, *src);
-               *(dst++) = p;
+               *(dst++) = prefix_pathspec(prefix, prefixlen, *src);
                src++;
        }
        *dst = NULL;
@@ -168,7 +268,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
  *    a proper "ref:", or a regular file HEAD that has a properly
  *    formatted sha1 object name.
  */
-static int is_git_directory(const char *suspect)
+int is_git_directory(const char *suspect)
 {
        char path[PATH_MAX];
        size_t len = strlen(suspect);
@@ -268,7 +368,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
  * Try to read the location of the git directory from the .git file,
  * return path to git directory if found.
  */
-const char *read_gitfile_gently(const char *path)
+const char *read_gitfile(const char *path)
 {
        char *buf;
        char *dir;
@@ -325,11 +425,12 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
        const char *worktree;
        char *gitfile;
+       int offset;
 
        if (PATH_MAX - 40 < strlen(gitdirenv))
                die("'$%s' too big", GIT_DIR_ENVIRONMENT);
 
-       gitfile = (char*)read_gitfile_gently(gitdirenv);
+       gitfile = (char*)read_gitfile(gitdirenv);
        if (gitfile) {
                gitfile = xstrdup(gitfile);
                gitdirenv = gitfile;
@@ -390,15 +491,15 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                return NULL;
        }
 
-       if (!prefixcmp(cwd, worktree) &&
-           cwd[strlen(worktree)] == '/') { /* cwd inside worktree */
+       offset = dir_inside_of(cwd, worktree);
+       if (offset >= 0) {      /* cwd inside worktree? */
                set_git_dir(real_path(gitdirenv));
                if (chdir(worktree))
                        die_errno("Could not chdir to '%s'", worktree);
                cwd[len++] = '/';
                cwd[len] = '\0';
                free(gitfile);
-               return cwd + strlen(worktree) + 1;
+               return cwd + offset;
        }
 
        /* cwd outside worktree */
@@ -489,13 +590,15 @@ static const char *setup_nongit(const char *cwd, int *nongit_ok)
        return NULL;
 }
 
-static dev_t get_device_or_die(const char *path, const char *prefix)
+static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len)
 {
        struct stat buf;
-       if (stat(path, &buf))
-               die_errno("failed to stat '%s%s%s'",
+       if (stat(path, &buf)) {
+               die_errno("failed to stat '%*s%s%s'",
+                               prefix_len,
                                prefix ? prefix : "",
                                prefix ? "/" : "", path);
+       }
        return buf.st_dev;
 }
 
@@ -509,7 +612,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
        static char cwd[PATH_MAX+1];
        const char *gitdirenv, *ret;
        char *gitfile;
-       int len, offset, ceil_offset;
+       int len, offset, offset_parent, ceil_offset;
        dev_t current_device = 0;
        int one_filesystem = 1;
 
@@ -551,9 +654,9 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
        if (one_filesystem)
-               current_device = get_device_or_die(".", NULL);
+               current_device = get_device_or_die(".", NULL, 0);
        for (;;) {
-               gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                if (gitfile)
                        gitdirenv = gitfile = xstrdup(gitfile);
                else {
@@ -573,11 +676,12 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                if (is_git_directory("."))
                        return setup_bare_git_dir(cwd, offset, len, nongit_ok);
 
-               while (--offset > ceil_offset && cwd[offset] != '/');
-               if (offset <= ceil_offset)
+               offset_parent = offset;
+               while (--offset_parent > ceil_offset && cwd[offset_parent] != '/');
+               if (offset_parent <= ceil_offset)
                        return setup_nongit(cwd, nongit_ok);
                if (one_filesystem) {
-                       dev_t parent_device = get_device_or_die("..", cwd);
+                       dev_t parent_device = get_device_or_die("..", cwd, offset);
                        if (parent_device != current_device) {
                                if (nongit_ok) {
                                        if (chdir(cwd))
@@ -586,7 +690,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                                        return NULL;
                                }
                                cwd[offset] = '\0';
-                               die("Not a git repository (or any parent up to mount parent %s)\n"
+                               die("Not a git repository (or any parent up to mount point %s)\n"
                                "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd);
                        }
                }
@@ -594,6 +698,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                        cwd[offset] = '\0';
                        die_errno("Cannot change to '%s/..'", cwd);
                }
+               offset = offset_parent;
        }
 }
 
@@ -602,6 +707,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
        const char *prefix;
 
        prefix = setup_git_directory_gently_1(nongit_ok);
+       if (prefix)
+               setenv("GIT_PREFIX", prefix, 1);
+       else
+               setenv("GIT_PREFIX", "", 1);
+
        if (startup_info) {
                startup_info->have_repository = !nongit_ok || !*nongit_ok;
                startup_info->prefix = prefix;
@@ -695,3 +805,10 @@ const char *setup_git_directory(void)
 {
        return setup_git_directory_gently(NULL);
 }
+
+const char *resolve_gitdir(const char *suspect)
+{
+       if (is_git_directory(suspect))
+               return suspect;
+       return read_gitfile(suspect);
+}