diff --git a/builtin/init-db.c b/builtin/init-db.c index edc40ff..a91e762 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -7,6 +7,7 @@ #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" +#include "environment.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" @@ -34,6 +35,27 @@ static void safe_create_dir(const char *dir, int share) die("Could not make %s writable by group", dir); } +static void mkdir_recusive(char *directory) +{ + char tmp[PATH_MAX+1]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp), "%s", directory); + len = strlen(tmp); + if(is_dir_sep(tmp[len - 1])) + tmp[len - 1] = 0; + + for(p = tmp + 1; *p; p++) + if(is_dir_sep(*p)) { + char c = *p; + *p = 0; + safe_create_dir(tmp, 1); + *p = c; + } + safe_create_dir(tmp,1); +} + static void copy_templates_1(char *path, int baselen, char *template, int template_baselen, DIR *dir) @@ -473,7 +495,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR * without --bare. Catch the error early. */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); + git_dir = get_git_dir_from_environment(); if ((!git_dir || is_bare_repository_cfg == 1) && getenv(GIT_WORK_TREE_ENVIRONMENT)) die("%s (or --work-tree=) not allowed without " @@ -490,7 +512,19 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) if (is_bare_repository_cfg < 0) is_bare_repository_cfg = guess_repository_type(git_dir); - if (!is_bare_repository_cfg) { + if (is_git_dir_global_environment()) { + char *global_base_dir = get_git_dir_global_base_dir(); + mkdir_recusive(global_base_dir); + if (!git_work_tree_cfg) { + git_work_tree_cfg = xcalloc(PATH_MAX, 1); + strncpy(git_work_tree_cfg, global_base_dir, PATH_MAX); + } + if (access(get_git_work_tree(), X_OK)) + die_errno ("Cannot access work tree '%s'", + get_git_work_tree()); + } + + if (!is_bare_repository_cfg && !is_git_dir_global_environment()) { if (git_dir) { const char *git_dir_parent = strrchr(git_dir, '/'); if (git_dir_parent) { diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 8fbf9d0..b64260f 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -9,6 +9,7 @@ #include "quote.h" #include "builtin.h" #include "parse-options.h" +#include "environment.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -642,7 +643,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--git-dir")) { - const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); + const char *gitdir = get_git_dir_from_environment(); static char cwd[PATH_MAX]; int len; if (gitdir) { diff --git a/cache.h b/cache.h index 5eb0573..57a647d 100644 --- a/cache.h +++ b/cache.h @@ -372,6 +372,8 @@ static inline enum object_type object_type(unsigned int mode) } #define GIT_DIR_ENVIRONMENT "GIT_DIR" +#define GIT_DIR_GLOBAL_ENVIRONMENT "GIT_DIR_GLOBAL" +#define GIT_DIR_GLOBAL_SPECIAL_ROOT_DIRECTORY "/SPECIAL_ROOT_DIRECTORY" #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" diff --git a/environment.c b/environment.c index 876c5e5..d9768a9 100644 --- a/environment.c +++ b/environment.c @@ -8,6 +8,9 @@ * are. */ #include "cache.h" +#include "setup.h" + +#define DIR_SEPARATOR "/" char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; @@ -80,9 +83,129 @@ const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = { NULL }; +char* path_shorten(char *path) +{ + int index = strlen(path); + + while (index > 0) { + index--; + if (is_dir_sep(path[index])) { + path[index] = 0; + return path; + } + } + + // should never get here + return ""; +} + +static char *get_dir_global_environment(void) +{ + return getenv(GIT_DIR_GLOBAL_ENVIRONMENT); +} + +char *get_git_dir_global_base_dir(void) +{ + static char cwd_buf[PATH_MAX+1]; + static char dir_global_buf[PATH_MAX+1]; + static char git_dir_buf[PATH_MAX+1]; + int global_environment_len = 0; + char *git_dir_global_environment = get_dir_global_environment(); + + if (!getcwd(cwd_buf, sizeof(cwd_buf)-1)) + die_errno("Unable to read current working directory"); + + if (!is_absolute_path(git_dir_global_environment)) + die_errno("Global environment variable" GIT_DIR_GLOBAL_ENVIRONMENT " needs absolute path!"); + + strncpy(dir_global_buf, git_dir_global_environment, sizeof(dir_global_buf)); + global_environment_len = strlen(dir_global_buf); + if (is_dir_sep(dir_global_buf[global_environment_len])) + dir_global_buf[global_environment_len] = 0; + + return mksnpath(git_dir_buf, sizeof(git_dir_buf), "%s%s", dir_global_buf, cwd_buf); +} + +char *get_git_dir_from_global_environment(char *git_dir_global_environment) +{ + static char cwd_buf[PATH_MAX+1]; + static char cwd_original_buf[PATH_MAX+1]; + static char dir_global_buf[PATH_MAX+1]; + static char git_dir_buf[PATH_MAX+1]; + char *cwd = cwd_buf; + char *git_dir = git_dir_buf; + int found = 0; + int global_environment_len = 0; + + if (!getcwd(cwd_buf, sizeof(cwd_buf)-1)) + die_errno("Unable to read current working directory"); + + if (!is_absolute_path(git_dir_global_environment)) + die_errno("Global environment variable" GIT_DIR_GLOBAL_ENVIRONMENT " needs absolute path!"); + + strncpy(dir_global_buf, git_dir_global_environment, sizeof(dir_global_buf)); + global_environment_len = strlen(dir_global_buf); + if (is_dir_sep(dir_global_buf[global_environment_len])) + dir_global_buf[global_environment_len] = 0; + + if (strlen(cwd) == 1 && is_dir_sep(cwd[0])) + { + // special case for / git repository + git_dir = mksnpath(git_dir_buf, sizeof(git_dir_buf), "%s%s" DIR_SEPARATOR "%s", dir_global_buf, GIT_DIR_GLOBAL_SPECIAL_ROOT_DIRECTORY, DEFAULT_GIT_DIR_ENVIRONMENT); + return git_dir; + } + + strncpy(cwd_original_buf, cwd, sizeof(cwd_original_buf)); + + /* + * Test in the following order (relative to the cwd): + * - .git (file containing "gitdir: ") + * - .git/ + * - ./ (bare) + * - ../.git + * - ../.git/ + * - ../ (bare) + * - ../../.git/ + * etc. + */ + for (;;) { + if (*cwd == '\0') { + break; + } + + git_dir = mksnpath(git_dir_buf, sizeof(git_dir_buf), "%s%s" DIR_SEPARATOR "%s", dir_global_buf, cwd, DEFAULT_GIT_DIR_ENVIRONMENT); + if (is_git_directory(git_dir)) { + found = 1; + break; + } + + cwd = path_shorten(cwd); + } + + if (!found) { + git_dir = mksnpath(git_dir_buf, sizeof(git_dir_buf), "%s%s/%s", dir_global_buf, cwd_original_buf, DEFAULT_GIT_DIR_ENVIRONMENT); + return git_dir; + } + + return git_dir; +} + +int is_git_dir_global_environment(void) +{ + return (get_dir_global_environment() != NULL); +} + +char *get_git_dir_from_environment(void) +{ + char *git_dir_global_environment = get_dir_global_environment(); + if (git_dir_global_environment) return get_git_dir_from_global_environment(git_dir_global_environment); + + return getenv(GIT_DIR_ENVIRONMENT); +} + static void setup_git_env(void) { - git_dir = getenv(GIT_DIR_ENVIRONMENT); + git_dir = get_git_dir_from_environment(); if (!git_dir) git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (!git_dir) diff --git a/setup.c b/setup.c index 5716d90..6ee3a59 100644 --- a/setup.c +++ b/setup.c @@ -1,5 +1,6 @@ #include "cache.h" #include "dir.h" +#include "environment.h" static int inside_git_dir = -1; static int inside_work_tree = -1; @@ -165,7 +166,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); @@ -337,7 +338,7 @@ const char *setup_git_directory_gently(int *nongit_ok) * to do any discovery, but we still do repository * validation. */ - gitdirenv = getenv(GIT_DIR_ENVIRONMENT); + gitdirenv = get_git_dir_from_environment(); if (gitdirenv) { if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT);