Commit 1fbfcdfcd0f3b87e95cea15b3c90808ccd5f9a79

Edward Thomson 2015-01-12T15:48:53

git_path_join_unrooted: return base len The documentation for `git_path_join_unrooted` states that the base length will be returned, so that consumers like checkout know where to start creating directories instead of always creating directories at the directory root.

diff --git a/src/checkout.c b/src/checkout.c
index 1074fe6..aad17f8 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 0bad962..58d7192 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 b753140..440b542 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 5b110f6..064f149 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);
+}