diff options
Diffstat (limited to 'path.c')
-rw-r--r-- | path.c | 595 |
1 files changed, 421 insertions, 174 deletions
@@ -1,26 +1,31 @@ /* - * I'm tired of doing "vsnprintf()" etc just to open a - * file, so here's a "return static buffer with printf" - * interface for paths. - * - * It's obviously not thread-safe. Sue me. But it's quite - * useful for doing things like - * - * f = open(mkpath("%s/%s.git", base, name), O_RDONLY); - * - * which is what it's designed for. + * Utilities for paths and pathnames */ #include "cache.h" #include "strbuf.h" #include "string-list.h" +#include "dir.h" + +static int get_st_mode_bits(const char *path, int *mode) +{ + struct stat st; + if (lstat(path, &st) < 0) + return -1; + *mode = st.st_mode; + return 0; +} 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,207 @@ 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, ...) +static const char *common_list[] = { + "/branches", "/hooks", "/info", "!/logs", "/lost-found", + "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn", + "config", "!gc.pid", "packed-refs", "shallow", + NULL +}; + +static void update_common_dir(struct strbuf *buf, int git_dir_len) +{ + char *base = buf->buf + git_dir_len; + const char **p; + + if (is_dir_file(base, "logs", "HEAD") || + is_dir_file(base, "info", "sparse-checkout")) + return; /* keep this in $GIT_DIR */ + for (p = common_list; *p; p++) { + const char *path = *p; + int is_dir = 0; + if (*path == '!') + path++; + if (*path == '/') { + path++; + is_dir = 1; + } + if (is_dir && dir_prefix(base, path)) { + replace_dir(buf, git_dir_len, get_git_common_dir()); + return; + } + if (!is_dir && !strcmp(base, path)) { + replace_dir(buf, git_dir_len, get_git_common_dir()); + return; + } + } +} + +void report_linked_checkout_garbage(void) +{ + struct strbuf sb = STRBUF_INIT; + const char **p; + int len; + + if (!git_common_dir_env) + return; + strbuf_addf(&sb, "%s/", get_git_dir()); + len = sb.len; + for (p = common_list; *p; p++) { + const char *path = *p; + if (*path == '!') + 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) @@ -249,9 +318,7 @@ int validate_headref(const char *path) static struct passwd *getpw_str(const char *username, size_t len) { struct passwd *pw; - char *username_z = xmalloc(len + 1); - memcpy(username_z, username, len); - username_z[len] = '\0'; + char *username_z = xmemdupz(username, len); pw = getpwnam(username_z); free(username_z); return pw; @@ -265,28 +332,28 @@ static struct passwd *getpw_str(const char *username, size_t len) char *expand_user_path(const char *path) { struct strbuf user_path = STRBUF_INIT; - const char *first_slash = strchrnul(path, '/'); const char *to_copy = path; if (path == NULL) goto return_null; if (path[0] == '~') { + const char *first_slash = strchrnul(path, '/'); const char *username = path + 1; size_t username_len = first_slash - username; if (username_len == 0) { 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); @@ -303,14 +370,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 @@ -389,28 +451,14 @@ const char *enter_repo(const char *path, int strict) return NULL; } -int set_shared_perm(const char *path, int mode) +static int calc_shared_perm(int mode) { - struct stat st; - int tweak, shared, orig_mode; + int tweak; - if (!shared_repository) { - if (mode) - return chmod(path, mode & ~S_IFMT); - return 0; - } - if (!mode) { - if (lstat(path, &st) < 0) - return -1; - mode = st.st_mode; - orig_mode = mode; - } else - orig_mode = 0; if (shared_repository < 0) - shared = -shared_repository; + tweak = -shared_repository; else - shared = shared_repository; - tweak = shared; + tweak = shared_repository; if (!(mode & S_IWUSR)) tweak &= ~0222; @@ -422,55 +470,190 @@ int set_shared_perm(const char *path, int mode) else mode |= tweak; - if (S_ISDIR(mode)) { + return mode; +} + + +int adjust_shared_perm(const char *path) +{ + int old_mode, new_mode; + + if (!shared_repository) + return 0; + if (get_st_mode_bits(path, &old_mode) < 0) + return -1; + + new_mode = calc_shared_perm(old_mode); + if (S_ISDIR(old_mode)) { /* Copy read bits to execute bits */ - mode |= (shared & 0444) >> 2; - mode |= FORCE_DIR_SET_GID; + new_mode |= (new_mode & 0444) >> 2; + new_mode |= FORCE_DIR_SET_GID; } - if (((shared_repository < 0 - ? (orig_mode & (FORCE_DIR_SET_GID | 0777)) - : (orig_mode & mode)) != mode) && - chmod(path, (mode & ~S_IFMT)) < 0) + if (((old_mode ^ new_mode) & ~S_IFMT) && + chmod(path, (new_mode & ~S_IFMT)) < 0) return -2; return 0; } -const char *relative_path(const char *abs, const char *base) +static int have_same_root(const char *path1, const char *path2) +{ + int is_abs1, is_abs2; + + is_abs1 = is_absolute_path(path1); + is_abs2 = is_absolute_path(path2); + return (is_abs1 && is_abs2 && tolower(path1[0]) == tolower(path2[0])) || + (!is_abs1 && !is_abs2); +} + +/* + * Give path as relative to prefix. + * + * The strbuf may or may not be used, so do not assume it contains the + * returned path. + */ +const char *relative_path(const char *in, const char *prefix, + struct strbuf *sb) +{ + int in_len = in ? strlen(in) : 0; + int prefix_len = prefix ? strlen(prefix) : 0; + int in_off = 0; + int prefix_off = 0; + int i = 0, j = 0; + + if (!in_len) + return "./"; + else if (!prefix_len) + return in; + + if (have_same_root(in, prefix)) { + /* bypass dos_drive, for "c:" is identical to "C:" */ + if (has_dos_drive_prefix(in)) { + i = 2; + j = 2; + } + } else { + return in; + } + + while (i < prefix_len && j < in_len && prefix[i] == in[j]) { + if (is_dir_sep(prefix[i])) { + while (is_dir_sep(prefix[i])) + i++; + while (is_dir_sep(in[j])) + j++; + prefix_off = i; + in_off = j; + } else { + i++; + j++; + } + } + + if ( + /* "prefix" seems like prefix of "in" */ + i >= prefix_len && + /* + * but "/foo" is not a prefix of "/foobar" + * (i.e. prefix not end with '/') + */ + prefix_off < prefix_len) { + if (j >= in_len) { + /* in="/a/b", prefix="/a/b" */ + in_off = in_len; + } else if (is_dir_sep(in[j])) { + /* in="/a/b/c", prefix="/a/b" */ + while (is_dir_sep(in[j])) + j++; + in_off = j; + } else { + /* in="/a/bbb/c", prefix="/a/b" */ + i = prefix_off; + } + } else if ( + /* "in" is short than "prefix" */ + j >= in_len && + /* "in" not end with '/' */ + in_off < in_len) { + if (is_dir_sep(prefix[i])) { + /* in="/a/b", prefix="/a/b/c/" */ + while (is_dir_sep(prefix[i])) + i++; + in_off = in_len; + } + } + in += in_off; + in_len -= in_off; + + if (i >= prefix_len) { + if (!in_len) + return "./"; + else + return in; + } + + strbuf_reset(sb); + strbuf_grow(sb, in_len); + + while (i < prefix_len) { + if (is_dir_sep(prefix[i])) { + strbuf_addstr(sb, "../"); + while (is_dir_sep(prefix[i])) + i++; + continue; + } + i++; + } + if (!is_dir_sep(prefix[prefix_len - 1])) + strbuf_addstr(sb, "../"); + + strbuf_addstr(sb, in); + + return sb->buf; +} + +/* + * A simpler implementation of relative_path + * + * Get relative path by removing "prefix" from "in". This function + * first appears in v1.5.6-1-g044bbbc, and makes git_dir shorter + * to increase performance when traversing the path to work_tree. + */ +const char *remove_leading_path(const char *in, const char *prefix) { static char buf[PATH_MAX + 1]; int i = 0, j = 0; - if (!base || !base[0]) - return abs; - while (base[i]) { - if (is_dir_sep(base[i])) { - if (!is_dir_sep(abs[j])) - return abs; - while (is_dir_sep(base[i])) + if (!prefix || !prefix[0]) + return in; + while (prefix[i]) { + if (is_dir_sep(prefix[i])) { + if (!is_dir_sep(in[j])) + return in; + while (is_dir_sep(prefix[i])) i++; - while (is_dir_sep(abs[j])) + while (is_dir_sep(in[j])) j++; continue; - } else if (abs[j] != base[i]) { - return abs; + } else if (in[j] != prefix[i]) { + return in; } i++; j++; } if ( /* "/foo" is a prefix of "/foo" */ - abs[j] && + in[j] && /* "/foo" is not a prefix of "/foobar" */ - !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) + !is_dir_sep(prefix[i-1]) && !is_dir_sep(in[j]) ) - return abs; - while (is_dir_sep(abs[j])) + return in; + while (is_dir_sep(in[j])) j++; - if (!abs[j]) + if (!in[j]) strcpy(buf, "."); else - strcpy(buf, abs + j); + strcpy(buf, in + j); return buf; } @@ -487,8 +670,14 @@ const char *relative_path(const char *abs, const char *base) * * Note that this function is purely textual. It does not follow symlinks, * verify the existence of the path, or make any system calls. + * + * prefix_len != NULL is for a specific case of prefix_pathspec(): + * assume that src == dst and src[0..prefix_len-1] is already + * normalized, any time "../" eats up to the prefix_len part, + * prefix_len is reduced. In the end prefix_len is the remaining + * prefix that has not been overridden by user pathspec. */ -int normalize_path_copy(char *dst, const char *src) +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; @@ -563,11 +752,18 @@ int normalize_path_copy(char *dst, const char *src) /* Windows: dst[-1] cannot be backslash anymore */ while (dst0 < dst && dst[-1] != '/') dst--; + if (prefix_len && *prefix_len > dst - dst0) + *prefix_len = dst - dst0; } *dst = '\0'; return 0; } +int normalize_path_copy(char *dst, const char *src) +{ + return normalize_path_copy_len(dst, src, NULL); +} + /* * path = Canonical absolute path * prefixes = string_list containing normalized, absolute paths without @@ -690,9 +886,60 @@ int daemon_avoid_alias(const char *p) } } -int offset_1st_component(const char *path) +static int only_spaces_and_periods(const char *path, size_t len, size_t skip) +{ + if (len < skip) + return 0; + len -= skip; + path += skip; + while (len-- > 0) { + char c = *(path++); + if (c != ' ' && c != '.') + return 0; + } + return 1; +} + +int is_ntfs_dotgit(const char *name) +{ + 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)) + return 1; + if (name[len] != '\\') + return 0; + name += len + 1; + len = -1; + } +} + +char *xdg_config_home(const char *filename) { - if (has_dos_drive_prefix(path)) - return 2 + is_dir_sep(path[2]); - return is_dir_sep(path[0]); + 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") |