]>
Commit | Line | Data |
---|---|---|
0d64bef9 | 1 | #include "clar_libgit2.h" |
4b3ec53c | 2 | #include "diff_generate.h" |
0d64bef9 RB |
3 | #include "git2/checkout.h" |
4 | #include "path.h" | |
5 | #include "posix.h" | |
22a2d3d5 | 6 | #include "futils.h" |
0d64bef9 RB |
7 | |
8 | static git_repository *g_repo = NULL; | |
9 | ||
70681ff7 JH |
10 | /* |
11 | From the test repo used for this test: | |
12 | -------------------------------------- | |
13 | ||
14 | This is a test repo for libgit2 where tree entries have type changes | |
15 | ||
16 | The key types that could be found in tree entries are: | |
17 | ||
18 | 1 - GIT_FILEMODE_NEW = 0000000 | |
19 | 2 - GIT_FILEMODE_TREE = 0040000 | |
20 | 3 - GIT_FILEMODE_BLOB = 0100644 | |
21 | 4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755 | |
22 | 5 - GIT_FILEMODE_LINK = 0120000 | |
23 | 6 - GIT_FILEMODE_COMMIT = 0160000 | |
24 | ||
25 | I will try to have every type of transition somewhere in the history | |
26 | of this repo. | |
27 | ||
28 | Commits | |
29 | ------- | |
30 | Initial commit - a(1) b(1) c(1) d(1) e(1) | |
31 | Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6) | |
32 | Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2) | |
33 | Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4) | |
34 | Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2) | |
35 | Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6) | |
36 | Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1) | |
37 | ||
38 | */ | |
39 | ||
0d64bef9 RB |
40 | static const char *g_typechange_oids[] = { |
41 | "79b9f23e85f55ea36a472a902e875bc1121a94cb", | |
42 | "9bdb75b73836a99e3dbeea640a81de81031fdc29", | |
43 | "0e7ed140b514b8cae23254cb8656fe1674403aff", | |
44 | "9d0235c7a7edc0889a18f97a42ee6db9fe688447", | |
45 | "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a", | |
46 | "1b63caae4a5ca96f78e8dfefc376c6a39a142475", | |
47 | "6eae26c90e8ccc4d16208972119c40635489c6f0", | |
48 | NULL | |
49 | }; | |
50 | ||
51 | static bool g_typechange_empty[] = { | |
52 | true, false, false, false, false, false, true, true | |
53 | }; | |
54 | ||
70681ff7 JH |
55 | static const int g_typechange_expected_conflicts[] = { |
56 | 1, 2, 3, 3, 2, 3, 2 | |
57 | }; | |
58 | ||
59 | static const int g_typechange_expected_untracked[] = { | |
60 | 6, 4, 3, 2, 3, 2, 5 | |
61 | }; | |
62 | ||
0d64bef9 RB |
63 | void test_checkout_typechange__initialize(void) |
64 | { | |
65 | g_repo = cl_git_sandbox_init("typechanges"); | |
66 | ||
67 | cl_fixture_sandbox("submod2_target"); | |
68 | p_rename("submod2_target/.gitted", "submod2_target/.git"); | |
69 | } | |
70 | ||
71 | void test_checkout_typechange__cleanup(void) | |
72 | { | |
73 | cl_git_sandbox_cleanup(); | |
74 | cl_fixture_cleanup("submod2_target"); | |
75 | } | |
76 | ||
c50c58de RB |
77 | static void assert_file_exists(const char *path) |
78 | { | |
79 | cl_assert_(git_path_isfile(path), path); | |
80 | } | |
81 | ||
82 | static void assert_dir_exists(const char *path) | |
83 | { | |
84 | cl_assert_(git_path_isdir(path), path); | |
85 | } | |
86 | ||
87 | static void assert_workdir_matches_tree( | |
88 | git_repository *repo, const git_oid *id, const char *root, bool recurse) | |
89 | { | |
90 | git_object *obj; | |
91 | git_tree *tree; | |
92 | size_t i, max_i; | |
93 | git_buf path = GIT_BUF_INIT; | |
94 | ||
95 | if (!root) | |
96 | root = git_repository_workdir(repo); | |
97 | cl_assert(root); | |
98 | ||
ac3d33df JK |
99 | cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJECT_ANY)); |
100 | cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJECT_TREE)); | |
c50c58de RB |
101 | git_object_free(obj); |
102 | ||
103 | max_i = git_tree_entrycount(tree); | |
104 | ||
105 | for (i = 0; i < max_i; ++i) { | |
106 | const git_tree_entry *te = git_tree_entry_byindex(tree, i); | |
107 | cl_assert(te); | |
108 | ||
109 | cl_git_pass(git_buf_joinpath(&path, root, git_tree_entry_name(te))); | |
110 | ||
111 | switch (git_tree_entry_type(te)) { | |
ac3d33df | 112 | case GIT_OBJECT_COMMIT: |
c50c58de RB |
113 | assert_dir_exists(path.ptr); |
114 | break; | |
ac3d33df | 115 | case GIT_OBJECT_TREE: |
c50c58de RB |
116 | assert_dir_exists(path.ptr); |
117 | if (recurse) | |
118 | assert_workdir_matches_tree( | |
119 | repo, git_tree_entry_id(te), path.ptr, true); | |
120 | break; | |
ac3d33df | 121 | case GIT_OBJECT_BLOB: |
c50c58de RB |
122 | switch (git_tree_entry_filemode(te)) { |
123 | case GIT_FILEMODE_BLOB: | |
124 | case GIT_FILEMODE_BLOB_EXECUTABLE: | |
125 | assert_file_exists(path.ptr); | |
126 | /* because of cross-platform, don't confirm exec bit yet */ | |
127 | break; | |
128 | case GIT_FILEMODE_LINK: | |
129 | cl_assert_(git_path_exists(path.ptr), path.ptr); | |
130 | /* because of cross-platform, don't confirm link yet */ | |
131 | break; | |
132 | default: | |
133 | cl_assert(false); /* really?! */ | |
134 | } | |
135 | break; | |
136 | default: | |
137 | cl_assert(false); /* really?!! */ | |
138 | } | |
139 | } | |
140 | ||
141 | git_tree_free(tree); | |
ac3d33df | 142 | git_buf_dispose(&path); |
c50c58de RB |
143 | } |
144 | ||
145 | void test_checkout_typechange__checkout_typechanges_safe(void) | |
0d64bef9 RB |
146 | { |
147 | int i; | |
148 | git_object *obj; | |
6affd71f | 149 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; |
0d64bef9 | 150 | |
0d64bef9 RB |
151 | for (i = 0; g_typechange_oids[i] != NULL; ++i) { |
152 | cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); | |
7e5c8a5b | 153 | |
70681ff7 | 154 | opts.checkout_strategy = !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; |
0d64bef9 | 155 | |
30a46ab1 | 156 | cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); |
0d64bef9 | 157 | |
ad9a921b | 158 | cl_git_pass( |
4e498646 | 159 | git_repository_set_head_detached(g_repo, git_object_id(obj))); |
ad9a921b | 160 | |
c50c58de RB |
161 | assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); |
162 | ||
0d64bef9 RB |
163 | git_object_free(obj); |
164 | ||
165 | if (!g_typechange_empty[i]) { | |
166 | cl_assert(git_path_isdir("typechanges")); | |
167 | cl_assert(git_path_exists("typechanges/a")); | |
168 | cl_assert(git_path_exists("typechanges/b")); | |
169 | cl_assert(git_path_exists("typechanges/c")); | |
170 | cl_assert(git_path_exists("typechanges/d")); | |
171 | cl_assert(git_path_exists("typechanges/e")); | |
172 | } else { | |
173 | cl_assert(git_path_isdir("typechanges")); | |
174 | cl_assert(!git_path_exists("typechanges/a")); | |
175 | cl_assert(!git_path_exists("typechanges/b")); | |
176 | cl_assert(!git_path_exists("typechanges/c")); | |
177 | cl_assert(!git_path_exists("typechanges/d")); | |
178 | cl_assert(!git_path_exists("typechanges/e")); | |
179 | } | |
180 | } | |
181 | } | |
c50c58de RB |
182 | |
183 | typedef struct { | |
184 | int conflicts; | |
185 | int dirty; | |
186 | int updates; | |
187 | int untracked; | |
188 | int ignored; | |
189 | } notify_counts; | |
190 | ||
191 | static int notify_counter( | |
192 | git_checkout_notify_t why, | |
193 | const char *path, | |
194 | const git_diff_file *baseline, | |
195 | const git_diff_file *target, | |
196 | const git_diff_file *workdir, | |
197 | void *payload) | |
198 | { | |
199 | notify_counts *cts = payload; | |
200 | ||
201 | GIT_UNUSED(path); | |
202 | GIT_UNUSED(baseline); | |
203 | GIT_UNUSED(target); | |
204 | GIT_UNUSED(workdir); | |
205 | ||
206 | switch (why) { | |
207 | case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break; | |
208 | case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break; | |
209 | case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break; | |
210 | case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break; | |
211 | case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break; | |
212 | default: break; | |
213 | } | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
218 | static void force_create_file(const char *file) | |
219 | { | |
220 | int error = git_futils_rmdir_r(file, NULL, | |
221 | GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); | |
222 | cl_assert(!error || error == GIT_ENOTFOUND); | |
223 | cl_git_pass(git_futils_mkpath2file(file, 0777)); | |
8023b83a | 224 | cl_git_rewritefile(file, "yowza!!"); |
c50c58de RB |
225 | } |
226 | ||
70681ff7 JH |
227 | static int make_submodule_dirty(git_submodule *sm, const char *name, void *payload) |
228 | { | |
229 | git_buf submodulepath = GIT_BUF_INIT; | |
230 | git_buf dirtypath = GIT_BUF_INIT; | |
231 | git_repository *submodule_repo; | |
232 | ||
bcef008f ET |
233 | GIT_UNUSED(name); |
234 | GIT_UNUSED(payload); | |
235 | ||
70681ff7 JH |
236 | /* remove submodule directory in preparation for init and repo_init */ |
237 | cl_git_pass(git_buf_joinpath( | |
238 | &submodulepath, | |
239 | git_repository_workdir(g_repo), | |
240 | git_submodule_path(sm) | |
241 | )); | |
242 | git_futils_rmdir_r(git_buf_cstr(&submodulepath), NULL, GIT_RMDIR_REMOVE_FILES); | |
243 | ||
6e0d473b | 244 | /* initialize submodule's repository */ |
70681ff7 JH |
245 | cl_git_pass(git_submodule_repo_init(&submodule_repo, sm, 0)); |
246 | ||
247 | /* create a file in the submodule workdir to make it dirty */ | |
248 | cl_git_pass( | |
249 | git_buf_joinpath(&dirtypath, git_repository_workdir(submodule_repo), "dirty")); | |
250 | force_create_file(git_buf_cstr(&dirtypath)); | |
251 | ||
ac3d33df JK |
252 | git_buf_dispose(&dirtypath); |
253 | git_buf_dispose(&submodulepath); | |
6e0d473b | 254 | git_repository_free(submodule_repo); |
70681ff7 JH |
255 | |
256 | return 0; | |
257 | } | |
258 | ||
c50c58de RB |
259 | void test_checkout_typechange__checkout_with_conflicts(void) |
260 | { | |
261 | int i; | |
262 | git_object *obj; | |
6affd71f | 263 | git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; |
c50c58de RB |
264 | notify_counts cts = {0}; |
265 | ||
266 | opts.notify_flags = | |
267 | GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED; | |
268 | opts.notify_cb = notify_counter; | |
269 | opts.notify_payload = &cts; | |
270 | ||
271 | for (i = 0; g_typechange_oids[i] != NULL; ++i) { | |
272 | cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); | |
273 | ||
274 | force_create_file("typechanges/a/blocker"); | |
275 | force_create_file("typechanges/b"); | |
276 | force_create_file("typechanges/c/sub/sub/file"); | |
277 | git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES); | |
278 | p_mkdir("typechanges/d", 0777); /* intentionally empty dir */ | |
279 | force_create_file("typechanges/untracked"); | |
70681ff7 | 280 | cl_git_pass(git_submodule_foreach(g_repo, make_submodule_dirty, NULL)); |
c50c58de | 281 | |
496b76d4 | 282 | opts.checkout_strategy = GIT_CHECKOUT_SAFE; |
c50c58de RB |
283 | memset(&cts, 0, sizeof(cts)); |
284 | ||
285 | cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); | |
70681ff7 JH |
286 | cl_assert_equal_i(cts.conflicts, g_typechange_expected_conflicts[i]); |
287 | cl_assert_equal_i(cts.untracked, g_typechange_expected_untracked[i]); | |
288 | cl_assert_equal_i(cts.dirty, 0); | |
289 | cl_assert_equal_i(cts.updates, 0); | |
290 | cl_assert_equal_i(cts.ignored, 0); | |
c50c58de RB |
291 | |
292 | opts.checkout_strategy = | |
293 | GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; | |
294 | memset(&cts, 0, sizeof(cts)); | |
295 | ||
296 | cl_assert(git_path_exists("typechanges/untracked")); | |
297 | ||
298 | cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); | |
299 | cl_assert_equal_i(0, cts.conflicts); | |
300 | ||
301 | cl_assert(!git_path_exists("typechanges/untracked")); | |
302 | ||
303 | cl_git_pass( | |
4e498646 | 304 | git_repository_set_head_detached(g_repo, git_object_id(obj))); |
c50c58de RB |
305 | |
306 | assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); | |
307 | ||
308 | git_object_free(obj); | |
309 | } | |
310 | } | |
4b3ec53c XL |
311 | |
312 | void test_checkout_typechange__status_char(void) | |
313 | { | |
314 | size_t i; | |
315 | git_oid oid; | |
316 | git_commit *commit; | |
317 | git_diff *diff; | |
318 | const git_diff_delta *delta; | |
319 | git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; | |
320 | char expected[8] = {'M', 'M', 'R', 'T', 'D', 'R', 'A', 'R'}; | |
321 | ||
322 | git_oid_fromstr(&oid, "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a"); | |
323 | cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); | |
324 | diffopts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; | |
325 | cl_git_pass(git_diff__commit(&diff, g_repo, commit, &diffopts)); | |
326 | cl_git_pass(git_diff_find_similar(diff, NULL)); | |
327 | ||
328 | for (i = 0; i < git_diff_num_deltas(diff); i++) { | |
329 | delta = git_diff_get_delta(diff, i); | |
330 | cl_assert_equal_i(expected[i], git_diff_status_char(delta->status)); | |
331 | } | |
332 | ||
333 | git_diff_free(diff); | |
334 | git_commit_free(commit); | |
335 | } |