lstat_cache(): print a warning if doing ping-pong between cache types
[gitweb.git] / setup.c
diff --git a/setup.c b/setup.c
index 3d2d9580f3283b4e4bc741d98863777510bd5173..dfda532adc16f5e6d25d7cfc5add3e0e2b6a5209 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -6,11 +6,17 @@ static int inside_work_tree = -1;
 
 static int sanitary_path_copy(char *dst, const char *src)
 {
-       char *dst0 = dst;
+       char *dst0;
 
-       if (*src == '/') {
+       if (has_dos_drive_prefix(src)) {
+               *dst++ = *src++;
+               *dst++ = *src++;
+       }
+       dst0 = dst;
+
+       if (is_dir_sep(*src)) {
                *dst++ = '/';
-               while (*src == '/')
+               while (is_dir_sep(*src))
                        src++;
        }
 
@@ -26,27 +32,24 @@ static int sanitary_path_copy(char *dst, const char *src)
                 * (4) "../"          -- strip one, eat slash and continue.
                 */
                if (c == '.') {
-                       switch (src[1]) {
-                       case '\0':
+                       if (!src[1]) {
                                /* (1) */
                                src++;
-                               break;
-                       case '/':
+                       } else if (is_dir_sep(src[1])) {
                                /* (2) */
                                src += 2;
-                               while (*src == '/')
+                               while (is_dir_sep(*src))
                                        src++;
                                continue;
-                       case '.':
-                               switch (src[2]) {
-                               case '\0':
+                       } else if (src[1] == '.') {
+                               if (!src[2]) {
                                        /* (3) */
                                        src += 2;
                                        goto up_one;
-                               case '/':
+                               } else if (is_dir_sep(src[2])) {
                                        /* (4) */
                                        src += 3;
-                                       while (*src == '/')
+                                       while (is_dir_sep(*src))
                                                src++;
                                        goto up_one;
                                }
@@ -54,11 +57,11 @@ static int sanitary_path_copy(char *dst, const char *src)
                }
 
                /* copy up to the next '/', and eat all '/' */
-               while ((c = *src++) != '\0' && c != '/')
+               while ((c = *src++) != '\0' && !is_dir_sep(c))
                        *dst++ = c;
-               if (c == '/') {
-                       *dst++ = c;
-                       while (c == '/')
+               if (is_dir_sep(c)) {
+                       *dst++ = '/';
+                       while (is_dir_sep(c))
                                c = *src++;
                        src--;
                } else if (!c)
@@ -77,7 +80,7 @@ static int sanitary_path_copy(char *dst, const char *src)
                        if (dst <= dst0)
                                break;
                        c = *dst--;
-                       if (c == '/') {
+                       if (c == '/') { /* MinGW: cannot be '\\' anymore */
                                dst += 2;
                                break;
                        }
@@ -107,9 +110,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
                if (strncmp(sanitized, work_tree, len) ||
                    (sanitized[len] != '\0' && sanitized[len] != '/')) {
                error_out:
-                       error("'%s' is outside repository", orig);
-                       free(sanitized);
-                       return NULL;
+                       die("'%s' is outside repository", orig);
                }
                if (sanitized[len] == '/')
                        len++;
@@ -126,10 +127,23 @@ const char *prefix_path(const char *prefix, int len, const char *path)
 const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
 {
        static char path[PATH_MAX];
+#ifndef __MINGW32__
        if (!pfx || !*pfx || 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
+               memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+       for (p = path + pfx_len; *p; p++)
+               if (*p == '\\')
+                       *p = '/';
+#endif
        return path;
 }
 
@@ -200,10 +214,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        prefixlen = prefix ? strlen(prefix) : 0;
        while (*src) {
                const char *p = prefix_path(prefix, prefixlen, *src);
-               if (p)
-                       *(dst++) = p;
-               else
-                       exit(128); /* error message already given */
+               *(dst++) = p;
                src++;
        }
        *dst = NULL;
@@ -292,15 +303,16 @@ void setup_work_tree(void)
        work_tree = get_git_work_tree();
        git_dir = get_git_dir();
        if (!is_absolute_path(git_dir))
-               set_git_dir(make_absolute_path(git_dir));
+               git_dir = make_absolute_path(git_dir);
        if (!work_tree || chdir(work_tree))
                die("This operation must be run in a work tree");
+       set_git_dir(make_relative_path(git_dir, work_tree));
        initialized = 1;
 }
 
 static int check_repository_format_gently(int *nongit_ok)
 {
-       git_config(check_repository_format_version);
+       git_config(check_repository_format_version, NULL);
        if (GIT_REPO_VERSION < repository_format_version) {
                if (!nongit_ok)
                        die ("Expected git repo version <= %d, found %d",
@@ -314,6 +326,44 @@ static int check_repository_format_gently(int *nongit_ok)
        return 0;
 }
 
+/*
+ * 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)
+{
+       char *buf;
+       struct stat st;
+       int fd;
+       size_t len;
+
+       if (stat(path, &st))
+               return NULL;
+       if (!S_ISREG(st.st_mode))
+               return NULL;
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die("Error opening %s: %s", path, strerror(errno));
+       buf = xmalloc(st.st_size + 1);
+       len = read_in_full(fd, buf, st.st_size);
+       close(fd);
+       if (len != st.st_size)
+               die("Error reading %s", path);
+       buf[len] = '\0';
+       if (prefixcmp(buf, "gitdir: "))
+               die("Invalid gitfile format: %s", path);
+       while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
+               len--;
+       if (len < 9)
+               die("No path in gitfile: %s", path);
+       buf[len] = '\0';
+       if (!is_git_directory(buf + 8))
+               die("Not a git repository: %s", buf + 8);
+       path = make_absolute_path(buf + 8);
+       free(buf);
+       return path;
+}
+
 /*
  * We cannot decide in this function whether we are in the work tree or
  * not, since the config can only be read _after_ this function was called.
@@ -321,9 +371,11 @@ static int check_repository_format_gently(int *nongit_ok)
 const char *setup_git_directory_gently(int *nongit_ok)
 {
        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
+       const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
        static char cwd[PATH_MAX+1];
        const char *gitdirenv;
-       int len, offset;
+       const char *gitfile_dir;
+       int len, offset, ceil_offset;
 
        /*
         * Let's assume that we are in a git repository.
@@ -375,10 +427,16 @@ const char *setup_git_directory_gently(int *nongit_ok)
        if (!getcwd(cwd, sizeof(cwd)-1))
                die("Unable to read current working directory");
 
+       ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
+       if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
+               ceil_offset = 1;
+
        /*
         * Test in the following order (relative to the cwd):
+        * - .git (file containing "gitdir: <path>")
         * - .git/
         * - ./ (bare)
+        * - ../.git
         * - ../.git/
         * - ../ (bare)
         * - ../../.git/
@@ -386,28 +444,38 @@ const char *setup_git_directory_gently(int *nongit_ok)
         */
        offset = len = strlen(cwd);
        for (;;) {
+               gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
+               if (gitfile_dir) {
+                       if (set_git_dir(gitfile_dir))
+                               die("Repository setup failed");
+                       break;
+               }
                if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
                        break;
                if (is_git_directory(".")) {
                        inside_git_dir = 1;
                        if (!work_tree_env)
                                inside_work_tree = 0;
-                       setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+                       if (offset != len) {
+                               cwd[offset] = '\0';
+                               setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+                       } else
+                               setenv(GIT_DIR_ENVIRONMENT, ".", 1);
                        check_repository_format_gently(nongit_ok);
                        return NULL;
                }
-               chdir("..");
-               do {
-                       if (!offset) {
-                               if (nongit_ok) {
-                                       if (chdir(cwd))
-                                               die("Cannot come back to cwd");
-                                       *nongit_ok = 1;
-                                       return NULL;
-                               }
-                               die("Not a git repository");
+               while (--offset > ceil_offset && cwd[offset] != '/');
+               if (offset <= ceil_offset) {
+                       if (nongit_ok) {
+                               if (chdir(cwd))
+                                       die("Cannot come back to cwd");
+                               *nongit_ok = 1;
+                               return NULL;
                        }
-               } while (cwd[--offset] != '/');
+                       die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
+               }
+               if (chdir(".."))
+                       die("Cannot change to %s/..: %s", cwd, strerror(errno));
        }
 
        inside_git_dir = 0;
@@ -428,24 +496,56 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
 int git_config_perm(const char *var, const char *value)
 {
-       if (value) {
-               int i;
-               if (!strcmp(value, "umask"))
-                       return PERM_UMASK;
-               if (!strcmp(value, "group"))
-                       return PERM_GROUP;
-               if (!strcmp(value, "all") ||
-                   !strcmp(value, "world") ||
-                   !strcmp(value, "everybody"))
-                       return PERM_EVERYBODY;
-               i = atoi(value);
-               if (i > 1)
-                       return i;
+       int i;
+       char *endptr;
+
+       if (value == NULL)
+               return PERM_GROUP;
+
+       if (!strcmp(value, "umask"))
+               return PERM_UMASK;
+       if (!strcmp(value, "group"))
+               return PERM_GROUP;
+       if (!strcmp(value, "all") ||
+           !strcmp(value, "world") ||
+           !strcmp(value, "everybody"))
+               return PERM_EVERYBODY;
+
+       /* Parse octal numbers */
+       i = strtol(value, &endptr, 8);
+
+       /* If not an octal number, maybe true/false? */
+       if (*endptr != 0)
+               return git_config_bool(var, value) ? PERM_GROUP : PERM_UMASK;
+
+       /*
+        * Treat values 0, 1 and 2 as compatibility cases, otherwise it is
+        * a chmod value.
+        */
+       switch (i) {
+       case PERM_UMASK:               /* 0 */
+               return PERM_UMASK;
+       case OLD_PERM_GROUP:           /* 1 */
+               return PERM_GROUP;
+       case OLD_PERM_EVERYBODY:       /* 2 */
+               return PERM_EVERYBODY;
        }
-       return git_config_bool(var, value);
+
+       /* A filemode value was given: 0xxx */
+
+       if ((i & 0600) != 0600)
+               die("Problem with core.sharedRepository filemode value "
+                   "(0%.3o).\nThe owner of files must always have "
+                   "read and write permissions.", i);
+
+       /*
+        * Mask filemode value. Others can not get write permission.
+        * x flags for directories are handled separately.
+        */
+       return i & 0666;
 }
 
-int check_repository_format_version(const char *var, const char *value)
+int check_repository_format_version(const char *var, const char *value, void *cb)
 {
        if (strcmp(var, "core.repositoryformatversion") == 0)
                repository_format_version = git_config_int(var, value);
@@ -481,6 +581,8 @@ const char *setup_git_directory(void)
                if (retval && chdir(retval))
                        die ("Could not jump back into original cwd");
                rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
+               if (rel && *rel && chdir(get_git_work_tree()))
+                       die ("Could not jump to working directory");
                return rel && *rel ? strcat(rel, "/") : NULL;
        }