| 1 | #include "cache.h" |
| 2 | |
| 3 | const char *prefix_path(const char *prefix, int len, const char *path) |
| 4 | { |
| 5 | const char *orig = path; |
| 6 | for (;;) { |
| 7 | char c; |
| 8 | if (*path != '.') |
| 9 | break; |
| 10 | c = path[1]; |
| 11 | /* "." */ |
| 12 | if (!c) { |
| 13 | path++; |
| 14 | break; |
| 15 | } |
| 16 | /* "./" */ |
| 17 | if (c == '/') { |
| 18 | path += 2; |
| 19 | continue; |
| 20 | } |
| 21 | if (c != '.') |
| 22 | break; |
| 23 | c = path[2]; |
| 24 | if (!c) |
| 25 | path += 2; |
| 26 | else if (c == '/') |
| 27 | path += 3; |
| 28 | else |
| 29 | break; |
| 30 | /* ".." and "../" */ |
| 31 | /* Remove last component of the prefix */ |
| 32 | do { |
| 33 | if (!len) |
| 34 | die("'%s' is outside repository", orig); |
| 35 | len--; |
| 36 | } while (len && prefix[len-1] != '/'); |
| 37 | continue; |
| 38 | } |
| 39 | if (len) { |
| 40 | int speclen = strlen(path); |
| 41 | char *n = xmalloc(speclen + len + 1); |
| 42 | |
| 43 | memcpy(n, prefix, len); |
| 44 | memcpy(n + len, path, speclen+1); |
| 45 | path = n; |
| 46 | } |
| 47 | return path; |
| 48 | } |
| 49 | |
| 50 | const char **get_pathspec(const char *prefix, const char **pathspec) |
| 51 | { |
| 52 | const char *entry = *pathspec; |
| 53 | const char **p; |
| 54 | int prefixlen; |
| 55 | |
| 56 | if (!prefix && !entry) |
| 57 | return NULL; |
| 58 | |
| 59 | if (!entry) { |
| 60 | static const char *spec[2]; |
| 61 | spec[0] = prefix; |
| 62 | spec[1] = NULL; |
| 63 | return spec; |
| 64 | } |
| 65 | |
| 66 | /* Otherwise we have to re-write the entries.. */ |
| 67 | p = pathspec; |
| 68 | prefixlen = prefix ? strlen(prefix) : 0; |
| 69 | do { |
| 70 | *p = prefix_path(prefix, prefixlen, entry); |
| 71 | } while ((entry = *++p) != NULL); |
| 72 | return (const char **) pathspec; |
| 73 | } |
| 74 | |
| 75 | /* |
| 76 | * Test if it looks like we're at the top level git directory. |
| 77 | * We want to see: |
| 78 | * |
| 79 | * - either a .git/objects/ directory _or_ the proper |
| 80 | * GIT_OBJECT_DIRECTORY environment variable |
| 81 | * - a refs/ directory under ".git" |
| 82 | * - either a HEAD symlink or a HEAD file that is formatted as |
| 83 | * a proper "ref:". |
| 84 | */ |
| 85 | static int is_toplevel_directory(void) |
| 86 | { |
| 87 | if (access(".git/refs/", X_OK) || |
| 88 | access(getenv(DB_ENVIRONMENT) ? |
| 89 | getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) || |
| 90 | validate_symref(".git/HEAD")) |
| 91 | return 0; |
| 92 | return 1; |
| 93 | } |
| 94 | |
| 95 | static const char *setup_git_directory_1(void) |
| 96 | { |
| 97 | static char cwd[PATH_MAX+1]; |
| 98 | int len, offset; |
| 99 | |
| 100 | /* |
| 101 | * If GIT_DIR is set explicitly, we're not going |
| 102 | * to do any discovery, but we still do repository |
| 103 | * validation. |
| 104 | */ |
| 105 | if (getenv(GIT_DIR_ENVIRONMENT)) { |
| 106 | char path[PATH_MAX]; |
| 107 | int len = strlen(getenv(GIT_DIR_ENVIRONMENT)); |
| 108 | if (sizeof(path) - 40 < len) |
| 109 | die("'$%s' too big", GIT_DIR_ENVIRONMENT); |
| 110 | memcpy(path, getenv(GIT_DIR_ENVIRONMENT), len); |
| 111 | |
| 112 | strcpy(path + len, "/refs"); |
| 113 | if (access(path, X_OK)) |
| 114 | goto bad_dir_environ; |
| 115 | strcpy(path + len, "/HEAD"); |
| 116 | if (validate_symref(path)) |
| 117 | goto bad_dir_environ; |
| 118 | if (getenv(DB_ENVIRONMENT)) { |
| 119 | if (access(DB_ENVIRONMENT, X_OK)) |
| 120 | goto bad_dir_environ; |
| 121 | } |
| 122 | else { |
| 123 | strcpy(path + len, "/objects"); |
| 124 | if (access(path, X_OK)) |
| 125 | goto bad_dir_environ; |
| 126 | } |
| 127 | return NULL; |
| 128 | bad_dir_environ: |
| 129 | path[len] = 0; |
| 130 | die("Not a git repository: '%s'", path); |
| 131 | } |
| 132 | |
| 133 | if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/') |
| 134 | die("Unable to read current working directory"); |
| 135 | |
| 136 | offset = len = strlen(cwd); |
| 137 | for (;;) { |
| 138 | if (is_toplevel_directory()) |
| 139 | break; |
| 140 | chdir(".."); |
| 141 | do { |
| 142 | if (!offset) |
| 143 | die("Not a git repository"); |
| 144 | } while (cwd[--offset] != '/'); |
| 145 | } |
| 146 | |
| 147 | if (offset == len) |
| 148 | return NULL; |
| 149 | |
| 150 | /* Make "offset" point to past the '/', and add a '/' at the end */ |
| 151 | offset++; |
| 152 | cwd[len++] = '/'; |
| 153 | cwd[len] = 0; |
| 154 | return cwd + offset; |
| 155 | } |
| 156 | |
| 157 | const char *setup_git_directory(void) |
| 158 | { |
| 159 | const char *retval = setup_git_directory_1(); |
| 160 | return retval; |
| 161 | } |