From 529fd30d1f81cc711ce3ca3857d637e84a1ca6e4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 8 Jul 2014 15:45:50 -0400 Subject: [PATCH] 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. --- src/clone.c | 42 +++++++++++++++++++--------------- src/path.c | 56 +++++++++++++++++++++------------------------ src/path.h | 1 + tests/clone/local.c | 56 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 94 insertions(+), 61 deletions(-) diff --git a/src/clone.c b/src/clone.c index 8f0284ae6..c7a708da7 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; + return 0; - is_url = !git__prefixcmp(url, "file://"); + 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; + } - if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) - return false; + path = fromurl.ptr; + } - path = url; - if (is_url) - path = url + strlen("file://"); + is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && + git_path_isdir(path); - if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) - return true; - - 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 5beab97ed..6d0b3749b 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; } +static int local_file_url_prefixlen(const char *file_url) +{ + int len = -1; + + if (git__prefixcmp(file_url, "file://") == 0) { + if (file_url[7] == '/') + len = 8; + else if (git__prefixcmp(file_url + 7, "localhost/") == 0) + len = 17; + } + + return len; +} + +bool git_path_is_local_file_url(const char *file_url) +{ + return (local_file_url_prefixlen(file_url) > 0); +} + int git_path_fromurl(git_buf *local_path_out, const char *file_url) { - int offset = 0, len; + int offset; assert(local_path_out && file_url); - if (git__prefixcmp(file_url, "file://") != 0) - return error_invalid_local_file_uri(file_url); - - offset += 7; - len = (int)strlen(file_url); - - 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); - - if (offset >= len || file_url[offset] == '/') + 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 3e6efe3de..b100af97e 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 c8ebc143d..78d026794 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"); - 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)); + 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)); + + git_buf_free(&buf); } void test_clone_local__hardlinks(void)