]>
Commit | Line | Data |
---|---|---|
b60149ec ET |
1 | #include "clar_libgit2.h" |
2 | #include "git2/merge.h" | |
3 | #include "buffer.h" | |
4 | #include "merge.h" | |
5 | #include "../merge_helpers.h" | |
bb13d391 | 6 | #include "posix.h" |
b60149ec ET |
7 | |
8 | #define TEST_REPO_PATH "merge-resolve" | |
9 | #define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" | |
10 | ||
16eb8b7c ET |
11 | #define AUTOMERGEABLE_MERGED_FILE \ |
12 | "this file is changed in master\n" \ | |
13 | "this file is automergeable\n" \ | |
14 | "this file is automergeable\n" \ | |
15 | "this file is automergeable\n" \ | |
16 | "this file is automergeable\n" \ | |
17 | "this file is automergeable\n" \ | |
18 | "this file is automergeable\n" \ | |
19 | "this file is automergeable\n" \ | |
20 | "this file is changed in branch\n" | |
21 | ||
22 | #define CHANGED_IN_BRANCH_FILE \ | |
23 | "changed in branch\n" | |
24 | ||
b60149ec ET |
25 | static git_repository *repo; |
26 | static git_index *repo_index; | |
27 | ||
28 | static char *unaffected[][4] = { | |
29 | { "added-in-master.txt", NULL }, | |
30 | { "changed-in-master.txt", NULL }, | |
31 | { "unchanged.txt", NULL }, | |
32 | { "added-in-master.txt", "changed-in-master.txt", NULL }, | |
33 | { "added-in-master.txt", "unchanged.txt", NULL }, | |
34 | { "changed-in-master.txt", "unchanged.txt", NULL }, | |
35 | { "added-in-master.txt", "changed-in-master.txt", "unchanged.txt", NULL }, | |
36 | { "new_file.txt", NULL }, | |
37 | { "new_file.txt", "unchanged.txt", NULL }, | |
38 | { NULL }, | |
39 | }; | |
40 | ||
41 | static char *affected[][5] = { | |
42 | { "automergeable.txt", NULL }, | |
43 | { "changed-in-branch.txt", NULL }, | |
44 | { "conflicting.txt", NULL }, | |
45 | { "removed-in-branch.txt", NULL }, | |
46 | { "automergeable.txt", "changed-in-branch.txt", NULL }, | |
47 | { "automergeable.txt", "conflicting.txt", NULL }, | |
48 | { "automergeable.txt", "removed-in-branch.txt", NULL }, | |
49 | { "changed-in-branch.txt", "conflicting.txt", NULL }, | |
50 | { "changed-in-branch.txt", "removed-in-branch.txt", NULL }, | |
51 | { "conflicting.txt", "removed-in-branch.txt", NULL }, | |
52 | { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", NULL }, | |
53 | { "automergeable.txt", "changed-in-branch.txt", "removed-in-branch.txt", NULL }, | |
54 | { "automergeable.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, | |
55 | { "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, | |
56 | { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, | |
57 | { NULL }, | |
58 | }; | |
59 | ||
16eb8b7c ET |
60 | static char *result_contents[4][6] = { |
61 | { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, NULL, NULL }, | |
62 | { "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, | |
63 | { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, | |
64 | { NULL } | |
65 | }; | |
66 | ||
b60149ec ET |
67 | void test_merge_workdir_dirty__initialize(void) |
68 | { | |
69 | repo = cl_git_sandbox_init(TEST_REPO_PATH); | |
70 | git_repository_index(&repo_index, repo); | |
71 | } | |
72 | ||
73 | void test_merge_workdir_dirty__cleanup(void) | |
74 | { | |
75 | git_index_free(repo_index); | |
76 | cl_git_sandbox_cleanup(); | |
77 | } | |
78 | ||
79 | static void set_core_autocrlf_to(git_repository *repo, bool value) | |
80 | { | |
81 | git_config *cfg; | |
82 | ||
83 | cl_git_pass(git_repository_config(&cfg, repo)); | |
84 | cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); | |
85 | ||
86 | git_config_free(cfg); | |
87 | } | |
88 | ||
a4e2c36a | 89 | static int merge_branch(void) |
b60149ec ET |
90 | { |
91 | git_oid their_oids[1]; | |
18b00406 | 92 | git_annotated_commit *their_head; |
5aa2ac6d | 93 | git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; |
02105a27 | 94 | git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; |
b60149ec ET |
95 | int error; |
96 | ||
97 | cl_git_pass(git_oid_fromstr(&their_oids[0], MERGE_BRANCH_OID)); | |
18b00406 | 98 | cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oids[0])); |
b60149ec | 99 | |
967f5a76 | 100 | checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; |
18b00406 | 101 | error = git_merge(repo, (const git_annotated_commit **)&their_head, 1, &merge_opts, &checkout_opts); |
b60149ec | 102 | |
18b00406 | 103 | git_annotated_commit_free(their_head); |
b60149ec ET |
104 | |
105 | return error; | |
106 | } | |
107 | ||
108 | static void write_files(char *files[]) | |
109 | { | |
110 | char *filename; | |
111 | git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT; | |
112 | size_t i; | |
113 | ||
114 | for (i = 0, filename = files[i]; filename; filename = files[++i]) { | |
115 | git_buf_clear(&path); | |
116 | git_buf_clear(&content); | |
117 | ||
118 | git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename); | |
119 | git_buf_printf(&content, "This is a dirty file in the working directory!\n\n" | |
120 | "It will not be staged! Its filename is %s.\n", filename); | |
121 | ||
122 | cl_git_mkfile(path.ptr, content.ptr); | |
123 | } | |
124 | ||
125 | git_buf_free(&path); | |
126 | git_buf_free(&content); | |
127 | } | |
128 | ||
bb13d391 ET |
129 | static void hack_index(char *files[]) |
130 | { | |
131 | char *filename; | |
132 | struct stat statbuf; | |
133 | git_buf path = GIT_BUF_INIT; | |
134 | git_index_entry *entry; | |
135 | size_t i; | |
136 | ||
137 | /* Update the index to suggest that checkout placed these files on | |
138 | * disk, keeping the object id but updating the cache, which will | |
139 | * emulate a Git implementation's different filter. | |
140 | */ | |
141 | for (i = 0, filename = files[i]; filename; filename = files[++i]) { | |
142 | git_buf_clear(&path); | |
143 | ||
144 | cl_assert(entry = (git_index_entry *) | |
145 | git_index_get_bypath(repo_index, filename, 0)); | |
146 | ||
147 | cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); | |
148 | cl_git_pass(p_stat(path.ptr, &statbuf)); | |
149 | ||
150 | entry->ctime.seconds = (git_time_t)statbuf.st_ctime; | |
151 | entry->ctime.nanoseconds = 0; | |
152 | entry->mtime.seconds = (git_time_t)statbuf.st_mtime; | |
153 | entry->mtime.nanoseconds = 0; | |
154 | entry->dev = statbuf.st_dev; | |
155 | entry->ino = statbuf.st_ino; | |
156 | entry->uid = statbuf.st_uid; | |
157 | entry->gid = statbuf.st_gid; | |
158 | entry->file_size = statbuf.st_size; | |
159 | } | |
160 | ||
161 | git_buf_free(&path); | |
162 | } | |
163 | ||
b60149ec ET |
164 | static void stage_random_files(char *files[]) |
165 | { | |
166 | char *filename; | |
167 | size_t i; | |
168 | ||
169 | write_files(files); | |
170 | ||
171 | for (i = 0, filename = files[i]; filename; filename = files[++i]) | |
172 | cl_git_pass(git_index_add_bypath(repo_index, filename)); | |
173 | } | |
174 | ||
16eb8b7c ET |
175 | static void stage_content(char *content[]) |
176 | { | |
177 | git_reference *head; | |
178 | git_object *head_object; | |
16eb8b7c ET |
179 | git_buf path = GIT_BUF_INIT; |
180 | char *filename, *text; | |
181 | size_t i; | |
182 | ||
183 | cl_git_pass(git_repository_head(&head, repo)); | |
184 | cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); | |
23a17803 | 185 | cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); |
16eb8b7c ET |
186 | |
187 | for (i = 0, filename = content[i], text = content[++i]; | |
188 | filename && text; | |
189 | filename = content[++i], text = content[++i]) { | |
190 | ||
191 | git_buf_clear(&path); | |
192 | ||
193 | cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); | |
194 | ||
195 | cl_git_mkfile(path.ptr, text); | |
196 | cl_git_pass(git_index_add_bypath(repo_index, filename)); | |
197 | } | |
198 | ||
16eb8b7c ET |
199 | git_object_free(head_object); |
200 | git_reference_free(head); | |
201 | git_buf_free(&path); | |
202 | } | |
203 | ||
b60149ec ET |
204 | static int merge_dirty_files(char *dirty_files[]) |
205 | { | |
206 | git_reference *head; | |
207 | git_object *head_object; | |
b60149ec ET |
208 | int error; |
209 | ||
210 | cl_git_pass(git_repository_head(&head, repo)); | |
211 | cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); | |
23a17803 | 212 | cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); |
b60149ec ET |
213 | |
214 | write_files(dirty_files); | |
215 | ||
a4e2c36a | 216 | error = merge_branch(); |
b60149ec | 217 | |
b60149ec ET |
218 | git_object_free(head_object); |
219 | git_reference_free(head); | |
220 | ||
221 | return error; | |
222 | } | |
223 | ||
bb13d391 ET |
224 | static int merge_differently_filtered_files(char *files[]) |
225 | { | |
226 | git_reference *head; | |
227 | git_object *head_object; | |
bb13d391 ET |
228 | int error; |
229 | ||
230 | cl_git_pass(git_repository_head(&head, repo)); | |
231 | cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); | |
23a17803 | 232 | cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); |
bb13d391 ET |
233 | |
234 | write_files(files); | |
235 | hack_index(files); | |
236 | ||
237 | cl_git_pass(git_index_write(repo_index)); | |
238 | ||
a4e2c36a | 239 | error = merge_branch(); |
bb13d391 | 240 | |
bb13d391 ET |
241 | git_object_free(head_object); |
242 | git_reference_free(head); | |
243 | ||
244 | return error; | |
245 | } | |
246 | ||
b60149ec | 247 | static int merge_staged_files(char *staged_files[]) |
d9fdee6e | 248 | { |
b60149ec | 249 | stage_random_files(staged_files); |
a4e2c36a | 250 | return merge_branch(); |
b60149ec ET |
251 | } |
252 | ||
253 | void test_merge_workdir_dirty__unaffected_dirty_files_allowed(void) | |
254 | { | |
255 | char **files; | |
256 | size_t i; | |
257 | ||
258 | for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) | |
259 | cl_git_pass(merge_dirty_files(files)); | |
260 | } | |
261 | ||
26564d80 ET |
262 | void test_merge_workdir_dirty__unstaged_deletes_maintained(void) |
263 | { | |
264 | git_reference *head; | |
265 | git_object *head_object; | |
266 | ||
267 | cl_git_pass(git_repository_head(&head, repo)); | |
268 | cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); | |
23a17803 | 269 | cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); |
26564d80 ET |
270 | |
271 | cl_git_pass(p_unlink("merge-resolve/unchanged.txt")); | |
272 | ||
a4e2c36a | 273 | cl_git_pass(merge_branch()); |
26564d80 ET |
274 | |
275 | git_object_free(head_object); | |
276 | git_reference_free(head); | |
277 | } | |
278 | ||
b60149ec ET |
279 | void test_merge_workdir_dirty__affected_dirty_files_disallowed(void) |
280 | { | |
281 | char **files; | |
282 | size_t i; | |
283 | ||
284 | for (i = 0, files = affected[i]; files[0]; files = affected[++i]) | |
285 | cl_git_fail(merge_dirty_files(files)); | |
286 | } | |
287 | ||
288 | void test_merge_workdir_dirty__staged_files_in_index_disallowed(void) | |
289 | { | |
290 | char **files; | |
291 | size_t i; | |
292 | ||
293 | for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) | |
294 | cl_git_fail(merge_staged_files(files)); | |
295 | ||
296 | for (i = 0, files = affected[i]; files[0]; files = affected[++i]) | |
297 | cl_git_fail(merge_staged_files(files)); | |
298 | } | |
16eb8b7c ET |
299 | |
300 | void test_merge_workdir_dirty__identical_staged_files_allowed(void) | |
301 | { | |
16eb8b7c ET |
302 | char **content; |
303 | size_t i; | |
304 | ||
305 | set_core_autocrlf_to(repo, false); | |
306 | ||
307 | for (i = 0, content = result_contents[i]; content[0]; content = result_contents[++i]) { | |
308 | stage_content(content); | |
309 | ||
310 | git_index_write(repo_index); | |
a4e2c36a | 311 | cl_git_pass(merge_branch()); |
16eb8b7c ET |
312 | } |
313 | } | |
bb13d391 ET |
314 | |
315 | void test_merge_workdir_dirty__honors_cache(void) | |
316 | { | |
317 | char **files; | |
318 | size_t i; | |
319 | ||
320 | for (i = 0, files = affected[i]; files[0]; files = affected[++i]) | |
321 | cl_git_pass(merge_differently_filtered_files(files)); | |
322 | } |