1 #include "clar_libgit2.h"
2 
3 #include "git2/clone.h"
4 #include "clone.h"
5 #include "buffer.h"
6 #include "path.h"
7 #include "posix.h"
8 #include "futils.h"
9 
file_url(git_buf * buf,const char * host,const char * path)10 static int file_url(git_buf *buf, const char *host, const char *path)
11 {
12 	if (path[0] == '/')
13 		path++;
14 
15 	git_buf_clear(buf);
16 	return git_buf_printf(buf, "file://%s/%s", host, path);
17 }
18 
19 #ifdef GIT_WIN32
git_style_unc_path(git_buf * buf,const char * host,const char * path)20 static int git_style_unc_path(git_buf *buf, const char *host, const char *path)
21 {
22 	git_buf_clear(buf);
23 
24 	if (host)
25 		git_buf_printf(buf, "//%s/", host);
26 
27 	if (path[0] == '/')
28 		path++;
29 
30 	if (git__isalpha(path[0]) && path[1] == ':' && path[2] == '/') {
31 		git_buf_printf(buf, "%c$/", path[0]);
32 		path += 3;
33 	}
34 
35 	git_buf_puts(buf, path);
36 
37 	return git_buf_oom(buf) ? -1 : 0;
38 }
39 
unc_path(git_buf * buf,const char * host,const char * path)40 static int unc_path(git_buf *buf, const char *host, const char *path)
41 {
42 	char *c;
43 
44 	if (git_style_unc_path(buf, host, path) < 0)
45 		return -1;
46 
47 	for (c = buf->ptr; *c; c++)
48 		if (*c == '/')
49 			*c = '\\';
50 
51 	return 0;
52 }
53 #endif
54 
test_clone_local__should_clone_local(void)55 void test_clone_local__should_clone_local(void)
56 {
57 	git_buf buf = GIT_BUF_INIT;
58 
59 	/* we use a fixture path because it needs to exist for us to want to clone */
60 	const char *path = cl_fixture("testrepo.git");
61 
62 	cl_git_pass(file_url(&buf, "", path));
63 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
64 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
65 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
66 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
67 
68 	cl_git_pass(file_url(&buf, "localhost", path));
69 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
70 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
71 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
72 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
73 
74 	cl_git_pass(file_url(&buf, "other-host.mycompany.com", path));
75 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
76 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
77 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
78 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
79 
80 	/* Ensure that file:/// urls are percent decoded: .git == %2e%67%69%74 */
81 	cl_git_pass(file_url(&buf, "", path));
82 	git_buf_shorten(&buf, 4);
83 	cl_git_pass(git_buf_puts(&buf, "%2e%67%69%74"));
84 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
85 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
86 	cl_assert_equal_i(1,  git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
87 	cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
88 
89 	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO));
90 	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL));
91 	cl_assert_equal_i(1,  git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS));
92 	cl_assert_equal_i(0, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL));
93 
94 	git_buf_dispose(&buf);
95 }
96 
test_clone_local__hardlinks(void)97 void test_clone_local__hardlinks(void)
98 {
99 	git_repository *repo;
100 	git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
101 	git_buf buf = GIT_BUF_INIT;
102 	struct stat st;
103 
104 	/*
105 	 * In this first clone, we just copy over, since the temp dir
106 	 * will often be in a different filesystem, so we cannot
107 	 * link. It also allows us to control the number of links
108 	 */
109 	opts.bare = true;
110 	opts.local = GIT_CLONE_LOCAL_NO_LINKS;
111 	cl_git_pass(git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts));
112 	git_repository_free(repo);
113 
114 	/* This second clone is in the same filesystem, so we can hardlink */
115 
116 	opts.local = GIT_CLONE_LOCAL;
117 	cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone2.git", &opts));
118 
119 #ifndef GIT_WIN32
120 	git_buf_clear(&buf);
121 	cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
122 
123 	cl_git_pass(p_stat(buf.ptr, &st));
124 	cl_assert_equal_i(2, st.st_nlink);
125 #endif
126 
127 	git_repository_free(repo);
128 	git_buf_clear(&buf);
129 
130 	opts.local = GIT_CLONE_LOCAL_NO_LINKS;
131 	cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone3.git", &opts));
132 
133 	git_buf_clear(&buf);
134 	cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
135 
136 	cl_git_pass(p_stat(buf.ptr, &st));
137 	cl_assert_equal_i(1, st.st_nlink);
138 
139 	git_repository_free(repo);
140 
141 	/* this one should automatically use links */
142 	cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL));
143 
144 #ifndef GIT_WIN32
145 	git_buf_clear(&buf);
146 	cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
147 
148 	cl_git_pass(p_stat(buf.ptr, &st));
149 	cl_assert_equal_i(3, st.st_nlink);
150 #endif
151 
152 	git_buf_dispose(&buf);
153 	git_repository_free(repo);
154 
155 	cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
156 	cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES));
157 	cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES));
158 	cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES));
159 }
160 
test_clone_local__standard_unc_paths_are_written_git_style(void)161 void test_clone_local__standard_unc_paths_are_written_git_style(void)
162 {
163 #ifdef GIT_WIN32
164 	git_repository *repo;
165 	git_remote *remote;
166 	git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
167 	git_buf unc = GIT_BUF_INIT, git_unc = GIT_BUF_INIT;
168 
169 	/* we use a fixture path because it needs to exist for us to want to clone */
170 	const char *path = cl_fixture("testrepo.git");
171 
172 	cl_git_pass(unc_path(&unc, "localhost", path));
173 	cl_git_pass(git_style_unc_path(&git_unc, "localhost", path));
174 
175 	cl_git_pass(git_clone(&repo, unc.ptr, "./clone.git", &opts));
176 	cl_git_pass(git_remote_lookup(&remote, repo, "origin"));
177 
178 	cl_assert_equal_s(git_unc.ptr, git_remote_url(remote));
179 
180 	git_remote_free(remote);
181 	git_repository_free(repo);
182 	git_buf_dispose(&unc);
183 	git_buf_dispose(&git_unc);
184 
185 	cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
186 #endif
187 }
188 
test_clone_local__git_style_unc_paths(void)189 void test_clone_local__git_style_unc_paths(void)
190 {
191 #ifdef GIT_WIN32
192 	git_repository *repo;
193 	git_remote *remote;
194 	git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
195 	git_buf git_unc = GIT_BUF_INIT;
196 
197 	/* we use a fixture path because it needs to exist for us to want to clone */
198 	const char *path = cl_fixture("testrepo.git");
199 
200 	cl_git_pass(git_style_unc_path(&git_unc, "localhost", path));
201 
202 	cl_git_pass(git_clone(&repo, git_unc.ptr, "./clone.git", &opts));
203 	cl_git_pass(git_remote_lookup(&remote, repo, "origin"));
204 
205 	cl_assert_equal_s(git_unc.ptr, git_remote_url(remote));
206 
207 	git_remote_free(remote);
208 	git_repository_free(repo);
209 	git_buf_dispose(&git_unc);
210 
211 	cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
212 #endif
213 }
214