summaryrefslogtreecommitdiff
path: root/path.c
diff options
context:
space:
mode:
Diffstat (limited to 'path.c')
-rw-r--r--path.c595
1 files changed, 423 insertions, 172 deletions
diff --git a/path.c b/path.c
index 6f2aa69..10f4cbf 100644
--- a/path.c
+++ b/path.c
@@ -1,25 +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)
@@ -33,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;
@@ -48,117 +61,192 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
return cleanup_path(buf);
}
-static char *git_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)
+{
+ 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] = '/';
+}
+
+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, ...)
{
va_list args;
va_start(args, fmt);
- (void)git_vsnpath(buf, n, fmt, args);
+ do_git_path(sb, fmt, args);
va_end(args);
- return buf;
}
-char *git_pathdup(const char *fmt, ...)
+const char *git_path(const char *fmt, ...)
{
- char path[PATH_MAX];
+ struct strbuf *pathname = get_pathname();
va_list args;
va_start(args, fmt);
- (void)git_vsnpath(path, sizeof(path), fmt, args);
+ do_git_path(pathname, fmt, args);
va_end(args);
- return xstrdup(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, ...)
{
- const char *git_dir = get_git_dir();
- char *pathname = get_pathname();
+ struct strbuf sb = STRBUF_INIT;
va_list args;
- unsigned len;
+ va_start(args, fmt);
+ strbuf_vaddf(&sb, fmt, args);
+ va_end(args);
+ strbuf_cleanup_path(&sb);
+ return strbuf_detach(&sb, NULL);
+}
- len = strlen(git_dir);
- if (len > PATH_MAX-100)
- return bad_path;
- memcpy(pathname, git_dir, len);
- if (len && git_dir[len-1] != '/')
- pathname[len++] = '/';
+const char *mkpath(const char *fmt, ...)
+{
+ va_list args;
+ struct strbuf *pathname = get_pathname();
va_start(args, fmt);
- len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(pathname, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ return cleanup_path(pathname->buf);
}
-char *git_path_submodule(const char *path, const char *fmt, ...)
+const char *git_path_submodule(const char *path, const char *fmt, ...)
{
- char *pathname = get_pathname();
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf *buf = get_pathname();
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, '/');
-
- if (buf.len >= PATH_MAX)
- return bad_path;
- memcpy(pathname, buf.buf, buf.len + 1);
-
- strbuf_release(&buf);
- len = strlen(pathname);
+ strbuf_addch(buf, '/');
va_start(args, fmt);
- len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ strbuf_vaddf(buf, fmt, args);
va_end(args);
- if (len >= PATH_MAX)
- return bad_path;
- return cleanup_path(pathname);
+ strbuf_cleanup_path(buf);
+ return buf->buf;
}
int validate_headref(const char *path)
@@ -215,9 +303,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;
@@ -231,28 +317,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);
@@ -269,14 +355,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
@@ -355,28 +436,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;
@@ -388,55 +455,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;
}
@@ -453,8 +655,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;
@@ -529,50 +737,52 @@ 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
- * prefix_list = Colon-separated list of absolute paths
+ * prefixes = string_list containing normalized, absolute paths without
+ * trailing slashes (except for the root directory, which is denoted by "/").
*
- * Determines, for each path in prefix_list, whether the "prefix" really
+ * Determines, for each path in prefixes, whether the "prefix"
* is an ancestor directory of path. Returns the length of the longest
* ancestor directory, excluding any trailing slashes, or -1 if no prefix
- * is an ancestor. (Note that this means 0 is returned if prefix_list is
- * "/".) "/foo" is not considered an ancestor of "/foobar". Directories
+ * is an ancestor. (Note that this means 0 is returned if prefixes is
+ * ["/"].) "/foo" is not considered an ancestor of "/foobar". Directories
* are not considered to be their own ancestors. path must be in a
* canonical form: empty components, or "." or ".." components are not
- * allowed. prefix_list may be null, which is like "".
+ * allowed.
*/
-int longest_ancestor_length(const char *path, const char *prefix_list)
+int longest_ancestor_length(const char *path, struct string_list *prefixes)
{
- char buf[PATH_MAX+1];
- const char *ceil, *colon;
- int len, max_len = -1;
+ int i, max_len = -1;
- if (prefix_list == NULL || !strcmp(path, "/"))
+ if (!strcmp(path, "/"))
return -1;
- for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
- for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
- len = colon - ceil;
- if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
- continue;
- strlcpy(buf, ceil, len+1);
- if (normalize_path_copy(buf, buf) < 0)
- continue;
- len = strlen(buf);
- if (len > 0 && buf[len-1] == '/')
- buf[--len] = '\0';
+ for (i = 0; i < prefixes->nr; i++) {
+ const char *ceil = prefixes->items[i].string;
+ int len = strlen(ceil);
+
+ if (len == 1 && ceil[0] == '/')
+ len = 0; /* root matches anything, with length 0 */
+ else if (!strncmp(path, ceil, len) && path[len] == '/')
+ ; /* match of length len */
+ else
+ continue; /* no match */
- if (!strncmp(path, buf, len) &&
- path[len] == '/' &&
- len > max_len) {
+ if (len > max_len)
max_len = len;
- }
}
return max_len;
@@ -661,9 +871,50 @@ 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)
{
- if (has_dos_drive_prefix(path))
- return 2 + is_dir_sep(path[2]);
- return is_dir_sep(path[0]);
+ 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)
+{
+ 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;
}