diff --git a/src/checkout.c b/src/checkout.c index 1074fe6bb..aad17f8b1 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1102,7 +1102,7 @@ static int checkout_conflicts_mark_directoryfile( goto done; } - prefixed = git_path_equal_or_prefixed(path, entry->path); + prefixed = git_path_equal_or_prefixed(path, entry->path, NULL); if (prefixed == GIT_PATH_EQUAL) continue; diff --git a/src/path.c b/src/path.c index 0bad96242..58d71921b 100644 --- a/src/path.c +++ b/src/path.c @@ -263,26 +263,31 @@ int git_path_root(const char *path) int git_path_join_unrooted( git_buf *path_out, const char *path, const char *base, ssize_t *root_at) { - int error, root; + ssize_t root; assert(path && path_out); - root = git_path_root(path); + root = (ssize_t)git_path_root(path); if (base != NULL && root < 0) { - error = git_buf_joinpath(path_out, base, path); + if (git_buf_joinpath(path_out, base, path) < 0) + return -1; - if (root_at) - *root_at = (ssize_t)strlen(base); - } - else { - error = git_buf_sets(path_out, path); + root = (ssize_t)strlen(base); + } else { + if (git_buf_sets(path_out, path) < 0) + return -1; - if (root_at) - *root_at = (root < 0) ? 0 : (ssize_t)root; + if (root < 0) + root = 0; + else if (base) + git_path_equal_or_prefixed(base, path, &root); } - return error; + if (root_at) + *root_at = root; + + return 0; } int git_path_prettify(git_buf *path_out, const char *path, const char *base) diff --git a/src/path.h b/src/path.h index b753140b2..440b5420c 100644 --- a/src/path.h +++ b/src/path.h @@ -396,21 +396,35 @@ enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 }; */ GIT_INLINE(int) git_path_equal_or_prefixed( const char *parent, - const char *child) + const char *child, + ssize_t *prefixlen) { const char *p = parent, *c = child; + int lastslash = 0; while (*p && *c) { + lastslash = (*p == '/'); + if (*p++ != *c++) return GIT_PATH_NOTEQUAL; } if (*p != '\0') return GIT_PATH_NOTEQUAL; - if (*c == '\0') + + if (*c == '\0') { + if (prefixlen) + *prefixlen = p - parent; + return GIT_PATH_EQUAL; - if (*c == '/') + } + + if (*c == '/' || lastslash) { + if (prefixlen) + *prefixlen = (p - parent) - lastslash; + return GIT_PATH_PREFIX; + } return GIT_PATH_NOTEQUAL; } diff --git a/tests/path/core.c b/tests/path/core.c index 5b110f611..064f1492a 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -305,3 +305,50 @@ void test_path_core__isvalid_dotgit_with_hfs_ignorables(void) cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\x80\xbf", GIT_PATH_REJECT_DOT_GIT_HFS)); cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\xab\x81", GIT_PATH_REJECT_DOT_GIT_HFS)); } + +static void test_join_unrooted( + const char *expected_result, + ssize_t expected_rootlen, + const char *path, + const char *base) +{ + git_buf result = GIT_BUF_INIT; + ssize_t root_at; + + cl_git_pass(git_path_join_unrooted(&result, path, base, &root_at)); + cl_assert_equal_s(expected_result, result.ptr); + cl_assert_equal_i(expected_rootlen, root_at); + + git_buf_free(&result); +} + +void test_path_core__join_unrooted(void) +{ + git_buf out = GIT_BUF_INIT; + + test_join_unrooted("foo", 0, "foo", NULL); + test_join_unrooted("foo/bar", 0, "foo/bar", NULL); + + /* Relative paths have base prepended */ + test_join_unrooted("/foo/bar", 4, "bar", "/foo"); + test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo"); + test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar"); + + /* Absolute paths are not prepended with base */ + test_join_unrooted("/foo", 0, "/foo", "/asdf"); + test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf"); + + /* Drive letter is given as root length on Windows */ + test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); + test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); + + /* Base is returned when it's provided and is the prefix */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); + + /* Trailing slash in the base is ignored */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/"); + + git_buf_free(&out); +}