]>
Commit | Line | Data |
---|---|---|
a64119e3 ET |
1 | #include "clar_libgit2.h" |
2 | #include "checkout_helpers.h" | |
3 | ||
4 | #include "git2/checkout.h" | |
5 | #include "repository.h" | |
6 | #include "buffer.h" | |
22a2d3d5 | 7 | #include "futils.h" |
a64119e3 ET |
8 | |
9 | static const char *repo_name = "nasty"; | |
10 | static git_repository *repo; | |
11 | static git_checkout_options checkout_opts; | |
12 | ||
13 | void test_checkout_nasty__initialize(void) | |
14 | { | |
15 | repo = cl_git_sandbox_init(repo_name); | |
16 | ||
17 | GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); | |
18 | checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; | |
19 | } | |
20 | ||
21 | void test_checkout_nasty__cleanup(void) | |
22 | { | |
23 | cl_git_sandbox_cleanup(); | |
24 | } | |
25 | ||
232bc895 ET |
26 | static void test_checkout_passes(const char *refname, const char *filename) |
27 | { | |
28 | git_oid commit_id; | |
29 | git_commit *commit; | |
30 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; | |
31 | git_buf path = GIT_BUF_INIT; | |
32 | ||
33 | cl_git_pass(git_buf_joinpath(&path, repo_name, filename)); | |
34 | ||
35 | cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); | |
36 | cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); | |
37 | ||
38 | opts.checkout_strategy = GIT_CHECKOUT_FORCE | | |
39 | GIT_CHECKOUT_DONT_UPDATE_INDEX; | |
40 | ||
41 | cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); | |
42 | cl_assert(!git_path_exists(path.ptr)); | |
43 | ||
44 | git_commit_free(commit); | |
ac3d33df | 45 | git_buf_dispose(&path); |
232bc895 ET |
46 | } |
47 | ||
48 | static void test_checkout_fails(const char *refname, const char *filename) | |
a64119e3 ET |
49 | { |
50 | git_oid commit_id; | |
51 | git_commit *commit; | |
52 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; | |
53 | git_buf path = GIT_BUF_INIT; | |
54 | ||
55 | cl_git_pass(git_buf_joinpath(&path, repo_name, filename)); | |
56 | ||
57 | cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); | |
58 | cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); | |
59 | ||
60 | opts.checkout_strategy = GIT_CHECKOUT_FORCE; | |
61 | ||
62 | cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts)); | |
63 | cl_assert(!git_path_exists(path.ptr)); | |
64 | ||
65 | git_commit_free(commit); | |
ac3d33df | 66 | git_buf_dispose(&path); |
a64119e3 ET |
67 | } |
68 | ||
69 | /* A tree that contains ".git" as a tree, with a blob inside | |
70 | * (".git/foobar"). | |
71 | */ | |
72 | void test_checkout_nasty__dotgit_tree(void) | |
73 | { | |
74 | test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar"); | |
75 | } | |
76 | ||
77 | /* A tree that contains ".GIT" as a tree, with a blob inside | |
78 | * (".GIT/foobar"). | |
79 | */ | |
80 | void test_checkout_nasty__dotcapitalgit_tree(void) | |
81 | { | |
82 | test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar"); | |
83 | } | |
84 | ||
85 | /* A tree that contains a tree ".", with a blob inside ("./foobar"). | |
86 | */ | |
87 | void test_checkout_nasty__dot_tree(void) | |
88 | { | |
89 | test_checkout_fails("refs/heads/dot_tree", "foobar"); | |
90 | } | |
91 | ||
92 | /* A tree that contains a tree ".", with a tree ".git", with a blob | |
93 | * inside ("./.git/foobar"). | |
94 | */ | |
95 | void test_checkout_nasty__dot_dotgit_tree(void) | |
96 | { | |
97 | test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar"); | |
98 | } | |
99 | ||
100 | /* A tree that contains a tree, with a tree "..", with a tree ".git", with a | |
101 | * blob inside ("foo/../.git/foobar"). | |
102 | */ | |
103 | void test_checkout_nasty__dotdot_dotgit_tree(void) | |
104 | { | |
105 | test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar"); | |
106 | } | |
107 | ||
108 | /* A tree that contains a tree, with a tree "..", with a blob inside | |
109 | * ("foo/../foobar"). | |
110 | */ | |
111 | void test_checkout_nasty__dotdot_tree(void) | |
112 | { | |
113 | test_checkout_fails("refs/heads/dotdot_tree", "foobar"); | |
114 | } | |
115 | ||
116 | /* A tree that contains a blob with the rogue name ".git/foobar" */ | |
117 | void test_checkout_nasty__dotgit_path(void) | |
118 | { | |
119 | test_checkout_fails("refs/heads/dotgit_path", ".git/foobar"); | |
120 | } | |
121 | ||
122 | /* A tree that contains a blob with the rogue name ".GIT/foobar" */ | |
123 | void test_checkout_nasty__dotcapitalgit_path(void) | |
124 | { | |
125 | test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar"); | |
126 | } | |
127 | ||
128 | /* A tree that contains a blob with the rogue name "./.git/foobar" */ | |
129 | void test_checkout_nasty__dot_dotgit_path(void) | |
130 | { | |
131 | test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar"); | |
132 | } | |
133 | ||
134 | /* A tree that contains a blob with the rogue name "./.GIT/foobar" */ | |
135 | void test_checkout_nasty__dot_dotcapitalgit_path(void) | |
136 | { | |
137 | test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar"); | |
138 | } | |
139 | ||
140 | /* A tree that contains a blob with the rogue name "foo/../.git/foobar" */ | |
141 | void test_checkout_nasty__dotdot_dotgit_path(void) | |
142 | { | |
143 | test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar"); | |
144 | } | |
145 | ||
146 | /* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */ | |
147 | void test_checkout_nasty__dotdot_dotcapitalgit_path(void) | |
148 | { | |
149 | test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar"); | |
150 | } | |
151 | ||
152 | /* A tree that contains a blob with the rogue name "foo/." */ | |
153 | void test_checkout_nasty__dot_path(void) | |
154 | { | |
155 | test_checkout_fails("refs/heads/dot_path", "./foobar"); | |
156 | } | |
157 | ||
158 | /* A tree that contains a blob with the rogue name "foo/." */ | |
159 | void test_checkout_nasty__dot_path_two(void) | |
160 | { | |
161 | test_checkout_fails("refs/heads/dot_path_two", "foo/."); | |
162 | } | |
163 | ||
164 | /* A tree that contains a blob with the rogue name "foo/../foobar" */ | |
165 | void test_checkout_nasty__dotdot_path(void) | |
166 | { | |
167 | test_checkout_fails("refs/heads/dotdot_path", "foobar"); | |
168 | } | |
169 | ||
170 | /* A tree that contains an entry with a backslash ".git\foobar" */ | |
171 | void test_checkout_nasty__dotgit_backslash_path(void) | |
172 | { | |
173 | #ifdef GIT_WIN32 | |
174 | test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); | |
175 | #endif | |
176 | } | |
177 | ||
178 | /* A tree that contains an entry with a backslash ".GIT\foobar" */ | |
179 | void test_checkout_nasty__dotcapitalgit_backslash_path(void) | |
180 | { | |
181 | #ifdef GIT_WIN32 | |
182 | test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); | |
183 | #endif | |
184 | } | |
185 | ||
186 | /* A tree that contains an entry with a backslash ".\.GIT\foobar" */ | |
187 | void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void) | |
188 | { | |
189 | #ifdef GIT_WIN32 | |
190 | test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar"); | |
191 | #endif | |
192 | } | |
193 | ||
194 | /* A tree that contains an entry ".git.", because Win32 APIs will drop the | |
195 | * trailing slash. | |
196 | */ | |
197 | void test_checkout_nasty__dot_git_dot(void) | |
198 | { | |
199 | #ifdef GIT_WIN32 | |
200 | test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); | |
201 | #endif | |
202 | } | |
203 | ||
204 | /* A tree that contains an entry "git~1", because that is typically the | |
205 | * short name for ".git". | |
206 | */ | |
207 | void test_checkout_nasty__git_tilde1(void) | |
208 | { | |
a64119e3 | 209 | test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); |
b6832cbf | 210 | test_checkout_fails("refs/heads/git_tilde1", "git~1/foobar"); |
a64119e3 ET |
211 | } |
212 | ||
213 | /* A tree that contains an entry "git~2", when we have forced the short | |
214 | * name for ".git" into "GIT~2". | |
215 | */ | |
216 | void test_checkout_nasty__git_custom_shortname(void) | |
217 | { | |
218 | #ifdef GIT_WIN32 | |
07c989e9 | 219 | if (!cl_sandbox_supports_8dot3()) |
5f28ec84 ET |
220 | clar__skip(); |
221 | ||
a64119e3 ET |
222 | cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); |
223 | cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); | |
224 | cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); | |
5f28ec84 | 225 | test_checkout_fails("refs/heads/git_tilde2", ".git/foobar"); |
a64119e3 ET |
226 | #endif |
227 | } | |
228 | ||
229 | /* A tree that contains an entry "git~3", which should be allowed, since | |
230 | * it is not the typical short name ("GIT~1") or the actual short name | |
231 | * ("GIT~2") for ".git". | |
232 | */ | |
233 | void test_checkout_nasty__only_looks_like_a_git_shortname(void) | |
234 | { | |
235 | #ifdef GIT_WIN32 | |
236 | git_oid commit_id; | |
237 | git_commit *commit; | |
238 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; | |
239 | ||
240 | cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); | |
241 | cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); | |
242 | cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); | |
243 | ||
244 | cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3")); | |
245 | cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); | |
246 | ||
247 | opts.checkout_strategy = GIT_CHECKOUT_FORCE; | |
248 | ||
249 | cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); | |
250 | cl_assert(git_path_exists("nasty/git~3/foobar")); | |
251 | ||
252 | git_commit_free(commit); | |
253 | #endif | |
254 | } | |
255 | ||
256 | /* A tree that contains an entry "git:", because Win32 APIs will reject | |
257 | * that as looking too similar to a drive letter. | |
258 | */ | |
259 | void test_checkout_nasty__dot_git_colon(void) | |
260 | { | |
261 | #ifdef GIT_WIN32 | |
262 | test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar"); | |
263 | #endif | |
264 | } | |
265 | ||
266 | /* A tree that contains an entry "git:foo", because Win32 APIs will turn | |
267 | * that into ".git". | |
268 | */ | |
269 | void test_checkout_nasty__dot_git_colon_stuff(void) | |
270 | { | |
271 | #ifdef GIT_WIN32 | |
272 | test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar"); | |
273 | #endif | |
274 | } | |
275 | ||
b6832cbf UG |
276 | /* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS |
277 | * will interpret that as a synonym to ".git", even when mounted via SMB | |
278 | * on macOS. | |
279 | */ | |
280 | void test_checkout_nasty__dotgit_alternate_data_stream(void) | |
281 | { | |
282 | test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file"); | |
283 | test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file"); | |
284 | } | |
285 | ||
11d67b75 ET |
286 | /* Trees that contains entries with a tree ".git" that contain |
287 | * byte sequences: | |
288 | * { 0xe2, 0x80, 0x8c } | |
289 | * { 0xe2, 0x80, 0x8d } | |
290 | * { 0xe2, 0x80, 0x8e } | |
291 | * { 0xe2, 0x80, 0x8f } | |
292 | * { 0xe2, 0x80, 0xaa } | |
293 | * { 0xe2, 0x80, 0xab } | |
294 | * { 0xe2, 0x80, 0xac } | |
295 | * { 0xe2, 0x80, 0xad } | |
296 | * { 0xe2, 0x81, 0xae } | |
297 | * { 0xe2, 0x81, 0xaa } | |
298 | * { 0xe2, 0x81, 0xab } | |
299 | * { 0xe2, 0x81, 0xac } | |
300 | * { 0xe2, 0x81, 0xad } | |
301 | * { 0xe2, 0x81, 0xae } | |
302 | * { 0xe2, 0x81, 0xaf } | |
303 | * { 0xef, 0xbb, 0xbf } | |
304 | * Because these map to characters that HFS filesystems "ignore". Thus | |
305 | * ".git<U+200C>" will map to ".git". | |
306 | */ | |
307 | void test_checkout_nasty__dot_git_hfs_ignorable(void) | |
308 | { | |
309 | #ifdef __APPLE__ | |
310 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); | |
311 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); | |
312 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); | |
313 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); | |
314 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); | |
315 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); | |
316 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); | |
317 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); | |
318 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); | |
319 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); | |
320 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); | |
321 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); | |
322 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); | |
323 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); | |
324 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); | |
325 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); | |
326 | #endif | |
327 | } | |
ec74b40c ET |
328 | |
329 | void test_checkout_nasty__honors_core_protecthfs(void) | |
330 | { | |
331 | cl_repo_set_bool(repo, "core.protectHFS", true); | |
332 | ||
333 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); | |
334 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); | |
335 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); | |
336 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); | |
337 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); | |
338 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); | |
339 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); | |
340 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); | |
341 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); | |
342 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); | |
343 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); | |
344 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); | |
345 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); | |
346 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); | |
347 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); | |
348 | test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); | |
349 | } | |
350 | ||
351 | void test_checkout_nasty__honors_core_protectntfs(void) | |
352 | { | |
353 | cl_repo_set_bool(repo, "core.protectNTFS", true); | |
354 | ||
355 | test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); | |
356 | test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); | |
357 | test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); | |
358 | test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); | |
359 | } | |
232bc895 ET |
360 | |
361 | void test_checkout_nasty__symlink1(void) | |
362 | { | |
363 | test_checkout_passes("refs/heads/symlink1", ".git/foobar"); | |
364 | } | |
365 | ||
366 | void test_checkout_nasty__symlink2(void) | |
367 | { | |
368 | test_checkout_passes("refs/heads/symlink2", ".git/foobar"); | |
369 | } | |
370 | ||
371 | void test_checkout_nasty__symlink3(void) | |
372 | { | |
373 | test_checkout_passes("refs/heads/symlink3", ".git/foobar"); | |
374 | } | |
375 | ||
4b3ec53c XL |
376 | void test_checkout_nasty__gitmodules_symlink(void) |
377 | { | |
378 | cl_repo_set_bool(repo, "core.protectHFS", true); | |
379 | test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); | |
380 | cl_repo_set_bool(repo, "core.protectHFS", false); | |
381 | ||
382 | cl_repo_set_bool(repo, "core.protectNTFS", true); | |
383 | test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); | |
384 | cl_repo_set_bool(repo, "core.protectNTFS", false); | |
385 | ||
386 | test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); | |
387 | } |