diff --git a/compat/basename.c b/compat/basename.c index d8f8a3c6dc..3ba9227f8f 100644 --- a/compat/basename.c +++ b/compat/basename.c @@ -1,15 +1,71 @@ #include "../git-compat-util.h" +#include "../strbuf.h" /* Adapted from libiberty's basename.c. */ char *gitbasename (char *path) { const char *base; - /* Skip over the disk name in MSDOS pathnames. */ - if (has_dos_drive_prefix(path)) - path += 2; + + if (path) + skip_dos_drive_prefix(&path); + + if (!path || !*path) + return "."; + for (base = path; *path; path++) { - if (is_dir_sep(*path)) - base = path + 1; + if (!is_dir_sep(*path)) + continue; + do { + path++; + } while (is_dir_sep(*path)); + if (*path) + base = path; + else + while (--path != base && is_dir_sep(*path)) + *path = '\0'; } return (char *)base; } + +char *gitdirname(char *path) +{ + char *p = path, *after_slash = NULL, c; + int dos_drive_prefix; + + if (!p) + return "."; + + if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) { + static struct strbuf buf = STRBUF_INIT; + +dot: + strbuf_reset(&buf); + strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path); + return buf.buf; + } + + /* + * POSIX.1-2001 says dirname("/") should return "/", and dirname("//") + * should return "//", but dirname("///") should return "/" again. + */ + if (is_dir_sep(*p)) { + if (!p[1] || (is_dir_sep(p[1]) && !p[2])) + return path; + after_slash = ++p; + } + while ((c = *(p++))) + if (is_dir_sep(c)) { + char *tentative = p - 1; + + /* POSIX.1-2001 says to ignore trailing slashes */ + while (is_dir_sep(*p)) + p++; + if (*p) + after_slash = tentative; + } + + if (!after_slash) + goto dot; + *after_slash = '\0'; + return path; +} diff --git a/compat/mingw.c b/compat/mingw.c index 6543b98ad4..d420431b1d 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2417,26 +2417,22 @@ pid_t waitpid(pid_t pid, int *status, int options) int mingw_offset_1st_component(const char *path) { - int offset = 0; - if (has_dos_drive_prefix(path)) - offset = 2; + char *pos = (char *)path; /* unc paths */ - else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) { - + if (!skip_dos_drive_prefix(&pos) && + is_dir_sep(pos[0]) && is_dir_sep(pos[1])) { /* skip server name */ - char *pos = strpbrk(path + 2, "\\/"); + pos = strpbrk(pos + 2, "\\/"); if (!pos) return 0; /* Error: malformed unc path */ do { pos++; } while (*pos && !is_dir_sep(*pos)); - - offset = pos - path; } - return offset + is_dir_sep(path[offset]); + return pos + is_dir_sep(*pos) - path; } int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) diff --git a/compat/mingw.h b/compat/mingw.h index 7b57f626b6..2abe64a980 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -416,7 +416,15 @@ HANDLE winansi_get_osfhandle(int fd); * git specific compatibility */ -#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') +#define has_dos_drive_prefix(path) \ + (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) +static inline int mingw_skip_dos_drive_prefix(char **path) +{ + int ret = has_dos_drive_prefix(*path); + *path += ret; + return ret; +} +#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix #define has_unc_prefix(path) (*(path) == '\\' && (path)[1] == '\\') #define is_dir_sep(c) ((c) == '/' || (c) == '\\') static inline char *mingw_find_last_dir_sep(const char *path) diff --git a/git-compat-util.h b/git-compat-util.h index b5e9ac2b80..2015bd96fc 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -255,6 +255,8 @@ struct itimerval { #else #define basename gitbasename extern char *gitbasename(char *); +#define dirname gitdirname +extern char *gitdirname(char *); #endif #ifndef NO_ICONV @@ -337,6 +339,14 @@ static inline int git_has_dos_drive_prefix(const char *path) #define has_dos_drive_prefix git_has_dos_drive_prefix #endif +#ifndef skip_dos_drive_prefix +static inline int git_skip_dos_drive_prefix(const char **path) +{ + return 0; +} +#define skip_dos_drive_prefix git_skip_dos_drive_prefix +#endif + #ifndef has_unc_prefix static inline int git_has_unc_prefix(const char *path) { diff --git a/path.c b/path.c index bbe1ef234f..c4d8d21cb5 100644 --- a/path.c +++ b/path.c @@ -787,13 +787,10 @@ const char *relative_path(const char *in, const char *prefix, else if (!prefix_len) return in; - if (have_same_root(in, prefix)) { + 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 { + i = j = has_dos_drive_prefix(in); + else { return in; } @@ -948,11 +945,10 @@ 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 = has_unc_prefix(src); - if (has_unc_prefix(src) || has_dos_drive_prefix(src)) { + for (i = i ? i : has_dos_drive_prefix(src); i > 0; i--) *dst++ = *src++; - *dst++ = *src++; - } dst0 = dst; if (is_dir_sep(*src)) { diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 627ef854d5..f0152a7ab4 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -59,6 +59,9 @@ case $(uname -s) in ;; esac +test_expect_success basename 'test-path-utils basename' +test_expect_success dirname 'test-path-utils dirname' + norm_path "" "" norm_path . "" norm_path ./ "" diff --git a/test-path-utils.c b/test-path-utils.c index c67bf65b34..74e74c92f2 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -39,6 +39,168 @@ static void normalize_argv_string(const char **var, const char *input) die("Bad value: %s\n", input); } +struct test_data { + char *from; /* input: transform from this ... */ + char *to; /* output: ... to this. */ +}; + +static int test_function(struct test_data *data, char *(*func)(char *input), + const char *funcname) +{ + int failed = 0, i; + static char buffer[1024]; + char *to; + + for (i = 0; data[i].to; i++) { + if (!data[i].from) + to = func(NULL); + else { + strcpy(buffer, data[i].from); + to = func(buffer); + } + if (strcmp(to, data[i].to)) { + error("FAIL: %s(%s) => '%s' != '%s'\n", + funcname, data[i].from, to, data[i].to); + failed++; + } + } + return !!failed; +} + +static struct test_data basename_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", ".." }, + { "/", "/" }, +#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H) + { "//", "//" }, + { "///", "//" }, + { "////", "//" }, +#else + { "//", "/" }, + { "///", "/" }, + { "////", "/" }, +#endif + { "usr", "usr" }, + { "/usr", "usr" }, + { "/usr/", "usr" }, + { "/usr//", "usr" }, + { "/usr/lib", "lib" }, + { "usr/lib", "lib" }, + { "usr/lib///", "lib" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + + /* --- win32 type paths --- */ + { "\\usr", "usr" }, + { "\\usr\\", "usr" }, + { "\\usr\\\\", "usr" }, + { "\\usr\\lib", "lib" }, + { "usr\\lib", "lib" }, + { "usr\\lib\\\\\\", "lib" }, + { "C:/usr", "usr" }, + { "C:/usr", "usr" }, + { "C:/usr/", "usr" }, + { "C:/usr//", "usr" }, + { "C:/usr/lib", "lib" }, + { "C:usr/lib", "lib" }, + { "C:usr/lib///", "lib" }, + { "C:", "." }, + { "C:a", "a" }, + { "C:/", "/" }, + { "C:///", "/" }, +#if defined(NO_LIBGEN_H) + { "\\", "\\" }, + { "\\\\", "\\" }, + { "\\\\\\", "\\" }, +#else + + /* win32 platform variations: */ +#if defined(__MINGW32__) + { "\\", "/" }, + { "\\\\", "/" }, + { "\\\\\\", "/" }, +#endif + +#if defined(_MSC_VER) + { "\\", "\\" }, + { "\\\\", "\\" }, + { "\\\\\\", "\\" }, +#endif + +#endif +#endif + { NULL, "." }, + { NULL, NULL } +}; + +static struct test_data dirname_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", "." }, + { "/", "/" }, + { "//", "//" }, +#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H) + { "///", "//" }, + { "////", "//" }, +#else + { "///", "/" }, + { "////", "/" }, +#endif + { "usr", "." }, + { "/usr", "/" }, + { "/usr/", "/" }, + { "/usr//", "/" }, + { "/usr/lib", "/usr" }, + { "usr/lib", "usr" }, + { "usr/lib///", "usr" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + + /* --- win32 type paths --- */ + { "\\", "\\" }, + { "\\\\", "\\\\" }, + { "\\usr", "\\" }, + { "\\usr\\", "\\" }, + { "\\usr\\\\", "\\" }, + { "\\usr\\lib", "\\usr" }, + { "usr\\lib", "usr" }, + { "usr\\lib\\\\\\", "usr" }, + { "C:a", "C:." }, + { "C:/", "C:/" }, + { "C:///", "C:/" }, + { "C:/usr", "C:/" }, + { "C:/usr/", "C:/" }, + { "C:/usr//", "C:/" }, + { "C:/usr/lib", "C:/usr" }, + { "C:usr/lib", "C:usr" }, + { "C:usr/lib///", "C:usr" }, + { "\\\\\\", "\\" }, + { "\\\\\\\\", "\\" }, +#if defined(NO_LIBGEN_H) + { "C:", "C:." }, +#else + + /* win32 platform variations: */ +#if defined(__MINGW32__) + /* the following is clearly wrong ... */ + { "C:", "." }, +#endif + +#if defined(_MSC_VER) + { "C:", "C:." }, +#endif + +#endif +#endif + { NULL, "." }, + { NULL, NULL } +}; + int main(int argc, char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { @@ -133,6 +295,12 @@ int main(int argc, char **argv) return 0; } + if (argc == 2 && !strcmp(argv[1], "basename")) + return test_function(basename_data, basename, argv[1]); + + if (argc == 2 && !strcmp(argv[1], "dirname")) + return test_function(dirname_data, dirname, argv[1]); + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1;