diff options
Diffstat (limited to 'path.c')
-rw-r--r-- | path.c | 539 |
1 files changed, 419 insertions, 120 deletions
@@ -4,6 +4,7 @@ #include "cache.h" #include "strbuf.h" #include "string-list.h" +#include "dir.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -16,11 +17,15 @@ static int get_st_mode_bits(const char *path, int *mode) static char bad_path[] = "/bad-path/"; -static char *get_pathname(void) +static struct strbuf *get_pathname(void) { - static char pathname_array[4][PATH_MAX]; + static struct strbuf pathname_array[4] = { + STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; static int index; - return pathname_array[3 & ++index]; + struct strbuf *sb = &pathname_array[3 & ++index]; + strbuf_reset(sb); + return sb; } static char *cleanup_path(char *path) @@ -34,6 +39,13 @@ static char *cleanup_path(char *path) return path; } +static void strbuf_cleanup_path(struct strbuf *sb) +{ + char *path = cleanup_path(sb->buf); + if (path > sb->buf) + strbuf_remove(sb, 0, path - sb->buf); +} + char *mksnpath(char *buf, size_t n, const char *fmt, ...) { va_list args; @@ -49,150 +61,424 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...) return cleanup_path(buf); } -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args) +static int dir_prefix(const char *buf, const char *dir) { - const char *git_dir = get_git_dir(); - size_t len; + int len = strlen(dir); + return !strncmp(buf, dir, len) && + (is_dir_sep(buf[len]) || buf[len] == '\0'); +} - len = strlen(git_dir); - if (n < len + 1) - goto bad; - memcpy(buf, git_dir, len); - if (len && !is_dir_sep(git_dir[len-1])) - buf[len++] = '/'; - len += vsnprintf(buf + len, n - len, fmt, args); - if (len >= n) - goto bad; - return cleanup_path(buf); -bad: - strlcpy(buf, bad_path, n); - return buf; +/* $buf =~ m|$dir/+$file| but without regex */ +static int is_dir_file(const char *buf, const char *dir, const char *file) +{ + int len = strlen(dir); + if (strncmp(buf, dir, len) || !is_dir_sep(buf[len])) + return 0; + while (is_dir_sep(buf[len])) + len++; + return !strcmp(buf + len, file); } -char *git_snpath(char *buf, size_t n, const char *fmt, ...) +static void replace_dir(struct strbuf *buf, int len, const char *newdir) { - char *ret; - va_list args; - va_start(args, fmt); - ret = vsnpath(buf, n, fmt, args); - va_end(args); - return ret; + int newlen = strlen(newdir); + int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) && + !is_dir_sep(newdir[newlen - 1]); + if (need_sep) + len--; /* keep one char, to be replaced with '/' */ + strbuf_splice(buf, 0, len, newdir, newlen); + if (need_sep) + buf->buf[newlen] = '/'; } -char *git_pathdup(const char *fmt, ...) +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; +}; + +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, 0, 0, NULL } +}; + +/* + * A compressed trie. A trie node consists of zero or more characters that + * are common to all elements with this prefix, optionally followed by some + * children. If value is not NULL, the trie node is a terminal node. + * + * For example, consider the following set of strings: + * abc + * def + * definite + * definition + * + * The trie would look 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") + * i: len = 3, contents = nit, children e and i non-NULL, value = NULL + * e: len = 0, children all NULL, value = (data for "definite") + * i: len = 2, contents = on, children all NULL, + * value = (data for "definition") + */ +struct trie { + struct trie *children[256]; + int len; + char *contents; + void *value; +}; + +static struct trie *make_trie_node(const char *key, void *value) +{ + struct trie *new_node = xcalloc(1, sizeof(*new_node)); + new_node->len = strlen(key); + if (new_node->len) { + new_node->contents = xmalloc(new_node->len); + memcpy(new_node->contents, key, new_node->len); + } + new_node->value = value; + return new_node; +} + +/* + * Add a key/value pair to a trie. The key is assumed to be \0-terminated. + * If there was an existing value for this key, return it. + */ +static void *add_to_trie(struct trie *root, const char *key, void *value) +{ + struct trie *child; + void *old; + int i; + + if (!*key) { + /* we have reached the end of the key */ + old = root->value; + root->value = value; + return old; + } + + for (i = 0; i < root->len; i++) { + if (root->contents[i] == key[i]) + continue; + + /* + * Split this node: child will contain this node's + * existing children. + */ + child = malloc(sizeof(*child)); + memcpy(child->children, root->children, sizeof(root->children)); + + child->len = root->len - i - 1; + if (child->len) { + child->contents = xstrndup(root->contents + i + 1, + child->len); + } + child->value = root->value; + root->value = NULL; + root->len = i; + + memset(root->children, 0, sizeof(root->children)); + root->children[(unsigned char)root->contents[i]] = child; + + /* This is the newly-added child. */ + root->children[(unsigned char)key[i]] = + make_trie_node(key + i + 1, value); + return NULL; + } + + /* We have matched the entire compressed section */ + if (key[i]) { + child = root->children[(unsigned char)key[root->len]]; + if (child) { + return add_to_trie(child, key + root->len + 1, value); + } else { + child = make_trie_node(key + root->len + 1, value); + root->children[(unsigned char)key[root->len]] = child; + return NULL; + } + } + + old = root->value; + root->value = value; + return old; +} + +typedef int (*match_fn)(const char *unmatched, void *data, 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. + * + * 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 | + * |-----------------|------------|---------------|--------------| + * + */ +static int trie_find(struct trie *root, const char *key, match_fn fn, + void *baton) +{ + int i; + int result; + struct trie *child; + + if (!*key) { + /* we have reached the end of the key */ + if (root->value && !root->len) + return fn(key, root->value, baton); + else + return -1; + } + + for (i = 0; i < root->len; i++) { + /* Partial path normalization: skip consecutive slashes. */ + if (key[i] == '/' && key[i+1] == '/') { + key++; + continue; + } + if (root->contents[i] != key[i]) + return -1; + } + + /* Matched the entire compressed section */ + key += i; + if (!*key) + /* End of key */ + return fn(key, root->value, baton); + + /* Partial path normalization: skip consecutive slashes */ + while (key[0] == '/' && key[1] == '/') + key++; + + child = root->children[(unsigned char)*key]; + if (child) + result = trie_find(child, key + 1, fn, baton); + else + result = -1; + + if (result >= 0 || (*key != '/' && *key != 0)) + return result; + if (root->value) + return fn(key, root->value, baton); + else + return -1; +} + +static struct trie common_trie; +static int common_trie_done_setup; + +static void init_common_trie(void) +{ + struct common_dir *p; + + if (common_trie_done_setup) + return; + + for (p = common_list; p->dirname; p++) + add_to_trie(&common_trie, p->dirname, p); + + common_trie_done_setup = 1; +} + +/* + * Helper function for update_common_dir: returns 1 if the dir + * prefix is common. + */ +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; + + if (!dir->is_dir && unmatched[0] == 0) + return !dir->exclude; + + return 0; +} + +static void update_common_dir(struct strbuf *buf, int git_dir_len) +{ + char *base = buf->buf + git_dir_len; + init_common_trie(); + if (trie_find(&common_trie, base, check_common, NULL) > 0) + replace_dir(buf, git_dir_len, get_git_common_dir()); +} + +void report_linked_checkout_garbage(void) +{ + struct strbuf sb = STRBUF_INIT; + const struct common_dir *p; + int len; + + if (!git_common_dir_env) + return; + strbuf_addf(&sb, "%s/", get_git_dir()); + len = sb.len; + for (p = common_list; p->dirname; p++) { + const char *path = p->dirname; + if (p->ignore_garbage) + continue; + strbuf_setlen(&sb, len); + strbuf_addstr(&sb, path); + if (file_exists(sb.buf)) + report_garbage("unused in linked checkout", sb.buf); + } + strbuf_release(&sb); +} + +static void adjust_git_path(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")) + strbuf_splice(buf, 0, buf->len, + get_graft_file(), strlen(get_graft_file())); + else if (git_index_env && !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); +} + +static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) +{ + int gitdir_len; + strbuf_addstr(buf, get_git_dir()); + 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); + strbuf_cleanup_path(buf); +} + +void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) { - char path[PATH_MAX], *ret; va_list args; va_start(args, fmt); - ret = vsnpath(path, sizeof(path), fmt, args); + do_git_path(sb, fmt, args); va_end(args); - return xstrdup(ret); } -char *mkpathdup(const char *fmt, ...) +const char *git_path(const char *fmt, ...) { - char *path; - struct strbuf sb = STRBUF_INIT; + struct strbuf *pathname = get_pathname(); va_list args; - va_start(args, fmt); - strbuf_vaddf(&sb, fmt, args); + do_git_path(pathname, fmt, args); va_end(args); - path = xstrdup(cleanup_path(sb.buf)); - - strbuf_release(&sb); - return path; + return pathname->buf; } -char *mkpath(const char *fmt, ...) +char *git_pathdup(const char *fmt, ...) { + struct strbuf path = STRBUF_INIT; va_list args; - unsigned len; - char *pathname = get_pathname(); - va_start(args, fmt); - len = vsnprintf(pathname, PATH_MAX, fmt, args); + do_git_path(&path, fmt, args); va_end(args); - if (len >= PATH_MAX) - return bad_path; - return cleanup_path(pathname); + return strbuf_detach(&path, NULL); } -char *git_path(const char *fmt, ...) +char *mkpathdup(const char *fmt, ...) { - char *pathname = get_pathname(); + struct strbuf sb = STRBUF_INIT; va_list args; - char *ret; - va_start(args, fmt); - ret = vsnpath(pathname, PATH_MAX, fmt, args); + strbuf_vaddf(&sb, fmt, args); va_end(args); - return ret; + strbuf_cleanup_path(&sb); + return strbuf_detach(&sb, NULL); } -void home_config_paths(char **global, char **xdg, char *file) +const char *mkpath(const char *fmt, ...) { - char *xdg_home = getenv("XDG_CONFIG_HOME"); - char *home = getenv("HOME"); - char *to_free = NULL; - - if (!home) { - if (global) - *global = NULL; - } else { - if (!xdg_home) { - to_free = mkpathdup("%s/.config", home); - xdg_home = to_free; - } - if (global) - *global = mkpathdup("%s/.gitconfig", home); - } - - if (!xdg_home) - *xdg = NULL; - else - *xdg = mkpathdup("%s/git/%s", xdg_home, file); - - free(to_free); + va_list args; + struct strbuf *pathname = get_pathname(); + va_start(args, fmt); + strbuf_vaddf(pathname, fmt, args); + va_end(args); + return cleanup_path(pathname->buf); } -char *git_path_submodule(const char *path, const char *fmt, ...) +static void do_submodule_path(struct strbuf *buf, const char *path, + const char *fmt, va_list args) { - char *pathname = get_pathname(); - struct strbuf buf = STRBUF_INIT; const char *git_dir; - va_list args; - unsigned len; - - len = strlen(path); - if (len > PATH_MAX-100) - return bad_path; - strbuf_addstr(&buf, path); - if (len && path[len-1] != '/') - strbuf_addch(&buf, '/'); - strbuf_addstr(&buf, ".git"); + strbuf_addstr(buf, path); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, ".git"); - git_dir = read_gitfile(buf.buf); + git_dir = read_gitfile(buf->buf); if (git_dir) { - strbuf_reset(&buf); - strbuf_addstr(&buf, git_dir); + strbuf_reset(buf); + strbuf_addstr(buf, git_dir); } - strbuf_addch(&buf, '/'); + strbuf_addch(buf, '/'); - if (buf.len >= PATH_MAX) - return bad_path; - memcpy(pathname, buf.buf, buf.len + 1); + strbuf_vaddf(buf, fmt, args); + strbuf_cleanup_path(buf); +} - strbuf_release(&buf); - len = strlen(pathname); +char *git_pathdup_submodule(const char *path, const char *fmt, ...) +{ + va_list args; + struct strbuf buf = STRBUF_INIT; + va_start(args, fmt); + do_submodule_path(&buf, path, fmt, args); + va_end(args); + return strbuf_detach(&buf, NULL); +} +void strbuf_git_path_submodule(struct strbuf *buf, const char *path, + const char *fmt, ...) +{ + va_list args; va_start(args, fmt); - len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args); + do_submodule_path(buf, path, fmt, args); va_end(args); - if (len >= PATH_MAX) - return bad_path; - return cleanup_path(pathname); } int validate_headref(const char *path) @@ -275,16 +561,16 @@ char *expand_user_path(const char *path) const char *home = getenv("HOME"); if (!home) goto return_null; - strbuf_add(&user_path, home, strlen(home)); + strbuf_addstr(&user_path, home); } else { struct passwd *pw = getpw_str(username, username_len); if (!pw) goto return_null; - strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir)); + strbuf_addstr(&user_path, pw->pw_dir); } to_copy = first_slash; } - strbuf_add(&user_path, to_copy, strlen(to_copy)); + strbuf_addstr(&user_path, to_copy); return strbuf_detach(&user_path, NULL); return_null: strbuf_release(&user_path); @@ -301,14 +587,9 @@ return_null: * (3) "relative/path" to mean cwd relative directory; or * (4) "/absolute/path" to mean absolute directory. * - * Unless "strict" is given, we try access() for existence of "%s.git/.git", - * "%s/.git", "%s.git", "%s" in this order. The first one that exists is - * what we try. - * - * Second, we try chdir() to that. Upon failure, we return NULL. - * - * Then, we try if the current directory is a valid git repository. - * Upon failure, we return NULL. + * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git" + * in this order. We select the first one that is a valid git repository, and + * chdir() to it. If none match, or we fail to chdir, we return NULL. * * If all goes well, we return the directory we used to chdir() (but * before ~user is expanded), avoiding getcwd() resolving symbolic @@ -822,13 +1103,6 @@ int daemon_avoid_alias(const char *p) } } -int offset_1st_component(const char *path) -{ - if (has_dos_drive_prefix(path)) - return 2 + is_dir_sep(path[2]); - return is_dir_sep(path[0]); -} - static int only_spaces_and_periods(const char *path, size_t len, size_t skip) { if (len < skip) @@ -861,3 +1135,28 @@ int is_ntfs_dotgit(const char *name) len = -1; } } + +char *xdg_config_home(const char *filename) +{ + const char *home, *config_home; + + assert(filename); + config_home = getenv("XDG_CONFIG_HOME"); + if (config_home && *config_home) + return mkpathdup("%s/git/%s", config_home, filename); + + home = getenv("HOME"); + if (home) + return mkpathdup("%s/.config/git/%s", home, 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") |