git-diff: turn on recursion by default
[gitweb.git] / setup.c
diff --git a/setup.c b/setup.c
index 2afdba414a073705440f887593a1b5daa1023758..7b07144af7b0ea96eb2fcd65098331768e46ca3b 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -39,7 +39,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        if (len) {
                int speclen = strlen(path);
                char *n = xmalloc(speclen + len + 1);
-       
+
                memcpy(n, prefix, len);
                memcpy(n + len, path, speclen+1);
                path = n;
@@ -47,7 +47,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        return path;
 }
 
-/* 
+/*
  * 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.
@@ -95,6 +95,8 @@ void verify_non_filename(const char *prefix, const char *arg)
        const char *name;
        struct stat st;
 
+       if (!is_inside_work_tree() || is_inside_git_dir())
+               return;
        if (*arg == '-')
                return; /* flag */
        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
@@ -131,101 +133,238 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
 }
 
 /*
- * Test if it looks like we're at the top level git directory.
+ * Test if it looks like we're at a git directory.
  * We want to see:
  *
- *  - either a .git/objects/ directory _or_ the proper
+ *  - either a objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
- *  - a refs/ directory under ".git"
+ *  - a refs/ directory
  *  - either a HEAD symlink or a HEAD file that is formatted as
- *    a proper "ref:".
+ *    a proper "ref:", or a regular file HEAD that has a properly
+ *    formatted sha1 object name.
  */
-static int is_toplevel_directory(void)
+static int is_git_directory(const char *suspect)
 {
-       if (access(".git/refs/", X_OK) ||
-           access(getenv(DB_ENVIRONMENT) ?
-                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
-           validate_symref(".git/HEAD"))
+       char path[PATH_MAX];
+       size_t len = strlen(suspect);
+
+       strcpy(path, suspect);
+       if (getenv(DB_ENVIRONMENT)) {
+               if (access(getenv(DB_ENVIRONMENT), X_OK))
+                       return 0;
+       }
+       else {
+               strcpy(path + len, "/objects");
+               if (access(path, X_OK))
+                       return 0;
+       }
+
+       strcpy(path + len, "/refs");
+       if (access(path, X_OK))
+               return 0;
+
+       strcpy(path + len, "/HEAD");
+       if (validate_headref(path))
                return 0;
+
        return 1;
 }
 
+static int inside_git_dir = -1;
+
+int is_inside_git_dir(void)
+{
+       if (inside_git_dir >= 0)
+               return inside_git_dir;
+       die("BUG: is_inside_git_dir called before setup_git_directory");
+}
+
+static int inside_work_tree = -1;
+
+int is_inside_work_tree(void)
+{
+       if (inside_git_dir >= 0)
+               return inside_work_tree;
+       die("BUG: is_inside_work_tree called before setup_git_directory");
+}
+
+static char *gitworktree_config;
+
+static int git_setup_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "core.worktree")) {
+               if (gitworktree_config)
+                       strlcpy(gitworktree_config, value, PATH_MAX);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 const char *setup_git_directory_gently(int *nongit_ok)
 {
        static char cwd[PATH_MAX+1];
-       int len, offset;
+       char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
+       const char *gitdirenv, *gitworktree;
+       int wt_rel_gitdir = 0;
 
-       /*
-        * If GIT_DIR is set explicitly, we're not going
-        * to do any discovery, but we still do repository
-        * validation.
-        */
-       if (getenv(GIT_DIR_ENVIRONMENT)) {
-               char path[PATH_MAX];
-               int len = strlen(getenv(GIT_DIR_ENVIRONMENT));
-               if (sizeof(path) - 40 < len)
-                       die("'$%s' too big", GIT_DIR_ENVIRONMENT);
-               memcpy(path, getenv(GIT_DIR_ENVIRONMENT), len);
-               
-               strcpy(path + len, "/refs");
-               if (access(path, X_OK))
-                       goto bad_dir_environ;
-               strcpy(path + len, "/HEAD");
-               if (validate_symref(path))
-                       goto bad_dir_environ;
-               if (getenv(DB_ENVIRONMENT)) {
-                       if (access(getenv(DB_ENVIRONMENT), X_OK))
-                               goto bad_dir_environ;
+       gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+       if (!gitdirenv) {
+               int len, offset;
+
+               if (!getcwd(cwd, sizeof(cwd)-1))
+                       die("Unable to read current working directory");
+
+               offset = len = strlen(cwd);
+               for (;;) {
+                       if (is_git_directory(".git"))
+                               break;
+                       if (offset == 0) {
+                               offset = -1;
+                               break;
+                       }
+                       chdir("..");
+                       while (cwd[--offset] != '/')
+                               ; /* do nothing */
                }
-               else {
-                       strcpy(path + len, "/objects");
-                       if (access(path, X_OK))
-                               goto bad_dir_environ;
+
+               if (offset >= 0) {
+                       inside_work_tree = 1;
+                       git_config(git_default_config);
+                       if (offset == len) {
+                               inside_git_dir = 0;
+                               return NULL;
+                       }
+
+                       cwd[len++] = '/';
+                       cwd[len] = '\0';
+                       inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
+                       return cwd + offset + 1;
                }
-               return NULL;
-       bad_dir_environ:
+
+               if (chdir(cwd))
+                       die("Cannot come back to cwd");
+               if (!is_git_directory(".")) {
+                       if (nongit_ok) {
+                               *nongit_ok = 1;
+                               return NULL;
+                       }
+                       die("Not a git repository");
+               }
+               setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+               gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+               if (!gitdirenv)
+                       die("getenv after setenv failed");
+       }
+
+       if (PATH_MAX - 40 < strlen(gitdirenv)) {
                if (nongit_ok) {
                        *nongit_ok = 1;
                        return NULL;
                }
-               path[len] = 0;
-               die("Not a git repository: '%s'", path);
+               die("$%s too big", GIT_DIR_ENVIRONMENT);
+       }
+       if (!is_git_directory(gitdirenv)) {
+               if (nongit_ok) {
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               die("Not a git repository: '%s'", gitdirenv);
        }
 
-       if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/')
+       if (!getcwd(cwd, sizeof(cwd)-1))
+               die("Unable to read current working directory");
+       if (chdir(gitdirenv)) {
+               if (nongit_ok) {
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               die("Cannot change directory to $%s '%s'",
+                       GIT_DIR_ENVIRONMENT, gitdirenv);
+       }
+       if (!getcwd(gitdir, sizeof(gitdir)-1))
                die("Unable to read current working directory");
+       if (chdir(cwd))
+               die("Cannot come back to cwd");
 
-       offset = len = strlen(cwd);
-       for (;;) {
-               if (is_toplevel_directory())
-                       break;
-               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");
+       /*
+        * In case there is a work tree we may change the directory,
+        * therefore make GIT_DIR an absolute path.
+        */
+       if (gitdirenv[0] != '/') {
+               setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
+               gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+               if (!gitdirenv)
+                       die("getenv after setenv failed");
+               if (PATH_MAX - 40 < strlen(gitdirenv)) {
+                       if (nongit_ok) {
+                               *nongit_ok = 1;
+                               return NULL;
                        }
-               } while (cwd[--offset] != '/');
+                       die("$%s too big after expansion to absolute path",
+                               GIT_DIR_ENVIRONMENT);
+               }
+       }
+
+       strcat(cwd, "/");
+       strcat(gitdir, "/");
+       inside_git_dir = !prefixcmp(cwd, gitdir);
+
+       gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+       if (!gitworktree) {
+               gitworktree_config = worktree;
+               worktree[0] = '\0';
+       }
+       git_config(git_setup_config);
+       if (!gitworktree) {
+               gitworktree_config = NULL;
+               if (worktree[0])
+                       gitworktree = worktree;
+               if (gitworktree && gitworktree[0] != '/')
+                       wt_rel_gitdir = 1;
+       }
+
+       if (wt_rel_gitdir && chdir(gitdirenv))
+               die("Cannot change directory to $%s '%s'",
+                       GIT_DIR_ENVIRONMENT, gitdirenv);
+       if (gitworktree && chdir(gitworktree)) {
+               if (nongit_ok) {
+                       if (wt_rel_gitdir && chdir(cwd))
+                               die("Cannot come back to cwd");
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               if (wt_rel_gitdir)
+                       die("Cannot change directory to working tree '%s'"
+                               " from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
+               else
+                       die("Cannot change directory to working tree '%s'",
+                               gitworktree);
        }
+       if (!getcwd(worktree, sizeof(worktree)-1))
+               die("Unable to read current working directory");
+       strcat(worktree, "/");
+       inside_work_tree = !prefixcmp(cwd, worktree);
 
-       if (offset == len)
+       if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
+           strcmp(worktree, gitdir)) {
+               inside_git_dir = 0;
+       }
+
+       if (!inside_work_tree) {
+               if (chdir(cwd))
+                       die("Cannot come back to cwd");
                return NULL;
+       }
 
-       /* Make "offset" point to past the '/', and add a '/' at the end */
-       offset++;
-       cwd[len++] = '/';
-       cwd[len] = 0;
-       return cwd + offset;
+       if (!strcmp(cwd, worktree))
+               return NULL;
+       return cwd+strlen(worktree);
 }
 
 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"))
@@ -234,6 +373,9 @@ int git_config_perm(const char *var, const char *value)
                    !strcmp(value, "world") ||
                    !strcmp(value, "everybody"))
                        return PERM_EVERYBODY;
+               i = atoi(value);
+               if (i > 1)
+                       return i;
        }
        return git_config_bool(var, value);
 }