Commit 529fd30d1f81cc711ce3ca3857d637e84a1ca6e4

Edward Thomson 2014-07-08T15:45:50

Handle local file:/// paths on Windows Windows can't handle a path like `/c:/foo`; when turning file:/// URIs into local paths, we must strip the leading slash.

diff --git a/src/clone.c b/src/clone.c
index 8f0284a..c7a708d 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -371,27 +371,30 @@ cleanup:
 	return error;
 }
 
-int git_clone__should_clone_local(const char *url, git_clone_local_t local)
+int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local)
 {
-	const char *path;
-	int is_url;
+	git_buf fromurl = GIT_BUF_INIT;
+	const char *path = url_or_path;
+	bool is_url, is_local;
 
 	if (local == GIT_CLONE_NO_LOCAL)
-		return false;
-
-	is_url = !git__prefixcmp(url, "file://");
+		return 0;
 
-	if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS )
-		return false;
+	if (is_url = git_path_is_local_file_url(url_or_path)) {
+		if (git_path_fromurl(&fromurl, url_or_path) < 0) {
+			is_local = -1;
+			goto done;
+		}
 
-	path = url;
-	if (is_url)
-		path = url + strlen("file://");
+		path = fromurl.ptr;
+	}
 
-	if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL)
-		return true;
+	is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) &&
+		git_path_isdir(path);
 
-	return false;
+done:
+	git_buf_free(&fromurl);
+	return is_local;
 }
 
 int git_clone(
@@ -434,16 +437,19 @@ int git_clone(
 		return error;
 
 	if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
-		if (git_clone__should_clone_local(url, options.local)) {
-			int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
+		int should_clone = git_clone__should_clone_local(url, options.local);
+		int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
+
+		if (should_clone == 1)
 			error = clone_local_into(
 				repo, origin, &options.checkout_opts,
 				options.checkout_branch, link, options.signature);
-		} else {
+		else if (should_clone == 0)
 			error = clone_into(
 				repo, origin, &options.checkout_opts,
 				options.checkout_branch, options.signature);
-		}
+		else
+			error = -1;
 
 		git_remote_free(origin);
 	}
diff --git a/src/path.c b/src/path.c
index 5beab97..6d0b374 100644
--- a/src/path.c
+++ b/src/path.c
@@ -377,26 +377,33 @@ static int error_invalid_local_file_uri(const char *uri)
 	return -1;
 }
 
-int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+static int local_file_url_prefixlen(const char *file_url)
 {
-	int offset = 0, len;
+	int len = -1;
 
-	assert(local_path_out && file_url);
+	if (git__prefixcmp(file_url, "file://") == 0) {
+		if (file_url[7] == '/')
+			len = 8;
+		else if (git__prefixcmp(file_url + 7, "localhost/") == 0)
+			len = 17;
+	}
 
-	if (git__prefixcmp(file_url, "file://") != 0)
-		return error_invalid_local_file_uri(file_url);
+	return len;
+}
 
-	offset += 7;
-	len = (int)strlen(file_url);
+bool git_path_is_local_file_url(const char *file_url)
+{
+	return (local_file_url_prefixlen(file_url) > 0);
+}
 
-	if (offset < len && file_url[offset] == '/')
-		offset++;
-	else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0)
-		offset += 10;
-	else
-		return error_invalid_local_file_uri(file_url);
+int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+{
+	int offset;
 
-	if (offset >= len || file_url[offset] == '/')
+	assert(local_path_out && file_url);
+
+	if ((offset = local_file_url_prefixlen(file_url)) < 0 ||
+		file_url[offset] == '\0' || file_url[offset] == '/')
 		return error_invalid_local_file_uri(file_url);
 
 #ifndef GIT_WIN32
@@ -404,7 +411,6 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url)
 #endif
 
 	git_buf_clear(local_path_out);
-
 	return git__percent_decode(local_path_out, file_url + offset);
 }
 
@@ -1130,18 +1136,8 @@ int git_path_dirload_with_stat(
 
 int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
 {
-	int error;
-
-	/* If url_or_path begins with file:// treat it as a URL */
-	if (!git__prefixcmp(url_or_path, "file://")) {
-		if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) {
-			return error;
-		}
-	} else { /* We assume url_or_path is already a path */
-		if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) {
-			return error;
-		}
-	}
-
-	return 0;
+	if (git_path_is_local_file_url(url_or_path))
+		return git_path_fromurl(local_path_out, url_or_path);
+	else
+		return git_buf_sets(local_path_out, url_or_path);
 }
diff --git a/src/path.h b/src/path.h
index 3e6efe3..b100af9 100644
--- a/src/path.h
+++ b/src/path.h
@@ -439,6 +439,7 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen);
 extern bool git_path_does_fs_decompose_unicode(const char *root);
 
 /* Used for paths to repositories on the filesystem */
+extern bool git_path_is_local_file_url(const char *file_url);
 extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
 
 #endif
diff --git a/tests/clone/local.c b/tests/clone/local.c
index c8ebc14..78d0267 100644
--- a/tests/clone/local.c
+++ b/tests/clone/local.c
@@ -7,25 +7,55 @@
 #include "posix.h"
 #include "fileops.h"
 
+static int file_url(git_buf *buf, const char *host, const char *path)
+{
+	if (path[0] == '/')
+		path++;
+
+	git_buf_clear(buf);
+	return git_buf_printf(buf, "file://%s/%s", host, path);
+}
+
 void test_clone_local__should_clone_local(void)
 {
 	git_buf buf = GIT_BUF_INIT;
-	const char *path;
 
 	/* we use a fixture path because it needs to exist for us to want to clone */
-	
-	cl_git_pass(git_buf_printf(&buf, "file://%s", cl_fixture("testrepo.git")));
-	cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
-	cl_assert_equal_i(true,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
-	cl_assert_equal_i(true,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
-	cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
-	git_buf_free(&buf);
+	const char *path = cl_fixture("testrepo.git");
+
+	cl_git_pass(file_url(&buf, "", path));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
+
+	cl_git_pass(file_url(&buf, "localhost", path));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
+
+	cl_git_pass(file_url(&buf, "other-host.mycompany.com", path));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
+
+	/* Ensure that file:/// urls are percent decoded: .git == %2e%67%69%74 */
+	cl_git_pass(file_url(&buf, "", path));
+	git_buf_shorten(&buf, 4);
+	cl_git_pass(git_buf_puts(&buf, "%2e%67%69%74"));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
+	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
+
+	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL));
+	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS));
+	cl_assert_equal_i(0, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL));
 
-	path = cl_fixture("testrepo.git");
-	cl_assert_equal_i(true,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO));
-	cl_assert_equal_i(true,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL));
-	cl_assert_equal_i(true,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS));
-	cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL));
+	git_buf_free(&buf);
 }
 
 void test_clone_local__hardlinks(void)