1 #include "clar_libgit2.h"
2 #include "patch.h"
3 #include "patch_parse.h"
4 #include "diff_helpers.h"
5 
6 #include "../patch/patch_common.h"
7 
test_diff_parse__cleanup(void)8 void test_diff_parse__cleanup(void)
9 {
10 	cl_git_sandbox_cleanup();
11 }
12 
test_diff_parse__nonpatches_fail_with_notfound(void)13 void test_diff_parse__nonpatches_fail_with_notfound(void)
14 {
15 	git_diff *diff;
16 	const char *not = PATCH_NOT_A_PATCH;
17 	const char *not_with_leading = "Leading text.\n" PATCH_NOT_A_PATCH;
18 	const char *not_with_trailing = PATCH_NOT_A_PATCH "Trailing text.\n";
19 	const char *not_with_both = "Lead.\n" PATCH_NOT_A_PATCH "Trail.\n";
20 
21 	cl_git_fail_with(GIT_ENOTFOUND,
22 		git_diff_from_buffer(&diff,
23 		not,
24 		strlen(not)));
25 	cl_git_fail_with(GIT_ENOTFOUND,
26 		git_diff_from_buffer(&diff,
27 		not_with_leading,
28 		strlen(not_with_leading)));
29 	cl_git_fail_with(GIT_ENOTFOUND,
30 		git_diff_from_buffer(&diff,
31 		not_with_trailing,
32 		strlen(not_with_trailing)));
33 	cl_git_fail_with(GIT_ENOTFOUND,
34 		git_diff_from_buffer(&diff,
35 		not_with_both,
36 		strlen(not_with_both)));
37 }
38 
test_parse_invalid_diff(const char * invalid_diff)39 static void test_parse_invalid_diff(const char *invalid_diff)
40 {
41 	git_diff *diff;
42 	git_buf buf = GIT_BUF_INIT;
43 
44 	/* throw some random (legitimate) diffs in with the given invalid
45 	 * one.
46 	 */
47 	git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE);
48 	git_buf_puts(&buf, PATCH_BINARY_DELTA);
49 	git_buf_puts(&buf, invalid_diff);
50 	git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_MIDDLE);
51 	git_buf_puts(&buf, PATCH_BINARY_LITERAL);
52 
53 	cl_git_fail_with(GIT_ERROR,
54 		git_diff_from_buffer(&diff, buf.ptr, buf.size));
55 
56 	git_buf_dispose(&buf);
57 }
58 
test_diff_parse__exact_rename(void)59 void test_diff_parse__exact_rename(void)
60 {
61 	const char *content =
62 	    "---\n"
63 	    " old_name.c => new_name.c | 0\n"
64 	    " 1 file changed, 0 insertions(+), 0 deletions(-)\n"
65 	    " rename old_name.c => new_name.c  (100%)\n"
66 	    "\n"
67 	    "diff --git a/old_name.c b/new_name.c\n"
68 	    "similarity index 100%\n"
69 	    "rename from old_name.c\n"
70 	    "rename to new_name.c\n"
71 	    "-- \n"
72 	    "2.9.3\n";
73 	git_diff *diff;
74 
75 	cl_git_pass(git_diff_from_buffer(
76 		&diff, content, strlen(content)));
77 	git_diff_free(diff);
78 }
79 
test_diff_parse__empty_file(void)80 void test_diff_parse__empty_file(void)
81 {
82 	const char *content =
83 	    "---\n"
84 	    " file | 0\n"
85 	    " 1 file changed, 0 insertions(+), 0 deletions(-)\n"
86 	    " created mode 100644 file\n"
87 	    "\n"
88 	    "diff --git a/file b/file\n"
89 	    "new file mode 100644\n"
90 	    "index 0000000..e69de29\n"
91 	    "-- \n"
92 	    "2.20.1\n";
93 	git_diff *diff;
94 
95 	cl_git_pass(git_diff_from_buffer(
96 		&diff, content, strlen(content)));
97 	git_diff_free(diff);
98 }
99 
test_diff_parse__no_extended_headers(void)100 void test_diff_parse__no_extended_headers(void)
101 {
102 	const char *content = PATCH_NO_EXTENDED_HEADERS;
103 	git_diff *diff;
104 
105 	cl_git_pass(git_diff_from_buffer(
106 		&diff, content, strlen(content)));
107 	git_diff_free(diff);
108 }
109 
test_diff_parse__add_delete_no_index(void)110 void test_diff_parse__add_delete_no_index(void)
111 {
112 	const char *content =
113 	    "diff --git a/file.txt b/file.txt\n"
114 	    "new file mode 100644\n"
115 	    "--- /dev/null\n"
116 	    "+++ b/file.txt\n"
117 	    "@@ -0,0 +1,2 @@\n"
118 	    "+one\n"
119 	    "+two\n"
120 	    "diff --git a/otherfile.txt b/otherfile.txt\n"
121 	    "deleted file mode 100644\n"
122 	    "--- a/otherfile.txt\n"
123 	    "+++ /dev/null\n"
124 	    "@@ -1,1 +0,0 @@\n"
125 	    "-three\n";
126 	git_diff *diff;
127 
128 	cl_git_pass(git_diff_from_buffer(
129 		&diff, content, strlen(content)));
130 	git_diff_free(diff);
131 }
132 
test_diff_parse__invalid_patches_fails(void)133 void test_diff_parse__invalid_patches_fails(void)
134 {
135 	test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE);
136 	test_parse_invalid_diff(PATCH_CORRUPT_MISSING_OLD_FILE);
137 	test_parse_invalid_diff(PATCH_CORRUPT_NO_CHANGES);
138 	test_parse_invalid_diff(PATCH_CORRUPT_MISSING_HUNK_HEADER);
139 }
140 
test_tree_to_tree_computed_to_parsed(const char * sandbox,const char * a_id,const char * b_id,uint32_t diff_flags,uint32_t find_flags)141 static void test_tree_to_tree_computed_to_parsed(
142 	const char *sandbox, const char *a_id, const char *b_id,
143 	uint32_t diff_flags, uint32_t find_flags)
144 {
145 	git_repository *repo;
146 	git_diff *computed, *parsed;
147 	git_tree *a, *b;
148 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
149 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
150 	git_buf computed_buf = GIT_BUF_INIT;
151 
152 	repo = cl_git_sandbox_init(sandbox);
153 
154 	opts.id_abbrev = GIT_OID_HEXSZ;
155 	opts.flags = GIT_DIFF_SHOW_BINARY | diff_flags;
156 	findopts.flags = find_flags;
157 
158 	cl_assert((a = resolve_commit_oid_to_tree(repo, a_id)) != NULL);
159 	cl_assert((b = resolve_commit_oid_to_tree(repo, b_id)) != NULL);
160 
161 	cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts));
162 
163 	if (find_flags)
164 		cl_git_pass(git_diff_find_similar(computed, &findopts));
165 
166 	cl_git_pass(git_diff_to_buf(&computed_buf,
167 		computed, GIT_DIFF_FORMAT_PATCH));
168 
169 	cl_git_pass(git_diff_from_buffer(&parsed,
170 		computed_buf.ptr, computed_buf.size));
171 
172 	diff_assert_equal(computed, parsed);
173 
174 	git_tree_free(a);
175 	git_tree_free(b);
176 
177 	git_diff_free(computed);
178 	git_diff_free(parsed);
179 
180 	git_buf_dispose(&computed_buf);
181 
182 	cl_git_sandbox_cleanup();
183 }
184 
test_diff_parse__can_parse_generated_diff(void)185 void test_diff_parse__can_parse_generated_diff(void)
186 {
187 	test_tree_to_tree_computed_to_parsed(
188 		"diff", "d70d245e", "7a9e0b02", 0, 0);
189 	test_tree_to_tree_computed_to_parsed(
190 		"unsymlinked.git", "806999", "a8595c", 0, 0);
191 	test_tree_to_tree_computed_to_parsed("diff",
192 		"d70d245ed97ed2aa596dd1af6536e4bfdb047b69",
193 		"7a9e0b02e63179929fed24f0a3e0f19168114d10", 0, 0);
194 	test_tree_to_tree_computed_to_parsed(
195 		"unsymlinked.git", "7fccd7", "806999", 0, 0);
196 	test_tree_to_tree_computed_to_parsed(
197 		"unsymlinked.git", "7fccd7", "a8595c", 0, 0);
198 	test_tree_to_tree_computed_to_parsed(
199 		"attr", "605812a", "370fe9ec22", 0, 0);
200 	test_tree_to_tree_computed_to_parsed(
201 		"attr", "f5b0af1fb4f5c", "370fe9ec22", 0, 0);
202 	test_tree_to_tree_computed_to_parsed(
203 		"diff", "d70d245e", "d70d245e", 0, 0);
204 	test_tree_to_tree_computed_to_parsed("diff_format_email",
205 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
206 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
207 		GIT_DIFF_SHOW_BINARY, 0);
208 	test_tree_to_tree_computed_to_parsed("diff_format_email",
209 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
210 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
211 		GIT_DIFF_SHOW_BINARY, 0);
212 	test_tree_to_tree_computed_to_parsed("renames",
213 		"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
214 		"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
215 		0, GIT_DIFF_FIND_RENAMES);
216 	test_tree_to_tree_computed_to_parsed("renames",
217 		"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
218 		"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
219 		GIT_DIFF_INCLUDE_UNMODIFIED,
220 		0);
221 	test_tree_to_tree_computed_to_parsed("renames",
222 		"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
223 		"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
224 		GIT_DIFF_INCLUDE_UNMODIFIED,
225 		GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | GIT_DIFF_FIND_EXACT_MATCH_ONLY);
226 }
227 
test_diff_parse__get_patch_from_diff(void)228 void test_diff_parse__get_patch_from_diff(void)
229 {
230 	git_repository *repo;
231 	git_diff *computed, *parsed;
232 	git_tree *a, *b;
233 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
234 	git_buf computed_buf = GIT_BUF_INIT;
235 	git_patch *patch_computed, *patch_parsed;
236 
237 	repo = cl_git_sandbox_init("diff");
238 
239 	opts.flags = GIT_DIFF_SHOW_BINARY;
240 
241 	cl_assert((a = resolve_commit_oid_to_tree(repo,
242 		"d70d245ed97ed2aa596dd1af6536e4bfdb047b69")) != NULL);
243 	cl_assert((b = resolve_commit_oid_to_tree(repo,
244 		"7a9e0b02e63179929fed24f0a3e0f19168114d10")) != NULL);
245 
246 	cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts));
247 	cl_git_pass(git_diff_to_buf(&computed_buf,
248 		computed, GIT_DIFF_FORMAT_PATCH));
249 	cl_git_pass(git_patch_from_diff(&patch_computed, computed, 0));
250 
251 	cl_git_pass(git_diff_from_buffer(&parsed,
252 		computed_buf.ptr, computed_buf.size));
253 	cl_git_pass(git_patch_from_diff(&patch_parsed, parsed, 0));
254 
255 	cl_assert_equal_i(
256 		git_patch_num_hunks(patch_computed),
257 		git_patch_num_hunks(patch_parsed));
258 
259 	git_patch_free(patch_computed);
260 	git_patch_free(patch_parsed);
261 
262 	git_tree_free(a);
263 	git_tree_free(b);
264 
265 	git_diff_free(computed);
266 	git_diff_free(parsed);
267 
268 	git_buf_dispose(&computed_buf);
269 
270 	cl_git_sandbox_cleanup();
271 }
272 
file_cb(const git_diff_delta * delta,float progress,void * payload)273 static int file_cb(const git_diff_delta *delta, float progress, void *payload)
274 {
275     int *called = (int *) payload;
276     GIT_UNUSED(delta);
277     GIT_UNUSED(progress);
278     (*called)++;
279     return 0;
280 }
281 
test_diff_parse__foreach_works_with_parsed_patch(void)282 void test_diff_parse__foreach_works_with_parsed_patch(void)
283 {
284 	const char patch[] =
285 	    "diff --git a/obj1 b/obj2\n"
286 	    "index 1234567..7654321 10644\n"
287 	    "--- a/obj1\n"
288 	    "+++ b/obj2\n"
289 	    "@@ -1 +1 @@\n"
290 	    "-abcde\n"
291 	    "+12345\n";
292 	int called = 0;
293 	git_diff *diff;
294 
295 	cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch)));
296 	cl_git_pass(git_diff_foreach(diff, file_cb, NULL, NULL, NULL, &called));
297 	cl_assert_equal_i(called, 1);
298 
299 	git_diff_free(diff);
300 }
301 
test_diff_parse__parsing_minimal_patch_succeeds(void)302 void test_diff_parse__parsing_minimal_patch_succeeds(void)
303 {
304 	const char patch[] =
305 	    "diff --git a/obj1 b/obj2\n"
306 	    "index 1234567..7654321 10644\n"
307 	    "--- a/obj1\n"
308 	    "+++ b/obj2\n"
309 	    "@@ -1 +1 @@\n"
310 	    "-a\n"
311 	    "+\n";
312 	git_buf buf = GIT_BUF_INIT;
313 	git_diff *diff;
314 
315 	cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch)));
316 	cl_git_pass(git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH));
317 	cl_assert_equal_s(patch, buf.ptr);
318 
319 	git_diff_free(diff);
320 	git_buf_dispose(&buf);
321 }
322 
test_diff_parse__patch_roundtrip_succeeds(void)323 void test_diff_parse__patch_roundtrip_succeeds(void)
324 {
325 	const char buf1[] = "a\n", buf2[] = "b\n";
326 	git_buf patchbuf = GIT_BUF_INIT, diffbuf = GIT_BUF_INIT;
327 	git_patch *patch;
328 	git_diff *diff;
329 
330 	cl_git_pass(git_patch_from_buffers(&patch, buf1, strlen(buf1), "obj1", buf2, strlen(buf2), "obj2", NULL));
331 	cl_git_pass(git_patch_to_buf(&patchbuf, patch));
332 
333 	cl_git_pass(git_diff_from_buffer(&diff, patchbuf.ptr, patchbuf.size));
334 	cl_git_pass(git_diff_to_buf(&diffbuf, diff, GIT_DIFF_FORMAT_PATCH));
335 
336 	cl_assert_equal_s(patchbuf.ptr, diffbuf.ptr);
337 
338 	git_patch_free(patch);
339 	git_diff_free(diff);
340 	git_buf_dispose(&patchbuf);
341 	git_buf_dispose(&diffbuf);
342 }
343 
344 #define cl_assert_equal_i_src(i1,i2,file,func,line) clar__assert_equal(file,func,line,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
345 
cl_git_assert_lineinfo_(int old_lineno,int new_lineno,int num_lines,git_patch * patch,size_t hunk_idx,size_t line_idx,const char * file,const char * func,int lineno)346 static void cl_git_assert_lineinfo_(int old_lineno, int new_lineno, int num_lines, git_patch *patch, size_t hunk_idx, size_t line_idx, const char *file, const char *func, int lineno)
347 {
348 	const git_diff_line *line;
349 
350 	cl_git_expect(git_patch_get_line_in_hunk(&line, patch, hunk_idx, line_idx), 0, file, func, lineno);
351 	cl_assert_equal_i_src(old_lineno, line->old_lineno, file, func, lineno);
352 	cl_assert_equal_i_src(new_lineno, line->new_lineno, file, func, lineno);
353 	cl_assert_equal_i_src(num_lines, line->num_lines, file, func, lineno);
354 }
355 
356 #define cl_git_assert_lineinfo(old, new, num, p, h, l) \
357 	cl_git_assert_lineinfo_(old,new,num,p,h,l,__FILE__,__func__,__LINE__)
358 
359 
test_diff_parse__issue4672(void)360 void test_diff_parse__issue4672(void)
361 {
362 	const char *text = "diff --git a/a b/a\n"
363 	"index 7f129fd..af431f2 100644\n"
364 	"--- a/a\n"
365 	"+++ b/a\n"
366 	"@@ -3 +3 @@\n"
367 	"-a contents 2\n"
368 	"+a contents\n";
369 
370 	git_diff *diff;
371 	git_patch *patch;
372 	const git_diff_hunk *hunk;
373 	size_t n, l = 0;
374 
375 	cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text)));
376 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
377 	cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0));
378 
379 	cl_git_assert_lineinfo(3, -1, 1, patch, 0, l++);
380 	cl_git_assert_lineinfo(-1, 3, 1, patch, 0, l++);
381 
382 	cl_assert_equal_i(n, l);
383 
384 	git_patch_free(patch);
385 	git_diff_free(diff);
386 }
387 
test_diff_parse__lineinfo(void)388 void test_diff_parse__lineinfo(void)
389 {
390 	const char *text = PATCH_ORIGINAL_TO_CHANGE_MIDDLE;
391 	git_diff *diff;
392 	git_patch *patch;
393 	const git_diff_hunk *hunk;
394 	size_t n, l = 0;
395 
396 	cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text)));
397 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
398 	cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0));
399 
400 	cl_git_assert_lineinfo(3, 3, 1, patch, 0, l++);
401 	cl_git_assert_lineinfo(4, 4, 1, patch, 0, l++);
402 	cl_git_assert_lineinfo(5, 5, 1, patch, 0, l++);
403 	cl_git_assert_lineinfo(6, -1, 1, patch, 0, l++);
404 	cl_git_assert_lineinfo(-1, 6, 1, patch, 0, l++);
405 	cl_git_assert_lineinfo(7, 7, 1, patch, 0, l++);
406 	cl_git_assert_lineinfo(8, 8, 1, patch, 0, l++);
407 	cl_git_assert_lineinfo(9, 9, 1, patch, 0, l++);
408 
409 	cl_assert_equal_i(n, l);
410 
411 	git_patch_free(patch);
412 	git_diff_free(diff);
413 }
414 
415 
test_diff_parse__new_file_with_space(void)416 void test_diff_parse__new_file_with_space(void)
417 {
418 	const char *content = PATCH_ORIGINAL_NEW_FILE_WITH_SPACE;
419 	git_patch *patch;
420 	git_diff *diff;
421 
422 	cl_git_pass(git_diff_from_buffer(&diff, content, strlen(content)));
423 	cl_git_pass(git_patch_from_diff((git_patch **) &patch, diff, 0));
424 
425 	cl_assert_equal_p(patch->diff_opts.old_prefix, NULL);
426 	cl_assert_equal_p(patch->delta->old_file.path, NULL);
427 	cl_assert_equal_s(patch->diff_opts.new_prefix, "b/");
428 	cl_assert_equal_s(patch->delta->new_file.path, "sp ace.txt");
429 
430 	git_patch_free(patch);
431 	git_diff_free(diff);
432 }
433 
test_diff_parse__crlf(void)434 void test_diff_parse__crlf(void)
435 {
436 	const char *text = PATCH_CRLF;
437 	git_diff *diff;
438 	git_patch *patch;
439 	const git_diff_delta *delta;
440 
441 	cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text)));
442 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
443 	delta = git_patch_get_delta(patch);
444 
445 	cl_assert_equal_s(delta->old_file.path, "test-file");
446 	cl_assert_equal_s(delta->new_file.path, "test-file");
447 
448 	git_patch_free(patch);
449 	git_diff_free(diff);
450 }
451