setup.con commit Merge branch 'maint' (237ce83)
   1#include "cache.h"
   2
   3const 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/*
  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{
  57        static char path[PATH_MAX];
  58        if (!pfx || !*pfx || arg[0] == '/')
  59                return arg;
  60        memcpy(path, pfx, pfx_len);
  61        strcpy(path + pfx_len, arg);
  62        return 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 */
  72void verify_filename(const char *prefix, const char *arg)
  73{
  74        const char *name;
  75        struct stat st;
  76
  77        if (*arg == '-')
  78                die("bad flag '%s' used after filename", arg);
  79        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
  80        if (!lstat(name, &st))
  81                return;
  82        if (errno == ENOENT)
  83                die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
  84                    "Use '--' to separate paths from revisions", arg);
  85        die("'%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 */
  93void verify_non_filename(const char *prefix, const char *arg)
  94{
  95        const char *name;
  96        struct stat st;
  97
  98        if (!is_inside_work_tree() || is_inside_git_dir())
  99                return;
 100        if (*arg == '-')
 101                return; /* flag */
 102        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
 103        if (!lstat(name, &st))
 104                die("ambiguous argument '%s': both revision and filename\n"
 105                    "Use '--' to separate filenames from revisions", arg);
 106        if (errno != ENOENT)
 107                die("'%s': %s", arg, strerror(errno));
 108}
 109
 110const char **get_pathspec(const char *prefix, const char **pathspec)
 111{
 112        const char *entry = *pathspec;
 113        const char **p;
 114        int prefixlen;
 115
 116        if (!prefix && !entry)
 117                return NULL;
 118
 119        if (!entry) {
 120                static const char *spec[2];
 121                spec[0] = prefix;
 122                spec[1] = NULL;
 123                return spec;
 124        }
 125
 126        /* Otherwise we have to re-write the entries.. */
 127        p = pathspec;
 128        prefixlen = prefix ? strlen(prefix) : 0;
 129        do {
 130                *p = prefix_path(prefix, prefixlen, entry);
 131        } while ((entry = *++p) != NULL);
 132        return (const char **) pathspec;
 133}
 134
 135/*
 136 * Test if it looks like we're at a git directory.
 137 * We want to see:
 138 *
 139 *  - either a objects/ directory _or_ the proper
 140 *    GIT_OBJECT_DIRECTORY environment variable
 141 *  - a refs/ directory
 142 *  - either a HEAD symlink or a HEAD file that is formatted as
 143 *    a proper "ref:", or a regular file HEAD that has a properly
 144 *    formatted sha1 object name.
 145 */
 146static int is_git_directory(const char *suspect)
 147{
 148        char path[PATH_MAX];
 149        size_t len = strlen(suspect);
 150
 151        strcpy(path, suspect);
 152        if (getenv(DB_ENVIRONMENT)) {
 153                if (access(getenv(DB_ENVIRONMENT), X_OK))
 154                        return 0;
 155        }
 156        else {
 157                strcpy(path + len, "/objects");
 158                if (access(path, X_OK))
 159                        return 0;
 160        }
 161
 162        strcpy(path + len, "/refs");
 163        if (access(path, X_OK))
 164                return 0;
 165
 166        strcpy(path + len, "/HEAD");
 167        if (validate_headref(path))
 168                return 0;
 169
 170        return 1;
 171}
 172
 173static int inside_git_dir = -1;
 174
 175int is_inside_git_dir(void)
 176{
 177        if (inside_git_dir >= 0)
 178                return inside_git_dir;
 179        die("BUG: is_inside_git_dir called before setup_git_directory");
 180}
 181
 182static int inside_work_tree = -1;
 183
 184int is_inside_work_tree(void)
 185{
 186        if (inside_git_dir >= 0)
 187                return inside_work_tree;
 188        die("BUG: is_inside_work_tree called before setup_git_directory");
 189}
 190
 191static char *gitworktree_config;
 192
 193static int git_setup_config(const char *var, const char *value)
 194{
 195        if (!strcmp(var, "core.worktree")) {
 196                if (gitworktree_config)
 197                        strlcpy(gitworktree_config, value, PATH_MAX);
 198                return 0;
 199        }
 200        return git_default_config(var, value);
 201}
 202
 203const char *setup_git_directory_gently(int *nongit_ok)
 204{
 205        static char cwd[PATH_MAX+1];
 206        char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
 207        const char *gitdirenv, *gitworktree;
 208        int wt_rel_gitdir = 0;
 209
 210        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 211        if (!gitdirenv) {
 212                int len, offset;
 213
 214                if (!getcwd(cwd, sizeof(cwd)-1))
 215                        die("Unable to read current working directory");
 216
 217                offset = len = strlen(cwd);
 218                for (;;) {
 219                        if (is_git_directory(".git"))
 220                                break;
 221                        if (offset == 0) {
 222                                offset = -1;
 223                                break;
 224                        }
 225                        chdir("..");
 226                        while (cwd[--offset] != '/')
 227                                ; /* do nothing */
 228                }
 229
 230                if (offset >= 0) {
 231                        inside_work_tree = 1;
 232                        git_config(git_default_config);
 233                        if (offset == len) {
 234                                inside_git_dir = 0;
 235                                return NULL;
 236                        }
 237
 238                        cwd[len++] = '/';
 239                        cwd[len] = '\0';
 240                        inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
 241                        return cwd + offset + 1;
 242                }
 243
 244                if (chdir(cwd))
 245                        die("Cannot come back to cwd");
 246                if (!is_git_directory(".")) {
 247                        if (nongit_ok) {
 248                                *nongit_ok = 1;
 249                                return NULL;
 250                        }
 251                        die("Not a git repository");
 252                }
 253                setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
 254                gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 255                if (!gitdirenv)
 256                        die("getenv after setenv failed");
 257        }
 258
 259        if (PATH_MAX - 40 < strlen(gitdirenv)) {
 260                if (nongit_ok) {
 261                        *nongit_ok = 1;
 262                        return NULL;
 263                }
 264                die("$%s too big", GIT_DIR_ENVIRONMENT);
 265        }
 266        if (!is_git_directory(gitdirenv)) {
 267                if (nongit_ok) {
 268                        *nongit_ok = 1;
 269                        return NULL;
 270                }
 271                die("Not a git repository: '%s'", gitdirenv);
 272        }
 273
 274        if (!getcwd(cwd, sizeof(cwd)-1))
 275                die("Unable to read current working directory");
 276        if (chdir(gitdirenv)) {
 277                if (nongit_ok) {
 278                        *nongit_ok = 1;
 279                        return NULL;
 280                }
 281                die("Cannot change directory to $%s '%s'",
 282                        GIT_DIR_ENVIRONMENT, gitdirenv);
 283        }
 284        if (!getcwd(gitdir, sizeof(gitdir)-1))
 285                die("Unable to read current working directory");
 286        if (chdir(cwd))
 287                die("Cannot come back to cwd");
 288
 289        /*
 290         * In case there is a work tree we may change the directory,
 291         * therefore make GIT_DIR an absolute path.
 292         */
 293        if (gitdirenv[0] != '/') {
 294                setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
 295                gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 296                if (!gitdirenv)
 297                        die("getenv after setenv failed");
 298                if (PATH_MAX - 40 < strlen(gitdirenv)) {
 299                        if (nongit_ok) {
 300                                *nongit_ok = 1;
 301                                return NULL;
 302                        }
 303                        die("$%s too big after expansion to absolute path",
 304                                GIT_DIR_ENVIRONMENT);
 305                }
 306        }
 307
 308        strcat(cwd, "/");
 309        strcat(gitdir, "/");
 310        inside_git_dir = !prefixcmp(cwd, gitdir);
 311
 312        gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
 313        if (!gitworktree) {
 314                gitworktree_config = worktree;
 315                worktree[0] = '\0';
 316        }
 317        git_config(git_setup_config);
 318        if (!gitworktree) {
 319                gitworktree_config = NULL;
 320                if (worktree[0])
 321                        gitworktree = worktree;
 322                if (gitworktree && gitworktree[0] != '/')
 323                        wt_rel_gitdir = 1;
 324        }
 325
 326        if (wt_rel_gitdir && chdir(gitdirenv))
 327                die("Cannot change directory to $%s '%s'",
 328                        GIT_DIR_ENVIRONMENT, gitdirenv);
 329        if (gitworktree && chdir(gitworktree)) {
 330                if (nongit_ok) {
 331                        if (wt_rel_gitdir && chdir(cwd))
 332                                die("Cannot come back to cwd");
 333                        *nongit_ok = 1;
 334                        return NULL;
 335                }
 336                if (wt_rel_gitdir)
 337                        die("Cannot change directory to working tree '%s'"
 338                                " from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
 339                else
 340                        die("Cannot change directory to working tree '%s'",
 341                                gitworktree);
 342        }
 343        if (!getcwd(worktree, sizeof(worktree)-1))
 344                die("Unable to read current working directory");
 345        strcat(worktree, "/");
 346        inside_work_tree = !prefixcmp(cwd, worktree);
 347
 348        if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
 349            strcmp(worktree, gitdir)) {
 350                inside_git_dir = 0;
 351        }
 352
 353        if (!inside_work_tree) {
 354                if (chdir(cwd))
 355                        die("Cannot come back to cwd");
 356                return NULL;
 357        }
 358
 359        if (!strcmp(cwd, worktree))
 360                return NULL;
 361        return cwd+strlen(worktree);
 362}
 363
 364int git_config_perm(const char *var, const char *value)
 365{
 366        if (value) {
 367                int i;
 368                if (!strcmp(value, "umask"))
 369                        return PERM_UMASK;
 370                if (!strcmp(value, "group"))
 371                        return PERM_GROUP;
 372                if (!strcmp(value, "all") ||
 373                    !strcmp(value, "world") ||
 374                    !strcmp(value, "everybody"))
 375                        return PERM_EVERYBODY;
 376                i = atoi(value);
 377                if (i > 1)
 378                        return i;
 379        }
 380        return git_config_bool(var, value);
 381}
 382
 383int check_repository_format_version(const char *var, const char *value)
 384{
 385       if (strcmp(var, "core.repositoryformatversion") == 0)
 386               repository_format_version = git_config_int(var, value);
 387        else if (strcmp(var, "core.sharedrepository") == 0)
 388                shared_repository = git_config_perm(var, value);
 389       return 0;
 390}
 391
 392int check_repository_format(void)
 393{
 394        git_config(check_repository_format_version);
 395        if (GIT_REPO_VERSION < repository_format_version)
 396                die ("Expected git repo version <= %d, found %d",
 397                     GIT_REPO_VERSION, repository_format_version);
 398        return 0;
 399}
 400
 401const char *setup_git_directory(void)
 402{
 403        const char *retval = setup_git_directory_gently(NULL);
 404        check_repository_format();
 405        return retval;
 406}