setup.con commit diff --cumulative is a sub-option of --dirstat (f88d225)
   1#include "cache.h"
   2#include "dir.h"
   3
   4static int inside_git_dir = -1;
   5static int inside_work_tree = -1;
   6
   7static int sanitary_path_copy(char *dst, const char *src)
   8{
   9        char *dst0;
  10
  11        if (has_dos_drive_prefix(src)) {
  12                *dst++ = *src++;
  13                *dst++ = *src++;
  14        }
  15        dst0 = dst;
  16
  17        if (is_dir_sep(*src)) {
  18                *dst++ = '/';
  19                while (is_dir_sep(*src))
  20                        src++;
  21        }
  22
  23        for (;;) {
  24                char c = *src;
  25
  26                /*
  27                 * A path component that begins with . could be
  28                 * special:
  29                 * (1) "." and ends   -- ignore and terminate.
  30                 * (2) "./"           -- ignore them, eat slash and continue.
  31                 * (3) ".." and ends  -- strip one and terminate.
  32                 * (4) "../"          -- strip one, eat slash and continue.
  33                 */
  34                if (c == '.') {
  35                        if (!src[1]) {
  36                                /* (1) */
  37                                src++;
  38                        } else if (is_dir_sep(src[1])) {
  39                                /* (2) */
  40                                src += 2;
  41                                while (is_dir_sep(*src))
  42                                        src++;
  43                                continue;
  44                        } else if (src[1] == '.') {
  45                                if (!src[2]) {
  46                                        /* (3) */
  47                                        src += 2;
  48                                        goto up_one;
  49                                } else if (is_dir_sep(src[2])) {
  50                                        /* (4) */
  51                                        src += 3;
  52                                        while (is_dir_sep(*src))
  53                                                src++;
  54                                        goto up_one;
  55                                }
  56                        }
  57                }
  58
  59                /* copy up to the next '/', and eat all '/' */
  60                while ((c = *src++) != '\0' && !is_dir_sep(c))
  61                        *dst++ = c;
  62                if (is_dir_sep(c)) {
  63                        *dst++ = '/';
  64                        while (is_dir_sep(c))
  65                                c = *src++;
  66                        src--;
  67                } else if (!c)
  68                        break;
  69                continue;
  70
  71        up_one:
  72                /*
  73                 * dst0..dst is prefix portion, and dst[-1] is '/';
  74                 * go up one level.
  75                 */
  76                dst -= 2; /* go past trailing '/' if any */
  77                if (dst < dst0)
  78                        return -1;
  79                while (1) {
  80                        if (dst <= dst0)
  81                                break;
  82                        c = *dst--;
  83                        if (c == '/') { /* MinGW: cannot be '\\' anymore */
  84                                dst += 2;
  85                                break;
  86                        }
  87                }
  88        }
  89        *dst = '\0';
  90        return 0;
  91}
  92
  93const char *prefix_path(const char *prefix, int len, const char *path)
  94{
  95        const char *orig = path;
  96        char *sanitized = xmalloc(len + strlen(path) + 1);
  97        if (is_absolute_path(orig))
  98                strcpy(sanitized, path);
  99        else {
 100                if (len)
 101                        memcpy(sanitized, prefix, len);
 102                strcpy(sanitized + len, path);
 103        }
 104        if (sanitary_path_copy(sanitized, sanitized))
 105                goto error_out;
 106        if (is_absolute_path(orig)) {
 107                const char *work_tree = get_git_work_tree();
 108                size_t len = strlen(work_tree);
 109                size_t total = strlen(sanitized) + 1;
 110                if (strncmp(sanitized, work_tree, len) ||
 111                    (sanitized[len] != '\0' && sanitized[len] != '/')) {
 112                error_out:
 113                        error("'%s' is outside repository", orig);
 114                        free(sanitized);
 115                        return NULL;
 116                }
 117                if (sanitized[len] == '/')
 118                        len++;
 119                memmove(sanitized, sanitized + len, total - len);
 120        }
 121        return sanitized;
 122}
 123
 124/*
 125 * Unlike prefix_path, this should be used if the named file does
 126 * not have to interact with index entry; i.e. name of a random file
 127 * on the filesystem.
 128 */
 129const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
 130{
 131        static char path[PATH_MAX];
 132#ifndef __MINGW32__
 133        if (!pfx || !*pfx || is_absolute_path(arg))
 134                return arg;
 135        memcpy(path, pfx, pfx_len);
 136        strcpy(path + pfx_len, arg);
 137#else
 138        char *p;
 139        /* don't add prefix to absolute paths, but still replace '\' by '/' */
 140        if (is_absolute_path(arg))
 141                pfx_len = 0;
 142        else
 143                memcpy(path, pfx, pfx_len);
 144        strcpy(path + pfx_len, arg);
 145        for (p = path + pfx_len; *p; p++)
 146                if (*p == '\\')
 147                        *p = '/';
 148#endif
 149        return path;
 150}
 151
 152/*
 153 * Verify a filename that we got as an argument for a pathspec
 154 * entry. Note that a filename that begins with "-" never verifies
 155 * as true, because even if such a filename were to exist, we want
 156 * it to be preceded by the "--" marker (or we want the user to
 157 * use a format like "./-filename")
 158 */
 159void verify_filename(const char *prefix, const char *arg)
 160{
 161        const char *name;
 162        struct stat st;
 163
 164        if (*arg == '-')
 165                die("bad flag '%s' used after filename", arg);
 166        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
 167        if (!lstat(name, &st))
 168                return;
 169        if (errno == ENOENT)
 170                die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
 171                    "Use '--' to separate paths from revisions", arg);
 172        die("'%s': %s", arg, strerror(errno));
 173}
 174
 175/*
 176 * Opposite of the above: the command line did not have -- marker
 177 * and we parsed the arg as a refname.  It should not be interpretable
 178 * as a filename.
 179 */
 180void verify_non_filename(const char *prefix, const char *arg)
 181{
 182        const char *name;
 183        struct stat st;
 184
 185        if (!is_inside_work_tree() || is_inside_git_dir())
 186                return;
 187        if (*arg == '-')
 188                return; /* flag */
 189        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
 190        if (!lstat(name, &st))
 191                die("ambiguous argument '%s': both revision and filename\n"
 192                    "Use '--' to separate filenames from revisions", arg);
 193        if (errno != ENOENT && errno != ENOTDIR)
 194                die("'%s': %s", arg, strerror(errno));
 195}
 196
 197const char **get_pathspec(const char *prefix, const char **pathspec)
 198{
 199        const char *entry = *pathspec;
 200        const char **src, **dst;
 201        int prefixlen;
 202
 203        if (!prefix && !entry)
 204                return NULL;
 205
 206        if (!entry) {
 207                static const char *spec[2];
 208                spec[0] = prefix;
 209                spec[1] = NULL;
 210                return spec;
 211        }
 212
 213        /* Otherwise we have to re-write the entries.. */
 214        src = pathspec;
 215        dst = pathspec;
 216        prefixlen = prefix ? strlen(prefix) : 0;
 217        while (*src) {
 218                const char *p = prefix_path(prefix, prefixlen, *src);
 219                if (p)
 220                        *(dst++) = p;
 221                else
 222                        exit(128); /* error message already given */
 223                src++;
 224        }
 225        *dst = NULL;
 226        if (!*pathspec)
 227                return NULL;
 228        return pathspec;
 229}
 230
 231/*
 232 * Test if it looks like we're at a git directory.
 233 * We want to see:
 234 *
 235 *  - either an objects/ directory _or_ the proper
 236 *    GIT_OBJECT_DIRECTORY environment variable
 237 *  - a refs/ directory
 238 *  - either a HEAD symlink or a HEAD file that is formatted as
 239 *    a proper "ref:", or a regular file HEAD that has a properly
 240 *    formatted sha1 object name.
 241 */
 242static int is_git_directory(const char *suspect)
 243{
 244        char path[PATH_MAX];
 245        size_t len = strlen(suspect);
 246
 247        strcpy(path, suspect);
 248        if (getenv(DB_ENVIRONMENT)) {
 249                if (access(getenv(DB_ENVIRONMENT), X_OK))
 250                        return 0;
 251        }
 252        else {
 253                strcpy(path + len, "/objects");
 254                if (access(path, X_OK))
 255                        return 0;
 256        }
 257
 258        strcpy(path + len, "/refs");
 259        if (access(path, X_OK))
 260                return 0;
 261
 262        strcpy(path + len, "/HEAD");
 263        if (validate_headref(path))
 264                return 0;
 265
 266        return 1;
 267}
 268
 269int is_inside_git_dir(void)
 270{
 271        if (inside_git_dir < 0)
 272                inside_git_dir = is_inside_dir(get_git_dir());
 273        return inside_git_dir;
 274}
 275
 276int is_inside_work_tree(void)
 277{
 278        if (inside_work_tree < 0)
 279                inside_work_tree = is_inside_dir(get_git_work_tree());
 280        return inside_work_tree;
 281}
 282
 283/*
 284 * set_work_tree() is only ever called if you set GIT_DIR explicitely.
 285 * The old behaviour (which we retain here) is to set the work tree root
 286 * to the cwd, unless overridden by the config, the command line, or
 287 * GIT_WORK_TREE.
 288 */
 289static const char *set_work_tree(const char *dir)
 290{
 291        char buffer[PATH_MAX + 1];
 292
 293        if (!getcwd(buffer, sizeof(buffer)))
 294                die ("Could not get the current working directory");
 295        git_work_tree_cfg = xstrdup(buffer);
 296        inside_work_tree = 1;
 297
 298        return NULL;
 299}
 300
 301void setup_work_tree(void)
 302{
 303        const char *work_tree, *git_dir;
 304        static int initialized = 0;
 305
 306        if (initialized)
 307                return;
 308        work_tree = get_git_work_tree();
 309        git_dir = get_git_dir();
 310        if (!is_absolute_path(git_dir))
 311                git_dir = make_absolute_path(git_dir);
 312        if (!work_tree || chdir(work_tree))
 313                die("This operation must be run in a work tree");
 314        set_git_dir(make_relative_path(git_dir, work_tree));
 315        initialized = 1;
 316}
 317
 318static int check_repository_format_gently(int *nongit_ok)
 319{
 320        git_config(check_repository_format_version, NULL);
 321        if (GIT_REPO_VERSION < repository_format_version) {
 322                if (!nongit_ok)
 323                        die ("Expected git repo version <= %d, found %d",
 324                             GIT_REPO_VERSION, repository_format_version);
 325                warning("Expected git repo version <= %d, found %d",
 326                        GIT_REPO_VERSION, repository_format_version);
 327                warning("Please upgrade Git");
 328                *nongit_ok = -1;
 329                return -1;
 330        }
 331        return 0;
 332}
 333
 334/*
 335 * Try to read the location of the git directory from the .git file,
 336 * return path to git directory if found.
 337 */
 338const char *read_gitfile_gently(const char *path)
 339{
 340        char *buf;
 341        struct stat st;
 342        int fd;
 343        size_t len;
 344
 345        if (stat(path, &st))
 346                return NULL;
 347        if (!S_ISREG(st.st_mode))
 348                return NULL;
 349        fd = open(path, O_RDONLY);
 350        if (fd < 0)
 351                die("Error opening %s: %s", path, strerror(errno));
 352        buf = xmalloc(st.st_size + 1);
 353        len = read_in_full(fd, buf, st.st_size);
 354        close(fd);
 355        if (len != st.st_size)
 356                die("Error reading %s", path);
 357        buf[len] = '\0';
 358        if (prefixcmp(buf, "gitdir: "))
 359                die("Invalid gitfile format: %s", path);
 360        while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
 361                len--;
 362        if (len < 9)
 363                die("No path in gitfile: %s", path);
 364        buf[len] = '\0';
 365        if (!is_git_directory(buf + 8))
 366                die("Not a git repository: %s", buf + 8);
 367        path = make_absolute_path(buf + 8);
 368        free(buf);
 369        return path;
 370}
 371
 372/*
 373 * We cannot decide in this function whether we are in the work tree or
 374 * not, since the config can only be read _after_ this function was called.
 375 */
 376const char *setup_git_directory_gently(int *nongit_ok)
 377{
 378        const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
 379        const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
 380        static char cwd[PATH_MAX+1];
 381        const char *gitdirenv;
 382        const char *gitfile_dir;
 383        int len, offset, ceil_offset;
 384
 385        /*
 386         * Let's assume that we are in a git repository.
 387         * If it turns out later that we are somewhere else, the value will be
 388         * updated accordingly.
 389         */
 390        if (nongit_ok)
 391                *nongit_ok = 0;
 392
 393        /*
 394         * If GIT_DIR is set explicitly, we're not going
 395         * to do any discovery, but we still do repository
 396         * validation.
 397         */
 398        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
 399        if (gitdirenv) {
 400                if (PATH_MAX - 40 < strlen(gitdirenv))
 401                        die("'$%s' too big", GIT_DIR_ENVIRONMENT);
 402                if (is_git_directory(gitdirenv)) {
 403                        static char buffer[1024 + 1];
 404                        const char *retval;
 405
 406                        if (!work_tree_env) {
 407                                retval = set_work_tree(gitdirenv);
 408                                /* config may override worktree */
 409                                if (check_repository_format_gently(nongit_ok))
 410                                        return NULL;
 411                                return retval;
 412                        }
 413                        if (check_repository_format_gently(nongit_ok))
 414                                return NULL;
 415                        retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
 416                                        get_git_work_tree());
 417                        if (!retval || !*retval)
 418                                return NULL;
 419                        set_git_dir(make_absolute_path(gitdirenv));
 420                        if (chdir(work_tree_env) < 0)
 421                                die ("Could not chdir to %s", work_tree_env);
 422                        strcat(buffer, "/");
 423                        return retval;
 424                }
 425                if (nongit_ok) {
 426                        *nongit_ok = 1;
 427                        return NULL;
 428                }
 429                die("Not a git repository: '%s'", gitdirenv);
 430        }
 431
 432        if (!getcwd(cwd, sizeof(cwd)-1))
 433                die("Unable to read current working directory");
 434
 435        ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
 436        if (ceil_offset < 0 && has_dos_drive_prefix(cwd))
 437                ceil_offset = 1;
 438
 439        /*
 440         * Test in the following order (relative to the cwd):
 441         * - .git (file containing "gitdir: <path>")
 442         * - .git/
 443         * - ./ (bare)
 444         * - ../.git
 445         * - ../.git/
 446         * - ../ (bare)
 447         * - ../../.git/
 448         *   etc.
 449         */
 450        offset = len = strlen(cwd);
 451        for (;;) {
 452                gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT);
 453                if (gitfile_dir) {
 454                        if (set_git_dir(gitfile_dir))
 455                                die("Repository setup failed");
 456                        break;
 457                }
 458                if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
 459                        break;
 460                if (is_git_directory(".")) {
 461                        inside_git_dir = 1;
 462                        if (!work_tree_env)
 463                                inside_work_tree = 0;
 464                        setenv(GIT_DIR_ENVIRONMENT, ".", 1);
 465                        check_repository_format_gently(nongit_ok);
 466                        return NULL;
 467                }
 468                while (--offset > ceil_offset && cwd[offset] != '/');
 469                if (offset <= ceil_offset) {
 470                        if (nongit_ok) {
 471                                if (chdir(cwd))
 472                                        die("Cannot come back to cwd");
 473                                *nongit_ok = 1;
 474                                return NULL;
 475                        }
 476                        die("Not a git repository");
 477                }
 478                chdir("..");
 479        }
 480
 481        inside_git_dir = 0;
 482        if (!work_tree_env)
 483                inside_work_tree = 1;
 484        git_work_tree_cfg = xstrndup(cwd, offset);
 485        if (check_repository_format_gently(nongit_ok))
 486                return NULL;
 487        if (offset == len)
 488                return NULL;
 489
 490        /* Make "offset" point to past the '/', and add a '/' at the end */
 491        offset++;
 492        cwd[len++] = '/';
 493        cwd[len] = 0;
 494        return cwd + offset;
 495}
 496
 497int git_config_perm(const char *var, const char *value)
 498{
 499        int i;
 500        char *endptr;
 501
 502        if (value == NULL)
 503                return PERM_GROUP;
 504
 505        if (!strcmp(value, "umask"))
 506                return PERM_UMASK;
 507        if (!strcmp(value, "group"))
 508                return PERM_GROUP;
 509        if (!strcmp(value, "all") ||
 510            !strcmp(value, "world") ||
 511            !strcmp(value, "everybody"))
 512                return PERM_EVERYBODY;
 513
 514        /* Parse octal numbers */
 515        i = strtol(value, &endptr, 8);
 516
 517        /* If not an octal number, maybe true/false? */
 518        if (*endptr != 0)
 519                return git_config_bool(var, value) ? PERM_GROUP : PERM_UMASK;
 520
 521        /*
 522         * Treat values 0, 1 and 2 as compatibility cases, otherwise it is
 523         * a chmod value.
 524         */
 525        switch (i) {
 526        case PERM_UMASK:               /* 0 */
 527                return PERM_UMASK;
 528        case OLD_PERM_GROUP:           /* 1 */
 529                return PERM_GROUP;
 530        case OLD_PERM_EVERYBODY:       /* 2 */
 531                return PERM_EVERYBODY;
 532        }
 533
 534        /* A filemode value was given: 0xxx */
 535
 536        if ((i & 0600) != 0600)
 537                die("Problem with core.sharedRepository filemode value "
 538                    "(0%.3o).\nThe owner of files must always have "
 539                    "read and write permissions.", i);
 540
 541        /*
 542         * Mask filemode value. Others can not get write permission.
 543         * x flags for directories are handled separately.
 544         */
 545        return i & 0666;
 546}
 547
 548int check_repository_format_version(const char *var, const char *value, void *cb)
 549{
 550        if (strcmp(var, "core.repositoryformatversion") == 0)
 551                repository_format_version = git_config_int(var, value);
 552        else if (strcmp(var, "core.sharedrepository") == 0)
 553                shared_repository = git_config_perm(var, value);
 554        else if (strcmp(var, "core.bare") == 0) {
 555                is_bare_repository_cfg = git_config_bool(var, value);
 556                if (is_bare_repository_cfg == 1)
 557                        inside_work_tree = -1;
 558        } else if (strcmp(var, "core.worktree") == 0) {
 559                if (!value)
 560                        return config_error_nonbool(var);
 561                free(git_work_tree_cfg);
 562                git_work_tree_cfg = xstrdup(value);
 563                inside_work_tree = -1;
 564        }
 565        return 0;
 566}
 567
 568int check_repository_format(void)
 569{
 570        return check_repository_format_gently(NULL);
 571}
 572
 573const char *setup_git_directory(void)
 574{
 575        const char *retval = setup_git_directory_gently(NULL);
 576
 577        /* If the work tree is not the default one, recompute prefix */
 578        if (inside_work_tree < 0) {
 579                static char buffer[PATH_MAX + 1];
 580                char *rel;
 581                if (retval && chdir(retval))
 582                        die ("Could not jump back into original cwd");
 583                rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
 584                if (rel && *rel && chdir(get_git_work_tree()))
 585                        die ("Could not jump to working directory");
 586                return rel && *rel ? strcat(rel, "/") : NULL;
 587        }
 588
 589        return retval;
 590}