1 #include "clar_libgit2.h"
2 
3 #include "git2/sys/diff.h"
4 
5 #include "buffer.h"
6 #include "delta.h"
7 #include "filebuf.h"
8 #include "repository.h"
9 
10 static git_repository *repo;
11 
test_diff_binary__initialize(void)12 void test_diff_binary__initialize(void)
13 {
14 }
15 
test_diff_binary__cleanup(void)16 void test_diff_binary__cleanup(void)
17 {
18 	cl_git_sandbox_cleanup();
19 }
20 
test_patch(const char * one,const char * two,const git_diff_options * opts,const char * expected)21 void test_patch(
22 	const char *one,
23 	const char *two,
24 	const git_diff_options *opts,
25 	const char *expected)
26 {
27 	git_oid id_one, id_two;
28 	git_index *index = NULL;
29 	git_commit *commit_one, *commit_two = NULL;
30 	git_tree *tree_one, *tree_two;
31 	git_diff *diff;
32 	git_patch *patch;
33 	git_buf actual = GIT_BUF_INIT;
34 
35 	cl_git_pass(git_oid_fromstr(&id_one, one));
36 	cl_git_pass(git_commit_lookup(&commit_one, repo, &id_one));
37 	cl_git_pass(git_commit_tree(&tree_one, commit_one));
38 
39 	if (two) {
40 		cl_git_pass(git_oid_fromstr(&id_two, two));
41 		cl_git_pass(git_commit_lookup(&commit_two, repo, &id_two));
42 		cl_git_pass(git_commit_tree(&tree_two, commit_two));
43 	} else {
44 		cl_git_pass(git_repository_index(&index, repo));
45 		cl_git_pass(git_index_write_tree(&id_two, index));
46 		cl_git_pass(git_tree_lookup(&tree_two, repo, &id_two));
47 	}
48 
49 	cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree_one, tree_two, opts));
50 
51 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
52 	cl_git_pass(git_patch_to_buf(&actual, patch));
53 
54 	cl_assert_equal_s(expected, actual.ptr);
55 
56 	git_buf_clear(&actual);
57 	cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual));
58 
59 	cl_assert_equal_s(expected, actual.ptr);
60 
61 	git_buf_dispose(&actual);
62 	git_patch_free(patch);
63 	git_diff_free(diff);
64 	git_tree_free(tree_one);
65 	git_tree_free(tree_two);
66 	git_commit_free(commit_one);
67 	git_commit_free(commit_two);
68 	git_index_free(index);
69 }
70 
test_diff_binary__add_normal(void)71 void test_diff_binary__add_normal(void)
72 {
73 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
74 	const char *expected =
75 		"diff --git a/binary.bin b/binary.bin\n" \
76 		"new file mode 100644\n" \
77 		"index 0000000..bd474b2\n" \
78 		"Binary files /dev/null and b/binary.bin differ\n";
79 
80 	repo = cl_git_sandbox_init("diff_format_email");
81 	test_patch(
82 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
83 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
84 		&opts,
85 		expected);
86 }
87 
test_diff_binary__add(void)88 void test_diff_binary__add(void)
89 {
90 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
91 	const char *expected =
92 		"diff --git a/binary.bin b/binary.bin\n" \
93 		"new file mode 100644\n" \
94 		"index 0000000000000000000000000000000000000000..bd474b2519cc15eab801ff851cc7d50f0dee49a1\n" \
95 		"GIT binary patch\n" \
96 		"literal 3\n" \
97 		"Kc${Nk-~s>u4FC%O\n"
98 		"\n" \
99 		"literal 0\n" \
100 		"Hc$@<O00001\n" \
101 		"\n";
102 
103 	opts.flags = GIT_DIFF_SHOW_BINARY;
104 	opts.id_abbrev = GIT_OID_HEXSZ;
105 
106 	repo = cl_git_sandbox_init("diff_format_email");
107 	test_patch(
108 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
109 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
110 		&opts,
111 		expected);
112 }
113 
test_diff_binary__modify_normal(void)114 void test_diff_binary__modify_normal(void)
115 {
116 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
117 	const char *expected =
118 		"diff --git a/binary.bin b/binary.bin\n" \
119 		"index bd474b2..9ac35ff 100644\n" \
120 		"Binary files a/binary.bin and b/binary.bin differ\n";
121 
122 	repo = cl_git_sandbox_init("diff_format_email");
123 	test_patch(
124 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
125 		"8d7523f6fcb2404257889abe0d96f093d9f524f9",
126 		&opts,
127 		expected);
128 }
129 
test_diff_binary__modify(void)130 void test_diff_binary__modify(void)
131 {
132 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
133 	const char *expected =
134 		"diff --git a/binary.bin b/binary.bin\n" \
135 		"index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \
136 		"GIT binary patch\n" \
137 		"literal 5\n" \
138 		"Mc${NkU}WL~000&M4gdfE\n" \
139 		"\n" \
140 		"literal 3\n" \
141 		"Kc${Nk-~s>u4FC%O\n" \
142 		"\n";
143 
144 	opts.flags = GIT_DIFF_SHOW_BINARY;
145 
146 	repo = cl_git_sandbox_init("diff_format_email");
147 	test_patch(
148 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
149 		"8d7523f6fcb2404257889abe0d96f093d9f524f9",
150 		&opts,
151 		expected);
152 }
153 
test_diff_binary__delete_normal(void)154 void test_diff_binary__delete_normal(void)
155 {
156 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
157 	const char *expected =
158 		"diff --git a/binary.bin b/binary.bin\n" \
159 		"deleted file mode 100644\n" \
160 		"index bd474b2..0000000\n" \
161 		"Binary files a/binary.bin and /dev/null differ\n";
162 
163 	repo = cl_git_sandbox_init("diff_format_email");
164 	test_patch(
165 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
166 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
167 		&opts,
168 		expected);
169 }
170 
test_diff_binary__delete(void)171 void test_diff_binary__delete(void)
172 {
173 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
174 	const char *expected =
175 		"diff --git a/binary.bin b/binary.bin\n" \
176 		"deleted file mode 100644\n" \
177 		"index bd474b2519cc15eab801ff851cc7d50f0dee49a1..0000000000000000000000000000000000000000\n" \
178 		"GIT binary patch\n" \
179 		"literal 0\n" \
180 		"Hc$@<O00001\n" \
181 		"\n" \
182 		"literal 3\n" \
183 		"Kc${Nk-~s>u4FC%O\n" \
184 		"\n";
185 
186 	opts.flags = GIT_DIFF_SHOW_BINARY;
187 	opts.id_abbrev = GIT_OID_HEXSZ;
188 
189 	repo = cl_git_sandbox_init("diff_format_email");
190 	test_patch(
191 		"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
192 		"873806f6f27e631eb0b23e4b56bea2bfac14a373",
193 		&opts,
194 		expected);
195 }
196 
test_diff_binary__delta(void)197 void test_diff_binary__delta(void)
198 {
199 	git_index *index;
200 	git_buf contents = GIT_BUF_INIT;
201 	size_t i;
202 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
203 	const char *expected =
204 		"diff --git a/songof7cities.txt b/songof7cities.txt\n" \
205 		"index 4210ffd5c390b21dd5483375e75288dea9ede512..cc84ec183351c9944ed90a619ca08911924055b5 100644\n" \
206 		"GIT binary patch\n" \
207 		"delta 198\n" \
208 		"zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pa)Ye#M3o+qJ$<Jl;sX*mF<MGCYv&*L7AHu\n" \
209 		"zGA1*^gt?gYVN82wTbPO_W)+x<&1+cP;HrPHR>PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \
210 		"JfH567LIG)KJdFSV\n" \
211 		"\n" \
212 		"delta 198\n" \
213 		"zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$<Jl;sX*mF<MGCYv&*L7AHu\n" \
214 		"zGA1*^gt?gYVN82wTbPO_W)+x<&1+cP;HrPHR>PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \
215 		"JfH567LIF3FM2!Fd\n" \
216 		"\n";
217 
218 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
219 	opts.id_abbrev = GIT_OID_HEXSZ;
220 
221 	repo = cl_git_sandbox_init("renames");
222 	cl_git_pass(git_repository_index(&index, repo));
223 
224 	cl_git_pass(git_futils_readbuffer(&contents, "renames/songof7cities.txt"));
225 
226 	for (i = 0; i < contents.size - 6; i++) {
227 		if (strncmp(&contents.ptr[i], "Cities", 6) == 0)
228 			memcpy(&contents.ptr[i], "cITIES", 6);
229 	}
230 
231 	cl_git_rewritefile("renames/songof7cities.txt", contents.ptr);
232 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
233 	cl_git_pass(git_index_write(index));
234 
235 	test_patch(
236 		"19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13",
237 		NULL,
238 		&opts,
239 		expected);
240 
241 	git_index_free(index);
242 	git_buf_dispose(&contents);
243 }
244 
test_diff_binary__delta_append(void)245 void test_diff_binary__delta_append(void)
246 {
247 	git_index *index;
248 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
249 	const char *expected =
250 		"diff --git a/untimely.txt b/untimely.txt\n" \
251 		"index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \
252 		"GIT binary patch\n" \
253 		"delta 32\n" \
254 		"nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \
255 		"\n" \
256 		"delta 7\n" \
257 		"Oc%18D`@*{63ljhg(E~C7\n" \
258 		"\n";
259 
260 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
261 	opts.id_abbrev = GIT_OID_HEXSZ;
262 
263 	repo = cl_git_sandbox_init("renames");
264 	cl_git_pass(git_repository_index(&index, repo));
265 
266 	cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n");
267 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
268 	cl_git_pass(git_index_write(index));
269 
270 	test_patch(
271 		"19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13",
272 		NULL,
273 		&opts,
274 		expected);
275 
276 	git_index_free(index);
277 }
278 
test_diff_binary__empty_for_no_diff(void)279 void test_diff_binary__empty_for_no_diff(void)
280 {
281 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
282 	git_oid id;
283 	git_commit *commit;
284 	git_tree *tree;
285 	git_diff *diff;
286 	git_buf actual = GIT_BUF_INIT;
287 
288 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
289 	opts.id_abbrev = GIT_OID_HEXSZ;
290 
291 	repo = cl_git_sandbox_init("renames");
292 
293 	cl_git_pass(git_oid_fromstr(&id, "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"));
294 	cl_git_pass(git_commit_lookup(&commit, repo, &id));
295 	cl_git_pass(git_commit_tree(&tree, commit));
296 
297 	cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, &opts));
298 	cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual));
299 
300 	cl_assert_equal_s("", actual.ptr);
301 
302 	git_buf_dispose(&actual);
303 	git_diff_free(diff);
304 	git_commit_free(commit);
305 	git_tree_free(tree);
306 }
307 
test_diff_binary__index_to_workdir(void)308 void test_diff_binary__index_to_workdir(void)
309 {
310 	git_index *index;
311 	git_diff *diff;
312 	git_patch *patch;
313 	git_buf actual = GIT_BUF_INIT;
314 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
315 	const char *expected =
316 		"diff --git a/untimely.txt b/untimely.txt\n" \
317 		"index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \
318 		"GIT binary patch\n" \
319 		"delta 32\n" \
320 		"nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \
321 		"\n" \
322 		"delta 7\n" \
323 		"Oc%18D`@*{63ljhg(E~C7\n" \
324 		"\n";
325 
326 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
327 	opts.id_abbrev = GIT_OID_HEXSZ;
328 
329 	repo = cl_git_sandbox_init("renames");
330 	cl_git_pass(git_repository_index(&index, repo));
331 
332 	cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n");
333 
334 	cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts));
335 
336 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
337 	cl_git_pass(git_patch_to_buf(&actual, patch));
338 
339 	cl_assert_equal_s(expected, actual.ptr);
340 
341 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
342 	cl_git_pass(git_index_write(index));
343 
344 	test_patch(
345 		"19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13",
346 		NULL,
347 		&opts,
348 		expected);
349 
350 	git_buf_dispose(&actual);
351 	git_patch_free(patch);
352 	git_diff_free(diff);
353 	git_index_free(index);
354 }
355 
print_cb(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)356 static int print_cb(
357 	const git_diff_delta *delta,
358 	const git_diff_hunk *hunk,
359 	const git_diff_line *line,
360 	void *payload)
361 {
362 	git_buf *buf = (git_buf *)payload;
363 
364 	GIT_UNUSED(delta);
365 
366 	if (hunk)
367 		git_buf_put(buf, hunk->header, hunk->header_len);
368 
369 	if (line)
370 		git_buf_put(buf, line->content, line->content_len);
371 
372 	return git_buf_oom(buf) ? -1 : 0;
373 }
374 
test_diff_binary__print_patch_from_diff(void)375 void test_diff_binary__print_patch_from_diff(void)
376 {
377 	git_index *index;
378 	git_diff *diff;
379 	git_buf actual = GIT_BUF_INIT;
380 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
381 	const char *expected =
382 		"diff --git a/untimely.txt b/untimely.txt\n" \
383 		"index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \
384 		"GIT binary patch\n" \
385 		"delta 32\n" \
386 		"nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \
387 		"\n" \
388 		"delta 7\n" \
389 		"Oc%18D`@*{63ljhg(E~C7\n" \
390 		"\n";
391 
392 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
393 	opts.id_abbrev = GIT_OID_HEXSZ;
394 
395 	repo = cl_git_sandbox_init("renames");
396 	cl_git_pass(git_repository_index(&index, repo));
397 
398 	cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n");
399 
400 	cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts));
401 
402 	cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, print_cb, &actual));
403 
404 	cl_assert_equal_s(expected, actual.ptr);
405 
406 	git_buf_dispose(&actual);
407 	git_diff_free(diff);
408 	git_index_free(index);
409 }
410 
411 struct diff_data {
412 	char *old_path;
413 	git_oid old_id;
414 	git_buf old_binary_base85;
415 	size_t old_binary_inflatedlen;
416 	git_diff_binary_t old_binary_type;
417 
418 	char *new_path;
419 	git_oid new_id;
420 	git_buf new_binary_base85;
421 	size_t new_binary_inflatedlen;
422 	git_diff_binary_t new_binary_type;
423 };
424 
file_cb(const git_diff_delta * delta,float progress,void * payload)425 static int file_cb(
426 	const git_diff_delta *delta,
427 	float progress,
428 	void *payload)
429 {
430 	struct diff_data *diff_data = payload;
431 
432 	GIT_UNUSED(progress);
433 
434 	if (delta->old_file.path)
435 		diff_data->old_path = git__strdup(delta->old_file.path);
436 
437 	if (delta->new_file.path)
438 		diff_data->new_path = git__strdup(delta->new_file.path);
439 
440 	git_oid_cpy(&diff_data->old_id, &delta->old_file.id);
441 	git_oid_cpy(&diff_data->new_id, &delta->new_file.id);
442 
443 	return 0;
444 }
445 
binary_cb(const git_diff_delta * delta,const git_diff_binary * binary,void * payload)446 static int binary_cb(
447 	const git_diff_delta *delta,
448 	const git_diff_binary *binary,
449 	void *payload)
450 {
451 	struct diff_data *diff_data = payload;
452 
453 	GIT_UNUSED(delta);
454 
455 	git_buf_encode_base85(&diff_data->old_binary_base85,
456 		binary->old_file.data, binary->old_file.datalen);
457 	diff_data->old_binary_inflatedlen = binary->old_file.inflatedlen;
458 	diff_data->old_binary_type = binary->old_file.type;
459 
460 	git_buf_encode_base85(&diff_data->new_binary_base85,
461 		binary->new_file.data, binary->new_file.datalen);
462 	diff_data->new_binary_inflatedlen = binary->new_file.inflatedlen;
463 	diff_data->new_binary_type = binary->new_file.type;
464 
465 	return 0;
466 }
467 
hunk_cb(const git_diff_delta * delta,const git_diff_hunk * hunk,void * payload)468 static int hunk_cb(
469 	const git_diff_delta *delta,
470 	const git_diff_hunk *hunk,
471 	void *payload)
472 {
473 	GIT_UNUSED(delta);
474 	GIT_UNUSED(hunk);
475 	GIT_UNUSED(payload);
476 
477 	cl_fail("did not expect hunk callback");
478 	return 0;
479 }
480 
line_cb(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)481 static int line_cb(
482 	const git_diff_delta *delta,
483 	const git_diff_hunk *hunk,
484 	const git_diff_line *line,
485 	void *payload)
486 {
487 	GIT_UNUSED(delta);
488 	GIT_UNUSED(hunk);
489 	GIT_UNUSED(line);
490 	GIT_UNUSED(payload);
491 
492 	cl_fail("did not expect line callback");
493 	return 0;
494 }
495 
test_diff_binary__blob_to_blob(void)496 void test_diff_binary__blob_to_blob(void)
497 {
498 	git_index *index;
499 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
500 	git_blob *old_blob, *new_blob;
501 	git_oid old_id, new_id;
502 	struct diff_data diff_data = {0};
503 
504 	opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
505 	opts.id_abbrev = GIT_OID_HEXSZ;
506 
507 	repo = cl_git_sandbox_init("renames");
508 	cl_git_pass(git_repository_index__weakptr(&index, repo));
509 
510 	cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n");
511 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
512 	cl_git_pass(git_index_write(index));
513 
514 	git_oid_fromstr(&old_id, "9a69d960ae94b060f56c2a8702545e2bb1abb935");
515 	git_oid_fromstr(&new_id, "1111d4f11f4b35bf6759e0fb714fe09731ef0840");
516 
517 	cl_git_pass(git_blob_lookup(&old_blob, repo, &old_id));
518 	cl_git_pass(git_blob_lookup(&new_blob, repo, &new_id));
519 
520 	cl_git_pass(git_diff_blobs(old_blob,
521 		"untimely.txt", new_blob, "untimely.txt", &opts,
522 		file_cb, binary_cb, hunk_cb, line_cb, &diff_data));
523 
524 	cl_assert_equal_s("untimely.txt", diff_data.old_path);
525 	cl_assert_equal_oid(&old_id, &diff_data.old_id);
526 	cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.old_binary_type);
527 	cl_assert_equal_i(7, diff_data.old_binary_inflatedlen);
528 	cl_assert_equal_s("c%18D`@*{63ljhg(E~C7",
529 		diff_data.old_binary_base85.ptr);
530 
531 	cl_assert_equal_s("untimely.txt", diff_data.new_path);
532 	cl_assert_equal_oid(&new_id, &diff_data.new_id);
533 	cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.new_binary_type);
534 	cl_assert_equal_i(32, diff_data.new_binary_inflatedlen);
535 	cl_assert_equal_s("c%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW",
536 		diff_data.new_binary_base85.ptr);
537 
538 	git_blob_free(old_blob);
539 	git_blob_free(new_blob);
540 
541 	git__free(diff_data.old_path);
542 	git__free(diff_data.new_path);
543 
544 	git_buf_dispose(&diff_data.old_binary_base85);
545 	git_buf_dispose(&diff_data.new_binary_base85);
546 }
547