X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=path.c;h=7b385e5eb282276f5d2e0becde48d5e09434d6b1;hb=a78305ffbca58e49a7cdad901df0ae779bbed8fb;hp=969b494d72dff1439150e7bb945339be07c4d475;hpb=8a118ec5a23c80fc98af03d0fdd6220b7dc9c5c3;p=platform%2Fupstream%2Fgit.git diff --git a/path.c b/path.c index 969b494..7b385e5 100644 --- a/path.c +++ b/path.c @@ -2,9 +2,16 @@ * Utilities for paths and pathnames */ #include "cache.h" +#include "repository.h" #include "strbuf.h" #include "string-list.h" #include "dir.h" +#include "worktree.h" +#include "submodule-config.h" +#include "path.h" +#include "packfile.h" +#include "object-store.h" +#include "lockfile.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -23,16 +30,16 @@ static struct strbuf *get_pathname(void) STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; static int index; - struct strbuf *sb = &pathname_array[3 & ++index]; + struct strbuf *sb = &pathname_array[index]; + index = (index + 1) % ARRAY_SIZE(pathname_array); strbuf_reset(sb); return sb; } -static char *cleanup_path(char *path) +static const char *cleanup_path(const char *path) { /* Clean it up */ - if (!memcmp(path, "./", 2)) { - path += 2; + if (skip_prefix(path, "./", &path)) { while (*path == '/') path++; } @@ -41,7 +48,7 @@ static char *cleanup_path(char *path) static void strbuf_cleanup_path(struct strbuf *sb) { - char *path = cleanup_path(sb->buf); + const char *path = cleanup_path(sb->buf); if (path > sb->buf) strbuf_remove(sb, 0, path - sb->buf); } @@ -58,7 +65,7 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...) strlcpy(buf, bad_path, n); return buf; } - return cleanup_path(buf); + return (char *)cleanup_path(buf); } static int dir_prefix(const char *buf, const char *dir) @@ -95,31 +102,36 @@ struct common_dir { /* Not considered garbage for report_linked_checkout_garbage */ unsigned ignore_garbage:1; unsigned is_dir:1; - /* Not common even though its parent is */ - unsigned exclude:1; - const char *dirname; + /* Belongs to the common dir, though it may contain paths that don't */ + unsigned is_common:1; + const char *path; }; static struct common_dir common_list[] = { - { 0, 1, 0, "branches" }, - { 0, 1, 0, "hooks" }, - { 0, 1, 0, "info" }, - { 0, 0, 1, "info/sparse-checkout" }, - { 1, 1, 0, "logs" }, - { 1, 1, 1, "logs/HEAD" }, - { 0, 1, 1, "logs/refs/bisect" }, - { 0, 1, 0, "lost-found" }, - { 0, 1, 0, "objects" }, - { 0, 1, 0, "refs" }, - { 0, 1, 1, "refs/bisect" }, - { 0, 1, 0, "remotes" }, - { 0, 1, 0, "worktrees" }, - { 0, 1, 0, "rr-cache" }, - { 0, 1, 0, "svn" }, - { 0, 0, 0, "config" }, - { 1, 0, 0, "gc.pid" }, - { 0, 0, 0, "packed-refs" }, - { 0, 0, 0, "shallow" }, + { 0, 1, 1, "branches" }, + { 0, 1, 1, "common" }, + { 0, 1, 1, "hooks" }, + { 0, 1, 1, "info" }, + { 0, 0, 0, "info/sparse-checkout" }, + { 1, 1, 1, "logs" }, + { 1, 0, 0, "logs/HEAD" }, + { 0, 1, 0, "logs/refs/bisect" }, + { 0, 1, 0, "logs/refs/rewritten" }, + { 0, 1, 0, "logs/refs/worktree" }, + { 0, 1, 1, "lost-found" }, + { 0, 1, 1, "objects" }, + { 0, 1, 1, "refs" }, + { 0, 1, 0, "refs/bisect" }, + { 0, 1, 0, "refs/rewritten" }, + { 0, 1, 0, "refs/worktree" }, + { 0, 1, 1, "remotes" }, + { 0, 1, 1, "worktrees" }, + { 0, 1, 1, "rr-cache" }, + { 0, 1, 1, "svn" }, + { 0, 0, 1, "config" }, + { 1, 0, 1, "gc.pid" }, + { 0, 0, 1, "packed-refs" }, + { 0, 0, 1, "shallow" }, { 0, 0, 0, NULL } }; @@ -134,7 +146,7 @@ static struct common_dir common_list[] = { * definite * definition * - * The trie would look look like: + * The trie would look like: * root: len = 0, children a and d non-NULL, value = NULL. * a: len = 2, contents = bc, value = (data for "abc") * d: len = 2, contents = ef, children i non-NULL, value = (data for "def") @@ -187,7 +199,7 @@ static void *add_to_trie(struct trie *root, const char *key, void *value) * Split this node: child will contain this node's * existing children. */ - child = malloc(sizeof(*child)); + child = xmalloc(sizeof(*child)); memcpy(child->children, root->children, sizeof(root->children)); child->len = root->len - i - 1; @@ -225,30 +237,41 @@ static void *add_to_trie(struct trie *root, const char *key, void *value) return old; } -typedef int (*match_fn)(const char *unmatched, void *data, void *baton); +typedef int (*match_fn)(const char *unmatched, void *value, void *baton); /* * Search a trie for some key. Find the longest /-or-\0-terminated - * prefix of the key for which the trie contains a value. Call fn - * with the unmatched portion of the key and the found value, and - * return its return value. If there is no such prefix, return -1. + * prefix of the key for which the trie contains a value. If there is + * no such prefix, return -1. Otherwise call fn with the unmatched + * portion of the key and the found value. If fn returns 0 or + * positive, then return its return value. If fn returns negative, + * then call fn with the next-longest /-terminated prefix of the key + * (i.e. a parent directory) for which the trie contains a value, and + * handle its return value the same way. If there is no shorter + * /-terminated prefix with a value left, then return the negative + * return value of the most recent fn invocation. * * The key is partially normalized: consecutive slashes are skipped. * - * For example, consider the trie containing only [refs, - * refs/worktree] (both with values). - * - * | key | unmatched | val from node | return value | - * |-----------------|------------|---------------|--------------| - * | a | not called | n/a | -1 | - * | refs | \0 | refs | as per fn | - * | refs/ | / | refs | as per fn | - * | refs/w | /w | refs | as per fn | - * | refs/worktree | \0 | refs/worktree | as per fn | - * | refs/worktree/ | / | refs/worktree | as per fn | - * | refs/worktree/a | /a | refs/worktree | as per fn | - * |-----------------|------------|---------------|--------------| + * For example, consider the trie containing only [logs, + * logs/refs/bisect], both with values, but not logs/refs. * + * | key | unmatched | prefix to node | return value | + * |--------------------|----------------|------------------|--------------| + * | a | not called | n/a | -1 | + * | logstore | not called | n/a | -1 | + * | logs | \0 | logs | as per fn | + * | logs/ | / | logs | as per fn | + * | logs/refs | /refs | logs | as per fn | + * | logs/refs/ | /refs/ | logs | as per fn | + * | logs/refs/b | /refs/b | logs | as per fn | + * | logs/refs/bisected | /refs/bisected | logs | as per fn | + * | logs/refs/bisect | \0 | logs/refs/bisect | as per fn | + * | logs/refs/bisect/ | / | logs/refs/bisect | as per fn | + * | logs/refs/bisect/a | /a | logs/refs/bisect | as per fn | + * | (If fn in the previous line returns -1, then fn is called once more:) | + * | logs/refs/bisect/a | /refs/bisect/a | logs | as per fn | + * |--------------------|----------------|------------------|--------------| */ static int trie_find(struct trie *root, const char *key, match_fn fn, void *baton) @@ -277,9 +300,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn, /* Matched the entire compressed section */ key += i; - if (!*key) + if (!*key) { /* End of key */ - return fn(key, root->value, baton); + if (root->value) + return fn(key, root->value, baton); + else + return -1; + } /* Partial path normalization: skip consecutive slashes */ while (key[0] == '/' && key[1] == '/') @@ -309,8 +336,8 @@ static void init_common_trie(void) if (common_trie_done_setup) return; - for (p = common_list; p->dirname; p++) - add_to_trie(&common_trie, p->dirname, p); + for (p = common_list; p->path; p++) + add_to_trie(&common_trie, p->path, p); common_trie_done_setup = 1; } @@ -323,14 +350,11 @@ static int check_common(const char *unmatched, void *value, void *baton) { struct common_dir *dir = value; - if (!dir) - return 0; - if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/')) - return !dir->exclude; + return dir->is_common; if (!dir->is_dir && unmatched[0] == 0) - return !dir->exclude; + return dir->is_common; return 0; } @@ -339,11 +363,14 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len, const char *common_dir) { char *base = buf->buf + git_dir_len; + int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX); + init_common_trie(); - if (!common_dir) - common_dir = get_git_common_dir(); if (trie_find(&common_trie, base, check_common, NULL) > 0) replace_dir(buf, git_dir_len, common_dir); + + if (has_lock_suffix) + strbuf_addstr(buf, LOCK_SUFFIX); } void report_linked_checkout_garbage(void) @@ -352,12 +379,12 @@ void report_linked_checkout_garbage(void) const struct common_dir *p; int len; - if (!git_common_dir_env) + if (!the_repository->different_commondir) return; strbuf_addf(&sb, "%s/", get_git_dir()); len = sb.len; - for (p = common_list; p->dirname; p++) { - const char *path = p->dirname; + for (p = common_list; p->path; p++) { + const char *path = p->path; if (p->ignore_garbage) continue; strbuf_setlen(&sb, len); @@ -368,39 +395,78 @@ void report_linked_checkout_garbage(void) strbuf_release(&sb); } -static void adjust_git_path(struct strbuf *buf, int git_dir_len) +static void adjust_git_path(const struct repository *repo, + struct strbuf *buf, int git_dir_len) { const char *base = buf->buf + git_dir_len; - if (git_graft_env && is_dir_file(base, "info", "grafts")) + if (is_dir_file(base, "info", "grafts")) strbuf_splice(buf, 0, buf->len, - get_graft_file(), strlen(get_graft_file())); - else if (git_index_env && !strcmp(base, "index")) + repo->graft_file, strlen(repo->graft_file)); + else if (!strcmp(base, "index")) strbuf_splice(buf, 0, buf->len, - get_index_file(), strlen(get_index_file())); - else if (git_db_env && dir_prefix(base, "objects")) - replace_dir(buf, git_dir_len + 7, get_object_directory()); - else if (git_common_dir_env) - update_common_dir(buf, git_dir_len, NULL); + repo->index_file, strlen(repo->index_file)); + else if (dir_prefix(base, "objects")) + replace_dir(buf, git_dir_len + 7, repo->objects->odb->path); + else if (git_hooks_path && dir_prefix(base, "hooks")) + replace_dir(buf, git_dir_len + 5, git_hooks_path); + else if (repo->different_commondir) + update_common_dir(buf, git_dir_len, repo->commondir); +} + +static void strbuf_worktree_gitdir(struct strbuf *buf, + const struct repository *repo, + const struct worktree *wt) +{ + if (!wt) + strbuf_addstr(buf, repo->gitdir); + else if (!wt->id) + strbuf_addstr(buf, repo->commondir); + else + strbuf_git_common_path(buf, repo, "worktrees/%s", wt->id); } -static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) +static void do_git_path(const struct repository *repo, + const struct worktree *wt, struct strbuf *buf, + const char *fmt, va_list args) { int gitdir_len; - strbuf_addstr(buf, get_git_dir()); + strbuf_worktree_gitdir(buf, repo, wt); if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) strbuf_addch(buf, '/'); gitdir_len = buf->len; strbuf_vaddf(buf, fmt, args); - adjust_git_path(buf, gitdir_len); + if (!wt) + adjust_git_path(repo, buf, gitdir_len); strbuf_cleanup_path(buf); } +char *repo_git_path(const struct repository *repo, + const char *fmt, ...) +{ + struct strbuf path = STRBUF_INIT; + va_list args; + va_start(args, fmt); + do_git_path(repo, NULL, &path, fmt, args); + va_end(args); + return strbuf_detach(&path, NULL); +} + +void strbuf_repo_git_path(struct strbuf *sb, + const struct repository *repo, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + do_git_path(repo, NULL, sb, fmt, args); + va_end(args); +} + char *git_path_buf(struct strbuf *buf, const char *fmt, ...) { va_list args; strbuf_reset(buf); va_start(args, fmt); - do_git_path(buf, fmt, args); + do_git_path(the_repository, NULL, buf, fmt, args); va_end(args); return buf->buf; } @@ -409,7 +475,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) { va_list args; va_start(args, fmt); - do_git_path(sb, fmt, args); + do_git_path(the_repository, NULL, sb, fmt, args); va_end(args); } @@ -418,7 +484,7 @@ const char *git_path(const char *fmt, ...) struct strbuf *pathname = get_pathname(); va_list args; va_start(args, fmt); - do_git_path(pathname, fmt, args); + do_git_path(the_repository, NULL, pathname, fmt, args); va_end(args); return pathname->buf; } @@ -428,7 +494,7 @@ char *git_pathdup(const char *fmt, ...) struct strbuf path = STRBUF_INIT; va_list args; va_start(args, fmt); - do_git_path(&path, fmt, args); + do_git_path(the_repository, NULL, &path, fmt, args); va_end(args); return strbuf_detach(&path, NULL); } @@ -454,25 +520,71 @@ const char *mkpath(const char *fmt, ...) return cleanup_path(pathname->buf); } -static void do_submodule_path(struct strbuf *buf, const char *path, - const char *fmt, va_list args) +const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) +{ + struct strbuf *pathname = get_pathname(); + va_list args; + va_start(args, fmt); + do_git_path(the_repository, wt, pathname, fmt, args); + va_end(args); + return pathname->buf; +} + +static void do_worktree_path(const struct repository *repo, + struct strbuf *buf, + const char *fmt, va_list args) +{ + strbuf_addstr(buf, repo->worktree); + if(buf->len && !is_dir_sep(buf->buf[buf->len - 1])) + strbuf_addch(buf, '/'); + + strbuf_vaddf(buf, fmt, args); + strbuf_cleanup_path(buf); +} + +char *repo_worktree_path(const struct repository *repo, const char *fmt, ...) +{ + struct strbuf path = STRBUF_INIT; + va_list args; + + if (!repo->worktree) + return NULL; + + va_start(args, fmt); + do_worktree_path(repo, &path, fmt, args); + va_end(args); + + return strbuf_detach(&path, NULL); +} + +void strbuf_repo_worktree_path(struct strbuf *sb, + const struct repository *repo, + const char *fmt, ...) +{ + va_list args; + + if (!repo->worktree) + return; + + va_start(args, fmt); + do_worktree_path(repo, sb, fmt, args); + va_end(args); +} + +/* Returns 0 on success, negative on failure. */ +static int do_submodule_path(struct strbuf *buf, const char *path, + const char *fmt, va_list args) { - const char *git_dir; struct strbuf git_submodule_common_dir = STRBUF_INIT; struct strbuf git_submodule_dir = STRBUF_INIT; + int ret; - strbuf_addstr(buf, path); - strbuf_complete(buf, '/'); - strbuf_addstr(buf, ".git"); - - git_dir = read_gitfile(buf->buf); - if (git_dir) { - strbuf_reset(buf); - strbuf_addstr(buf, git_dir); - } - strbuf_addch(buf, '/'); - strbuf_addstr(&git_submodule_dir, buf->buf); + ret = submodule_to_gitdir(&git_submodule_dir, path); + if (ret) + goto cleanup; + strbuf_complete(&git_submodule_dir, '/'); + strbuf_addbuf(buf, &git_submodule_dir); strbuf_vaddf(buf, fmt, args); if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf)) @@ -480,34 +592,77 @@ static void do_submodule_path(struct strbuf *buf, const char *path, strbuf_cleanup_path(buf); +cleanup: strbuf_release(&git_submodule_dir); strbuf_release(&git_submodule_common_dir); + return ret; } char *git_pathdup_submodule(const char *path, const char *fmt, ...) { + int err; va_list args; struct strbuf buf = STRBUF_INIT; va_start(args, fmt); - do_submodule_path(&buf, path, fmt, args); + err = do_submodule_path(&buf, path, fmt, args); va_end(args); + if (err) { + strbuf_release(&buf); + return NULL; + } return strbuf_detach(&buf, NULL); } -void strbuf_git_path_submodule(struct strbuf *buf, const char *path, - const char *fmt, ...) +int strbuf_git_path_submodule(struct strbuf *buf, const char *path, + const char *fmt, ...) +{ + int err; + va_list args; + va_start(args, fmt); + err = do_submodule_path(buf, path, fmt, args); + va_end(args); + + return err; +} + +static void do_git_common_path(const struct repository *repo, + struct strbuf *buf, + const char *fmt, + va_list args) +{ + strbuf_addstr(buf, repo->commondir); + if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) + strbuf_addch(buf, '/'); + strbuf_vaddf(buf, fmt, args); + strbuf_cleanup_path(buf); +} + +const char *git_common_path(const char *fmt, ...) +{ + struct strbuf *pathname = get_pathname(); + va_list args; + va_start(args, fmt); + do_git_common_path(the_repository, pathname, fmt, args); + va_end(args); + return pathname->buf; +} + +void strbuf_git_common_path(struct strbuf *sb, + const struct repository *repo, + const char *fmt, ...) { va_list args; va_start(args, fmt); - do_submodule_path(buf, path, fmt, args); + do_git_common_path(repo, sb, fmt, args); va_end(args); } int validate_headref(const char *path) { struct stat st; - char *buf, buffer[256]; - unsigned char sha1[20]; + char buffer[256]; + const char *refname; + struct object_id oid; int fd; ssize_t len; @@ -531,24 +686,24 @@ int validate_headref(const char *path) len = read_in_full(fd, buffer, sizeof(buffer)-1); close(fd); + if (len < 0) + return -1; + buffer[len] = '\0'; + /* * Is it a symbolic ref? */ - if (len < 4) - return -1; - if (!memcmp("ref:", buffer, 4)) { - buf = buffer + 4; - len -= 4; - while (len && isspace(*buf)) - buf++, len--; - if (len >= 5 && !memcmp("refs/", buf, 5)) + if (skip_prefix(buffer, "ref:", &refname)) { + while (isspace(*refname)) + refname++; + if (starts_with(refname, "refs/")) return 0; } /* * Is this a detached HEAD? */ - if (!get_sha1_hex(buffer, sha1)) + if (!get_oid_hex(buffer, &oid)) return 0; return -1; @@ -567,8 +722,10 @@ static struct passwd *getpw_str(const char *username, size_t len) * Return a string with ~ and ~user expanded via getpw*. If buf != NULL, * then it is a newly allocated string. Returns NULL on getpw failure or * if path is NULL. + * + * If real_home is true, strbuf_realpath($HOME) is used in the expansion. */ -char *expand_user_path(const char *path) +char *expand_user_path(const char *path, int real_home) { struct strbuf user_path = STRBUF_INIT; const char *to_copy = path; @@ -583,7 +740,10 @@ char *expand_user_path(const char *path) const char *home = getenv("HOME"); if (!home) goto return_null; - strbuf_addstr(&user_path, home); + if (real_home) + strbuf_add_real_path(&user_path, home); + else + strbuf_addstr(&user_path, home); #ifdef GIT_WINDOWS_NATIVE convert_slashes(user_path.buf); #endif @@ -652,7 +812,7 @@ const char *enter_repo(const char *path, int strict) strbuf_add(&validated_path, path, len); if (used_path.buf[0] == '~') { - char *newpath = expand_user_path(used_path.buf); + char *newpath = expand_user_path(used_path.buf, 0); if (!newpath) return NULL; strbuf_attach(&used_path, newpath, strlen(newpath), @@ -690,8 +850,8 @@ const char *enter_repo(const char *path, int strict) } if (is_git_directory(".")) { - set_git_dir("."); - check_repository_format(); + set_git_dir(".", 0); + check_repository_format(NULL); return path; } @@ -702,17 +862,17 @@ static int calc_shared_perm(int mode) { int tweak; - if (shared_repository < 0) - tweak = -shared_repository; + if (get_shared_repository() < 0) + tweak = -get_shared_repository(); else - tweak = shared_repository; + tweak = get_shared_repository(); if (!(mode & S_IWUSR)) tweak &= ~0222; if (mode & S_IXUSR) /* Copy read bits to execute bits */ tweak |= (tweak & 0444) >> 2; - if (shared_repository < 0) + if (get_shared_repository() < 0) mode = (mode & ~0777) | tweak; else mode |= tweak; @@ -725,7 +885,7 @@ int adjust_shared_perm(const char *path) { int old_mode, new_mode; - if (!shared_repository) + if (!get_shared_repository()) return 0; if (get_st_mode_bits(path, &old_mode) < 0) return -1; @@ -917,10 +1077,12 @@ const char *remove_leading_path(const char *in, const char *prefix) /* * It is okay if dst == src, but they should not overlap otherwise. + * The "dst" buffer must be at least as long as "src"; normalizing may shrink + * the size of the path, but will never grow it. * * Performs the following normalizations on src, storing the result in dst: * - Ensures that components are separated by '/' (Windows only) - * - Squashes sequences of '/'. + * - Squashes sequences of '/' except "//server/share" on Windows * - Removes "." components. * - Removes ".." components, and the components the precede them. * Returns failure (non-zero) if a ".." component appears as first path @@ -943,17 +1105,22 @@ const char *remove_leading_path(const char *in, const char *prefix) int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; - int i; + const char *end; - for (i = has_dos_drive_prefix(src); i > 0; i--) - *dst++ = *src++; + /* + * Copy initial part of absolute path: "/", "C:/", "//server/share/". + */ + end = src + offset_1st_component(src); + while (src < end) { + char c = *src++; + if (is_dir_sep(c)) + c = '/'; + *dst++ = c; + } dst0 = dst; - if (is_dir_sep(*src)) { - *dst++ = '/'; - while (is_dir_sep(*src)) - src++; - } + while (is_dir_sep(*src)) + src++; for (;;) { char c = *src; @@ -1074,31 +1241,52 @@ static inline int chomp_trailing_dir_sep(const char *path, int len) } /* - * If path ends with suffix (complete path components), returns the - * part before suffix (sans trailing directory separators). - * Otherwise returns NULL. + * If path ends with suffix (complete path components), returns the offset of + * the last character in the path before the suffix (sans trailing directory + * separators), and -1 otherwise. */ -char *strip_path_suffix(const char *path, const char *suffix) +static ssize_t stripped_path_suffix_offset(const char *path, const char *suffix) { int path_len = strlen(path), suffix_len = strlen(suffix); while (suffix_len) { if (!path_len) - return NULL; + return -1; if (is_dir_sep(path[path_len - 1])) { if (!is_dir_sep(suffix[suffix_len - 1])) - return NULL; + return -1; path_len = chomp_trailing_dir_sep(path, path_len); suffix_len = chomp_trailing_dir_sep(suffix, suffix_len); } else if (path[--path_len] != suffix[--suffix_len]) - return NULL; + return -1; } if (path_len && !is_dir_sep(path[path_len - 1])) - return NULL; - return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); + return -1; + return chomp_trailing_dir_sep(path, path_len); +} + +/* + * Returns true if the path ends with components, considering only complete path + * components, and false otherwise. + */ +int ends_with_path_components(const char *path, const char *components) +{ + return stripped_path_suffix_offset(path, components) != -1; +} + +/* + * If path ends with suffix (complete path components), returns the + * part before suffix (sans trailing directory separators). + * Otherwise returns NULL. + */ +char *strip_path_suffix(const char *path, const char *suffix) +{ + ssize_t offset = stripped_path_suffix_offset(path, suffix); + + return offset == -1 ? NULL : xstrndup(path, offset); } int daemon_avoid_alias(const char *p) @@ -1148,37 +1336,166 @@ int daemon_avoid_alias(const char *p) } } -static int only_spaces_and_periods(const char *path, size_t len, size_t skip) +/* + * On NTFS, we need to be careful to disallow certain synonyms of the `.git/` + * directory: + * + * - For historical reasons, file names that end in spaces or periods are + * automatically trimmed. Therefore, `.git . . ./` is a valid way to refer + * to `.git/`. + * + * - For other historical reasons, file names that do not conform to the 8.3 + * format (up to eight characters for the basename, three for the file + * extension, certain characters not allowed such as `+`, etc) are associated + * with a so-called "short name", at least on the `C:` drive by default. + * Which means that `git~1/` is a valid way to refer to `.git/`. + * + * Note: Technically, `.git/` could receive the short name `git~2` if the + * short name `git~1` were already used. In Git, however, we guarantee that + * `.git` is the first item in a directory, therefore it will be associated + * with the short name `git~1` (unless short names are disabled). + * + * - For yet other historical reasons, NTFS supports so-called "Alternate Data + * Streams", i.e. metadata associated with a given file, referred to via + * `::`. There exists a default stream + * type for directories, allowing `.git/` to be accessed via + * `.git::$INDEX_ALLOCATION/`. + * + * When this function returns 1, it indicates that the specified file/directory + * name refers to a `.git` file or directory, or to any of these synonyms, and + * Git should therefore not track it. + * + * For performance reasons, _all_ Alternate Data Streams of `.git/` are + * forbidden, not just `::$INDEX_ALLOCATION`. + * + * This function is intended to be used by `git fsck` even on platforms where + * the backslash is a regular filename character, therefore it needs to handle + * backlash characters in the provided `name` specially: they are interpreted + * as directory separators. + */ +int is_ntfs_dotgit(const char *name) { - if (len < skip) + char c; + + /* + * Note that when we don't find `.git` or `git~1` we end up with `name` + * advanced partway through the string. That's okay, though, as we + * return immediately in those cases, without looking at `name` any + * further. + */ + c = *(name++); + if (c == '.') { + /* .git */ + if (((c = *(name++)) != 'g' && c != 'G') || + ((c = *(name++)) != 'i' && c != 'I') || + ((c = *(name++)) != 't' && c != 'T')) + return 0; + } else if (c == 'g' || c == 'G') { + /* git ~1 */ + if (((c = *(name++)) != 'i' && c != 'I') || + ((c = *(name++)) != 't' && c != 'T') || + *(name++) != '~' || + *(name++) != '1') + return 0; + } else return 0; - len -= skip; - path += skip; - while (len-- > 0) { - char c = *(path++); - if (c != ' ' && c != '.') + + for (;;) { + c = *(name++); + if (!c || c == '\\' || c == '/' || c == ':') + return 1; + if (c != '.' && c != ' ') return 0; } - return 1; } -int is_ntfs_dotgit(const char *name) +static int is_ntfs_dot_generic(const char *name, + const char *dotgit_name, + size_t len, + const char *dotgit_ntfs_shortname_prefix) { - int len; - - for (len = 0; ; len++) - if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { - if (only_spaces_and_periods(name, len, 4) && - !strncasecmp(name, ".git", 4)) - return 1; - if (only_spaces_and_periods(name, len, 5) && - !strncasecmp(name, "git~1", 5)) + int saw_tilde; + size_t i; + + if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) { + i = len + 1; +only_spaces_and_periods: + for (;;) { + char c = name[i++]; + if (!c || c == ':') return 1; - if (name[len] != '\\') + if (c != ' ' && c != '.') return 0; - name += len + 1; - len = -1; } + } + + /* + * Is it a regular NTFS short name, i.e. shortened to 6 characters, + * followed by ~1, ... ~4? + */ + if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' && + name[7] >= '1' && name[7] <= '4') { + i = 8; + goto only_spaces_and_periods; + } + + /* + * Is it a fall-back NTFS short name (for details, see + * https://en.wikipedia.org/wiki/8.3_filename? + */ + for (i = 0, saw_tilde = 0; i < 8; i++) + if (name[i] == '\0') + return 0; + else if (saw_tilde) { + if (name[i] < '0' || name[i] > '9') + return 0; + } else if (name[i] == '~') { + if (name[++i] < '1' || name[i] > '9') + return 0; + saw_tilde = 1; + } else if (i >= 6) + return 0; + else if (name[i] & 0x80) { + /* + * We know our needles contain only ASCII, so we clamp + * here to make the results of tolower() sane. + */ + return 0; + } else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i]) + return 0; + + goto only_spaces_and_periods; +} + +/* + * Inline helper to make sure compiler resolves strlen() on literals at + * compile time. + */ +static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name, + const char *dotgit_ntfs_shortname_prefix) +{ + return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name), + dotgit_ntfs_shortname_prefix); +} + +int is_ntfs_dotgitmodules(const char *name) +{ + return is_ntfs_dot_str(name, "gitmodules", "gi7eba"); +} + +int is_ntfs_dotgitignore(const char *name) +{ + return is_ntfs_dot_str(name, "gitignore", "gi250a"); +} + +int is_ntfs_dotgitattributes(const char *name) +{ + return is_ntfs_dot_str(name, "gitattributes", "gi7d29"); +} + +int looks_like_command_line_option(const char *str) +{ + return str && str[0] == '-'; } char *xdg_config_home(const char *filename) @@ -1196,12 +1513,26 @@ char *xdg_config_home(const char *filename) return NULL; } -GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD") -GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD") -GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG") -GIT_PATH_FUNC(git_path_merge_msg, "MERGE_MSG") -GIT_PATH_FUNC(git_path_merge_rr, "MERGE_RR") -GIT_PATH_FUNC(git_path_merge_mode, "MERGE_MODE") -GIT_PATH_FUNC(git_path_merge_head, "MERGE_HEAD") -GIT_PATH_FUNC(git_path_fetch_head, "FETCH_HEAD") -GIT_PATH_FUNC(git_path_shallow, "shallow") +char *xdg_cache_home(const char *filename) +{ + const char *home, *cache_home; + + assert(filename); + cache_home = getenv("XDG_CACHE_HOME"); + if (cache_home && *cache_home) + return mkpathdup("%s/git/%s", cache_home, filename); + + home = getenv("HOME"); + if (home) + return mkpathdup("%s/.cache/git/%s", home, filename); + return NULL; +} + +REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG") +REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") +REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") +REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE") +REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") +REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH") +REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD") +REPO_GIT_PATH_FUNC(shallow, "shallow")