1#include"cache.h" 2 3const char*prefix_path(const char*prefix,int len,const char*path) 4{ 5const char*orig = path; 6for(;;) { 7char c; 8if(*path !='.') 9break; 10 c = path[1]; 11/* "." */ 12if(!c) { 13 path++; 14break; 15} 16/* "./" */ 17if(c =='/') { 18 path +=2; 19continue; 20} 21if(c !='.') 22break; 23 c = path[2]; 24if(!c) 25 path +=2; 26else if(c =='/') 27 path +=3; 28else 29break; 30/* ".." and "../" */ 31/* Remove last component of the prefix */ 32do{ 33if(!len) 34die("'%s' is outside repository", orig); 35 len--; 36}while(len && prefix[len-1] !='/'); 37continue; 38} 39if(len) { 40int speclen =strlen(path); 41char*n =xmalloc(speclen + len +1); 42 43memcpy(n, prefix, len); 44memcpy(n + len, path, speclen+1); 45 path = n; 46} 47return path; 48} 49 50/* 51 * Unlike prefix_path, this should be used if the named file does 52 * not have to interact with index entry; i.e. name of a random file 53 * on the filesystem. 54 */ 55const char*prefix_filename(const char*pfx,int pfx_len,const char*arg) 56{ 57static char path[PATH_MAX]; 58if(!pfx || !*pfx || arg[0] =='/') 59return arg; 60memcpy(path, pfx, pfx_len); 61strcpy(path + pfx_len, arg); 62return path; 63} 64 65/* 66 * Verify a filename that we got as an argument for a pathspec 67 * entry. Note that a filename that begins with "-" never verifies 68 * as true, because even if such a filename were to exist, we want 69 * it to be preceded by the "--" marker (or we want the user to 70 * use a format like "./-filename") 71 */ 72voidverify_filename(const char*prefix,const char*arg) 73{ 74const char*name; 75struct stat st; 76 77if(*arg =='-') 78die("bad flag '%s' used after filename", arg); 79 name = prefix ?prefix_filename(prefix,strlen(prefix), arg) : arg; 80if(!lstat(name, &st)) 81return; 82if(errno == ENOENT) 83die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" 84"Use '--' to separate paths from revisions", arg); 85die("'%s':%s", arg,strerror(errno)); 86} 87 88/* 89 * Opposite of the above: the command line did not have -- marker 90 * and we parsed the arg as a refname. It should not be interpretable 91 * as a filename. 92 */ 93voidverify_non_filename(const char*prefix,const char*arg) 94{ 95const char*name; 96struct stat st; 97 98if(*arg =='-') 99return;/* flag */ 100 name = prefix ?prefix_filename(prefix,strlen(prefix), arg) : arg; 101if(!lstat(name, &st)) 102die("ambiguous argument '%s': both revision and filename\n" 103"Use '--' to separate filenames from revisions", arg); 104if(errno != ENOENT) 105die("'%s':%s", arg,strerror(errno)); 106} 107 108const char**get_pathspec(const char*prefix,const char**pathspec) 109{ 110const char*entry = *pathspec; 111const char**p; 112int prefixlen; 113 114if(!prefix && !entry) 115return NULL; 116 117if(!entry) { 118static const char*spec[2]; 119 spec[0] = prefix; 120 spec[1] = NULL; 121return spec; 122} 123 124/* Otherwise we have to re-write the entries.. */ 125 p = pathspec; 126 prefixlen = prefix ?strlen(prefix) :0; 127do{ 128*p =prefix_path(prefix, prefixlen, entry); 129}while((entry = *++p) != NULL); 130return(const char**) pathspec; 131} 132 133/* 134 * Test if it looks like we're at the top level git directory. 135 * We want to see: 136 * 137 * - either a .git/objects/ directory _or_ the proper 138 * GIT_OBJECT_DIRECTORY environment variable 139 * - a refs/ directory under ".git" 140 * - either a HEAD symlink or a HEAD file that is formatted as 141 * a proper "ref:". 142 */ 143static intis_toplevel_directory(void) 144{ 145if(access(".git/refs/", X_OK) || 146access(getenv(DB_ENVIRONMENT) ? 147getenv(DB_ENVIRONMENT) :".git/objects/", X_OK) || 148validate_symref(".git/HEAD")) 149return0; 150return1; 151} 152 153const char*setup_git_directory_gently(int*nongit_ok) 154{ 155static char cwd[PATH_MAX+1]; 156int len, offset; 157 158/* 159 * If GIT_DIR is set explicitly, we're not going 160 * to do any discovery, but we still do repository 161 * validation. 162 */ 163if(getenv(GIT_DIR_ENVIRONMENT)) { 164char path[PATH_MAX]; 165int len =strlen(getenv(GIT_DIR_ENVIRONMENT)); 166if(sizeof(path) -40< len) 167die("'$%s' too big", GIT_DIR_ENVIRONMENT); 168memcpy(path,getenv(GIT_DIR_ENVIRONMENT), len); 169 170strcpy(path + len,"/refs"); 171if(access(path, X_OK)) 172goto bad_dir_environ; 173strcpy(path + len,"/HEAD"); 174if(validate_symref(path)) 175goto bad_dir_environ; 176if(getenv(DB_ENVIRONMENT)) { 177if(access(getenv(DB_ENVIRONMENT), X_OK)) 178goto bad_dir_environ; 179} 180else{ 181strcpy(path + len,"/objects"); 182if(access(path, X_OK)) 183goto bad_dir_environ; 184} 185return NULL; 186 bad_dir_environ: 187 path[len] =0; 188die("Not a git repository: '%s'", path); 189} 190 191if(!getcwd(cwd,sizeof(cwd)) || cwd[0] !='/') 192die("Unable to read current working directory"); 193 194 offset = len =strlen(cwd); 195for(;;) { 196if(is_toplevel_directory()) 197break; 198chdir(".."); 199do{ 200if(!offset) { 201if(nongit_ok) { 202if(chdir(cwd)) 203die("Cannot come back to cwd"); 204*nongit_ok =1; 205return NULL; 206} 207die("Not a git repository"); 208} 209}while(cwd[--offset] !='/'); 210} 211 212if(offset == len) 213return NULL; 214 215/* Make "offset" point to past the '/', and add a '/' at the end */ 216 offset++; 217 cwd[len++] ='/'; 218 cwd[len] =0; 219return cwd + offset; 220} 221 222intcheck_repository_format_version(const char*var,const char*value) 223{ 224if(strcmp(var,"core.repositoryformatversion") ==0) 225 repository_format_version =git_config_int(var, value); 226else if(strcmp(var,"core.sharedrepository") ==0) 227 shared_repository =git_config_bool(var, value); 228return0; 229} 230 231intcheck_repository_format(void) 232{ 233git_config(check_repository_format_version); 234if(GIT_REPO_VERSION < repository_format_version) 235die("Expected git repo version <=%d, found%d", 236 GIT_REPO_VERSION, repository_format_version); 237return0; 238} 239 240const char*setup_git_directory(void) 241{ 242const char*retval =setup_git_directory_gently(NULL); 243check_repository_format(); 244return retval; 245}