Merge pull request #2619 from ethomson/remotes_with_unc Remote paths: canonicalize UNC paths on Win32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
diff --git a/src/remote.c b/src/remote.c
index 8d928a5..ea510c7 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -114,10 +114,30 @@ static int get_check_cert(int *out, git_repository *repo)
}
#endif
+static int canonicalize_url(git_buf *out, const char *in)
+{
+ const char *c;
+
+#ifdef GIT_WIN32
+ /* Given a UNC path like \\server\path, we need to convert this
+ * to //server/path for compatibility with core git.
+ */
+ if (in[0] == '\\' && in[1] == '\\' &&
+ (git__isalpha(in[2]) || git__isdigit(in[2]))) {
+ for (c = in; *c; c++)
+ git_buf_putc(out, *c == '\\' ? '/' : *c);
+
+ return git_buf_oom(out) ? -1 : 0;
+ }
+#endif
+
+ return git_buf_puts(out, in);
+}
+
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
git_remote *remote;
- git_buf fetchbuf = GIT_BUF_INIT;
+ git_buf canonical_url = GIT_BUF_INIT, fetchbuf = GIT_BUF_INIT;
int error = -1;
/* name is optional */
@@ -129,11 +149,11 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
remote->repo = repo;
remote->update_fetchhead = 1;
- if (git_vector_init(&remote->refs, 32, NULL) < 0)
+ if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
+ canonicalize_url(&canonical_url, url) < 0)
goto on_error;
- remote->url = git__strdup(url);
- GITERR_CHECK_ALLOC(remote->url);
+ remote->url = git_buf_detach(&canonical_url);
if (name != NULL) {
remote->name = git__strdup(name);
@@ -151,11 +171,13 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
*out = remote;
git_buf_free(&fetchbuf);
+ git_buf_free(&canonical_url);
return 0;
on_error:
git_remote_free(remote);
git_buf_free(&fetchbuf);
+ git_buf_free(&canonical_url);
return error;
}
diff --git a/tests/clone/local.c b/tests/clone/local.c
index 78d0267..fec3be5 100644
--- a/tests/clone/local.c
+++ b/tests/clone/local.c
@@ -16,6 +16,40 @@ static int file_url(git_buf *buf, const char *host, const char *path)
return git_buf_printf(buf, "file://%s/%s", host, path);
}
+static int git_style_unc_path(git_buf *buf, const char *host, const char *path)
+{
+ git_buf_clear(buf);
+
+ if (host)
+ git_buf_printf(buf, "//%s/", host);
+
+ if (path[0] == '/')
+ path++;
+
+ if (isalpha(path[0]) && path[1] == ':' && path[2] == '/') {
+ git_buf_printf(buf, "%c$/", path[0]);
+ path += 3;
+ }
+
+ git_buf_puts(buf, path);
+
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int unc_path(git_buf *buf, const char *host, const char *path)
+{
+ char *c;
+
+ if (git_style_unc_path(buf, host, path) < 0)
+ return -1;
+
+ for (c = buf->ptr; *c; c++)
+ if (*c == '/')
+ *c = '\\';
+
+ return 0;
+}
+
void test_clone_local__should_clone_local(void)
{
git_buf buf = GIT_BUF_INIT;
@@ -121,3 +155,57 @@ void test_clone_local__hardlinks(void)
cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES));
}
+
+void test_clone_local__standard_unc_paths_are_written_git_style(void)
+{
+#ifdef GIT_WIN32
+ git_repository *repo;
+ git_remote *remote;
+ git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ git_buf unc = GIT_BUF_INIT, git_unc = GIT_BUF_INIT;
+
+ /* we use a fixture path because it needs to exist for us to want to clone */
+ const char *path = cl_fixture("testrepo.git");
+
+ cl_git_pass(unc_path(&unc, "localhost", path));
+ cl_git_pass(git_style_unc_path(&git_unc, "localhost", path));
+
+ cl_git_pass(git_clone(&repo, unc.ptr, "./clone.git", &opts));
+ cl_git_pass(git_remote_load(&remote, repo, "origin"));
+
+ cl_assert_equal_s(git_unc.ptr, git_remote_url(remote));
+
+ git_remote_free(remote);
+ git_repository_free(repo);
+ git_buf_free(&unc);
+ git_buf_free(&git_unc);
+
+ cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
+#endif
+}
+
+void test_clone_local__git_style_unc_paths(void)
+{
+#ifdef GIT_WIN32
+ git_repository *repo;
+ git_remote *remote;
+ git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ git_buf git_unc = GIT_BUF_INIT;
+
+ /* we use a fixture path because it needs to exist for us to want to clone */
+ const char *path = cl_fixture("testrepo.git");
+
+ cl_git_pass(git_style_unc_path(&git_unc, "localhost", path));
+
+ cl_git_pass(git_clone(&repo, git_unc.ptr, "./clone.git", &opts));
+ cl_git_pass(git_remote_load(&remote, repo, "origin"));
+
+ cl_assert_equal_s(git_unc.ptr, git_remote_url(remote));
+
+ git_remote_free(remote);
+ git_repository_free(repo);
+ git_buf_free(&git_unc);
+
+ cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
+#endif
+}