1 #include "clar_libgit2.h"
2 #include "diff_helpers.h"
3 
4 static git_repository *g_repo = NULL;
5 
test_diff_rename__initialize(void)6 void test_diff_rename__initialize(void)
7 {
8 	g_repo = cl_git_sandbox_init("renames");
9 
10 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
11 }
12 
test_diff_rename__cleanup(void)13 void test_diff_rename__cleanup(void)
14 {
15 	cl_git_sandbox_cleanup();
16 }
17 
18 #define INITIAL_COMMIT "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"
19 #define COPY_RENAME_COMMIT "2bc7f351d20b53f1c72c16c4b036e491c478c49a"
20 #define REWRITE_COPY_COMMIT "1c068dee5790ef1580cfc4cd670915b48d790084"
21 #define RENAME_MODIFICATION_COMMIT "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"
22 #define REWRITE_DELETE_COMMIT "84d8efa38af7ace2b302de0adbda16b1f1cd2e1b"
23 #define DELETE_RENAME_COMMIT "be053a189b0bbde545e0a3f59ce00b46ad29ce0d"
24 #define BREAK_REWRITE_BASE_COMMIT "db98035f715427eef1f5e17f03e1801c05301e9e"
25 #define BREAK_REWRITE_COMMIT "7e7bfb88ba9bc65fd700fee1819cf1c317aafa56"
26 
27 /*
28  * Renames repo has:
29  *
30  * commit 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 -
31  *   serving.txt     (25 lines)
32  *   sevencities.txt (50 lines)
33  * commit 2bc7f351d20b53f1c72c16c4b036e491c478c49a -
34  *   serving.txt     -> sixserving.txt  (rename, no change, 100% match)
35  *   sevencities.txt -> sevencities.txt (no change)
36  *   sevencities.txt -> songofseven.txt (copy, no change, 100% match)
37  * commit 1c068dee5790ef1580cfc4cd670915b48d790084
38  *   songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
39  *   sixserving.txt  -> sixserving.txt  (indentation change)
40  *   sixserving.txt  -> ikeepsix.txt    (copy, add title, >80% match)
41  *   sevencities.txt                    (no change)
42  * commit 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
43  *   songofseven.txt -> untimely.txt    (rename, convert to crlf)
44  *   ikeepsix.txt    -> ikeepsix.txt    (reorder sections in file)
45  *   sixserving.txt  -> sixserving.txt  (whitespace change - not just indent)
46  *   sevencities.txt -> songof7cities.txt (rename, small text changes)
47  * commit 84d8efa38af7ace2b302de0adbda16b1f1cd2e1b
48  *   songof7cities.txt -> songof7citie.txt (major rewrite, <20% match)
49  *   ikeepsix.txt      ->                  (deleted)
50  *   untimely.txt                          (no change)
51  *   sixserving.txt                        (no change)
52  * commit be053a189b0bbde545e0a3f59ce00b46ad29ce0d
53  *   ikeepsix.txt      ->              (deleted)
54  *   songof7cities.txt -> ikeepsix.txt (rename, 100% match)
55  *   untimely.txt                      (no change)
56  *   sixserving.txt                    (no change)
57  */
58 
test_diff_rename__match_oid(void)59 void test_diff_rename__match_oid(void)
60 {
61 	const char *old_sha = INITIAL_COMMIT;
62 	const char *new_sha = COPY_RENAME_COMMIT;
63 	git_tree *old_tree, *new_tree;
64 	git_diff *diff;
65 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
66 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
67 	diff_expects exp;
68 
69 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
70 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
71 
72 	/* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate
73 	 * --find-copies-harder during rename transformion...
74 	 */
75 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
76 
77 	cl_git_pass(git_diff_tree_to_tree(
78 		&diff, g_repo, old_tree, new_tree, &diffopts));
79 
80 	/* git diff --no-renames \
81 	 *          31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
82 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a
83 	 */
84 	memset(&exp, 0, sizeof(exp));
85 	cl_git_pass(git_diff_foreach(
86 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
87 
88 	cl_assert_equal_i(4, exp.files);
89 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
90 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
91 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
92 
93 	/* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
94 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a
95 	 * don't use NULL opts to avoid config `diff.renames` contamination
96 	 */
97 	opts.flags = GIT_DIFF_FIND_RENAMES;
98 	cl_git_pass(git_diff_find_similar(diff, &opts));
99 
100 	memset(&exp, 0, sizeof(exp));
101 	cl_git_pass(git_diff_foreach(
102 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
103 
104 	cl_assert_equal_i(3, exp.files);
105 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
106 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
107 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
108 
109 	git_diff_free(diff);
110 
111 	cl_git_pass(git_diff_tree_to_tree(
112 		&diff, g_repo, old_tree, new_tree, &diffopts));
113 
114 	/* git diff --find-copies-harder \
115 	 *          31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
116 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a
117 	 */
118 	opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
119 	cl_git_pass(git_diff_find_similar(diff, &opts));
120 
121 	memset(&exp, 0, sizeof(exp));
122 	cl_git_pass(git_diff_foreach(
123 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
124 
125 	cl_assert_equal_i(3, exp.files);
126 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
127 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
128 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
129 
130 	git_diff_free(diff);
131 
132 	cl_git_pass(git_diff_tree_to_tree(
133 		&diff, g_repo, old_tree, new_tree, &diffopts));
134 
135 	/* git diff --find-copies-harder -M100 -B100 \
136 	 *          31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
137 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a
138 	 */
139 	opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
140 		GIT_DIFF_FIND_EXACT_MATCH_ONLY;
141 	cl_git_pass(git_diff_find_similar(diff, &opts));
142 
143 	memset(&exp, 0, sizeof(exp));
144 	cl_git_pass(git_diff_foreach(
145 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
146 
147 	cl_assert_equal_i(3, exp.files);
148 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
149 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
150 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
151 
152 	git_diff_free(diff);
153 
154 	git_tree_free(old_tree);
155 	git_tree_free(new_tree);
156 }
157 
test_diff_rename__checks_options_version(void)158 void test_diff_rename__checks_options_version(void)
159 {
160 	const char *old_sha = INITIAL_COMMIT;
161 	const char *new_sha = COPY_RENAME_COMMIT;
162 	git_tree *old_tree, *new_tree;
163 	git_diff *diff;
164 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
165 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
166 	const git_error *err;
167 
168 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
169 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
170 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
171 	cl_git_pass(git_diff_tree_to_tree(
172 		&diff, g_repo, old_tree, new_tree, &diffopts));
173 
174 	opts.version = 0;
175 	cl_git_fail(git_diff_find_similar(diff, &opts));
176 	err = git_error_last();
177 	cl_assert_equal_i(GIT_ERROR_INVALID, err->klass);
178 
179 	git_error_clear();
180 	opts.version = 1024;
181 	cl_git_fail(git_diff_find_similar(diff, &opts));
182 	err = git_error_last();
183 	cl_assert_equal_i(GIT_ERROR_INVALID, err->klass);
184 
185 	git_diff_free(diff);
186 	git_tree_free(old_tree);
187 	git_tree_free(new_tree);
188 }
189 
test_diff_rename__not_exact_match(void)190 void test_diff_rename__not_exact_match(void)
191 {
192 	const char *sha0 = COPY_RENAME_COMMIT;
193 	const char *sha1 = REWRITE_COPY_COMMIT;
194 	const char *sha2 = RENAME_MODIFICATION_COMMIT;
195 	git_tree *old_tree, *new_tree;
196 	git_diff *diff;
197 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
198 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
199 	diff_expects exp;
200 
201 	/* == Changes =====================================================
202 	 * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
203 	 * sixserving.txt  -> sixserving.txt  (indentation change)
204 	 * sixserving.txt  -> ikeepsix.txt    (copy, add title, >80% match)
205 	 * sevencities.txt                    (no change)
206 	 */
207 
208 	old_tree = resolve_commit_oid_to_tree(g_repo, sha0);
209 	new_tree = resolve_commit_oid_to_tree(g_repo, sha1);
210 
211 	/* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate
212 	 * --find-copies-harder during rename transformion...
213 	 */
214 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
215 
216 	cl_git_pass(git_diff_tree_to_tree(
217 		&diff, g_repo, old_tree, new_tree, &diffopts));
218 
219 	/* git diff --no-renames \
220 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a \
221 	 *          1c068dee5790ef1580cfc4cd670915b48d790084
222 	 */
223 	memset(&exp, 0, sizeof(exp));
224 	cl_git_pass(git_diff_foreach(
225 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
226 
227 	cl_assert_equal_i(4, exp.files);
228 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
229 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
230 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
231 
232 	/* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a \
233 	 *          1c068dee5790ef1580cfc4cd670915b48d790084
234 	 *
235 	 * must not pass NULL for opts because it will pick up environment
236 	 * values for "diff.renames" and test won't be consistent.
237 	 */
238 	opts.flags = GIT_DIFF_FIND_RENAMES;
239 	cl_git_pass(git_diff_find_similar(diff, &opts));
240 
241 	memset(&exp, 0, sizeof(exp));
242 	cl_git_pass(git_diff_foreach(
243 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
244 
245 	cl_assert_equal_i(4, exp.files);
246 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
247 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
248 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
249 
250 	git_diff_free(diff);
251 
252 	/* git diff -M -C \
253 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a \
254 	 *          1c068dee5790ef1580cfc4cd670915b48d790084
255 	 */
256 	cl_git_pass(git_diff_tree_to_tree(
257 		&diff, g_repo, old_tree, new_tree, &diffopts));
258 
259 	opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
260 	cl_git_pass(git_diff_find_similar(diff, &opts));
261 
262 	memset(&exp, 0, sizeof(exp));
263 	cl_git_pass(git_diff_foreach(
264 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
265 
266 	cl_assert_equal_i(4, exp.files);
267 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
268 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
269 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
270 
271 	git_diff_free(diff);
272 
273 	/* git diff -M -C --find-copies-harder --break-rewrites \
274 	 *          2bc7f351d20b53f1c72c16c4b036e491c478c49a \
275 	 *          1c068dee5790ef1580cfc4cd670915b48d790084
276 	 */
277 	cl_git_pass(git_diff_tree_to_tree(
278 		&diff, g_repo, old_tree, new_tree, &diffopts));
279 
280 	opts.flags = GIT_DIFF_FIND_ALL;
281 	opts.break_rewrite_threshold = 70;
282 
283 	cl_git_pass(git_diff_find_similar(diff, &opts));
284 
285 	memset(&exp, 0, sizeof(exp));
286 	cl_git_pass(git_diff_foreach(
287 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
288 
289 	cl_assert_equal_i(5, exp.files);
290 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
291 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
292 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
293 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
294 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
295 
296 	git_diff_free(diff);
297 
298 	/* == Changes =====================================================
299 	 * songofseven.txt -> untimely.txt    (rename, convert to crlf)
300 	 * ikeepsix.txt    -> ikeepsix.txt    (reorder sections in file)
301 	 * sixserving.txt  -> sixserving.txt  (whitespace - not just indent)
302 	 * sevencities.txt -> songof7cities.txt (rename, small text changes)
303 	 */
304 
305 	git_tree_free(old_tree);
306 	old_tree = new_tree;
307 	new_tree = resolve_commit_oid_to_tree(g_repo, sha2);
308 
309 	cl_git_pass(git_diff_tree_to_tree(
310 		&diff, g_repo, old_tree, new_tree, &diffopts));
311 
312 	/* git diff --no-renames \
313 	 *          1c068dee5790ef1580cfc4cd670915b48d790084 \
314 	 *          19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
315 	 */
316 	memset(&exp, 0, sizeof(exp));
317 	cl_git_pass(git_diff_foreach(
318 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
319 
320 	cl_assert_equal_i(6, exp.files);
321 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
322 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
323 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
324 	git_diff_free(diff);
325 
326 	/* git diff -M -C \
327 	 *          1c068dee5790ef1580cfc4cd670915b48d790084 \
328 	 *          19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
329 	 */
330 	cl_git_pass(git_diff_tree_to_tree(
331 		&diff, g_repo, old_tree, new_tree, &diffopts));
332 
333 	opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
334 	cl_git_pass(git_diff_find_similar(diff, &opts));
335 
336 	memset(&exp, 0, sizeof(exp));
337 	cl_git_pass(git_diff_foreach(
338 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
339 
340 	cl_assert_equal_i(4, exp.files);
341 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
342 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
343 
344 	git_diff_free(diff);
345 
346 	/* git diff -M -C --find-copies-harder --break-rewrites \
347 	 *          1c068dee5790ef1580cfc4cd670915b48d790084 \
348 	 *          19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
349 	 * with libgit2 default similarity comparison...
350 	 */
351 	cl_git_pass(git_diff_tree_to_tree(
352 		&diff, g_repo, old_tree, new_tree, &diffopts));
353 
354 	opts.flags = GIT_DIFF_FIND_ALL;
355 	cl_git_pass(git_diff_find_similar(diff, &opts));
356 
357 	/* the default match algorithm is going to find the internal
358 	 * whitespace differences in the lines of sixserving.txt to be
359 	 * significant enough that this will decide to split it into an ADD
360 	 * and a DELETE
361 	 */
362 
363 	memset(&exp, 0, sizeof(exp));
364 	cl_git_pass(git_diff_foreach(
365 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
366 
367 	cl_assert_equal_i(5, exp.files);
368 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
369 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
370 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
371 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
372 
373 	git_diff_free(diff);
374 
375 	/* git diff -M -C --find-copies-harder --break-rewrites \
376 	 *          1c068dee5790ef1580cfc4cd670915b48d790084 \
377 	 *          19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
378 	 * with ignore_space whitespace comparision
379 	 */
380 	cl_git_pass(git_diff_tree_to_tree(
381 		&diff, g_repo, old_tree, new_tree, &diffopts));
382 
383 	opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_IGNORE_WHITESPACE;
384 	cl_git_pass(git_diff_find_similar(diff, &opts));
385 
386 	/* Ignoring whitespace, this should no longer split sixserver.txt */
387 
388 	memset(&exp, 0, sizeof(exp));
389 	cl_git_pass(git_diff_foreach(
390 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
391 
392 	cl_assert_equal_i(4, exp.files);
393 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
394 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
395 
396 	git_diff_free(diff);
397 
398 	git_tree_free(old_tree);
399 	git_tree_free(new_tree);
400 }
401 
test_diff_rename__test_small_files(void)402 void test_diff_rename__test_small_files(void)
403 {
404 	git_index *index;
405 	git_reference *head_reference;
406 	git_commit *head_commit;
407 	git_tree *head_tree;
408 	git_tree *commit_tree;
409 	git_signature *signature;
410 	git_diff *diff;
411 	git_oid oid;
412 	const git_diff_delta *delta;
413 	git_diff_options diff_options = GIT_DIFF_OPTIONS_INIT;
414 	git_diff_find_options find_options = GIT_DIFF_FIND_OPTIONS_INIT;
415 
416 	cl_git_pass(git_repository_index(&index, g_repo));
417 
418 	cl_git_mkfile("renames/small.txt", "Hello World!\n");
419 	cl_git_pass(git_index_add_bypath(index, "small.txt"));
420 
421 	cl_git_pass(git_repository_head(&head_reference, g_repo));
422 	cl_git_pass(git_reference_peel((git_object**)&head_commit, head_reference, GIT_OBJECT_COMMIT));
423 	cl_git_pass(git_commit_tree(&head_tree, head_commit));
424 	cl_git_pass(git_index_write_tree(&oid, index));
425 	cl_git_pass(git_tree_lookup(&commit_tree, g_repo, &oid));
426 	cl_git_pass(git_signature_new(&signature, "Rename", "rename@example.com", 1404157834, 0));
427 	cl_git_pass(git_commit_create(&oid, g_repo, "HEAD", signature, signature, NULL, "Test commit", commit_tree, 1, (const git_commit**)&head_commit));
428 
429 	cl_git_mkfile("renames/copy.txt", "Hello World!\n");
430 	cl_git_rmfile("renames/small.txt");
431 
432 	diff_options.flags = GIT_DIFF_INCLUDE_UNTRACKED;
433 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, commit_tree, &diff_options));
434 	find_options.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_FOR_UNTRACKED;
435 	cl_git_pass(git_diff_find_similar(diff, &find_options));
436 
437 	cl_assert_equal_i(git_diff_num_deltas(diff), 1);
438 	delta = git_diff_get_delta(diff, 0);
439 	cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED);
440 	cl_assert_equal_s(delta->old_file.path, "small.txt");
441 	cl_assert_equal_s(delta->new_file.path, "copy.txt");
442 
443 	git_diff_free(diff);
444 	git_signature_free(signature);
445 	git_tree_free(commit_tree);
446 	git_tree_free(head_tree);
447 	git_commit_free(head_commit);
448 	git_reference_free(head_reference);
449 	git_index_free(index);
450 }
451 
test_diff_rename__working_directory_changes(void)452 void test_diff_rename__working_directory_changes(void)
453 {
454 	const char *sha0 = COPY_RENAME_COMMIT;
455 	const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba";
456 	git_oid id;
457 	git_tree *tree;
458 	git_blob *blob;
459 	git_diff *diff;
460 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
461 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
462 	diff_expects exp;
463 	git_buf old_content = GIT_BUF_INIT, content = GIT_BUF_INIT;;
464 
465 	tree = resolve_commit_oid_to_tree(g_repo, sha0);
466 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED;
467 
468 	/*
469 	$ git cat-file -p 2bc7f351d20b53f1c72c16c4b036e491c478c49a^{tree}
470 
471 	100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba	sevencities.txt
472 	100644 blob ad0a8e55a104ac54a8a29ed4b84b49e76837a113	sixserving.txt
473 	100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba	songofseven.txt
474 
475 	$ for f in *.txt; do
476 		echo `git hash-object -t blob $f` $f
477 	done
478 
479 	eaf4a3e3bfe68585e90cada20736ace491cd100b ikeepsix.txt
480 	f90d4fc20ecddf21eebe6a37e9225d244339d2b5 sixserving.txt
481 	4210ffd5c390b21dd5483375e75288dea9ede512 songof7cities.txt
482 	9a69d960ae94b060f56c2a8702545e2bb1abb935 untimely.txt
483 	*/
484 
485 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
486 
487 	/* git diff --no-renames 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
488 	memset(&exp, 0, sizeof(exp));
489 	cl_git_pass(git_diff_foreach(
490 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
491 
492 	cl_assert_equal_i(6, exp.files);
493 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
494 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
495 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
496 
497 	/* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
498 	opts.flags = GIT_DIFF_FIND_ALL;
499 	cl_git_pass(git_diff_find_similar(diff, &opts));
500 
501 	memset(&exp, 0, sizeof(exp));
502 	cl_git_pass(git_diff_foreach(
503 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
504 
505 	cl_assert_equal_i(5, exp.files);
506 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
507 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
508 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
509 
510 	git_diff_free(diff);
511 
512 	/* rewrite files in the working directory with / without CRLF changes */
513 
514 	cl_git_pass(
515 		git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));
516 	cl_git_pass(
517 		git_buf_lf_to_crlf(&content, &old_content));
518 	cl_git_pass(
519 		git_futils_writebuffer(&content, "renames/songof7cities.txt", 0, 0));
520 
521 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
522 
523 	/* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
524 	opts.flags = GIT_DIFF_FIND_ALL;
525 	cl_git_pass(git_diff_find_similar(diff, &opts));
526 
527 	memset(&exp, 0, sizeof(exp));
528 	cl_git_pass(git_diff_foreach(
529 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
530 
531 	cl_assert_equal_i(5, exp.files);
532 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
533 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
534 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
535 
536 	git_diff_free(diff);
537 
538 	/* try a different whitespace option */
539 
540 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
541 
542 	opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE;
543 	opts.rename_threshold = 70;
544 	cl_git_pass(git_diff_find_similar(diff, &opts));
545 
546 	memset(&exp, 0, sizeof(exp));
547 	cl_git_pass(git_diff_foreach(
548 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
549 
550 	cl_assert_equal_i(6, exp.files);
551 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
552 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
553 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
554 
555 	git_diff_free(diff);
556 
557 	/* try a different matching option */
558 
559 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
560 
561 	opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
562 	cl_git_pass(git_diff_find_similar(diff, &opts));
563 
564 	memset(&exp, 0, sizeof(exp));
565 	cl_git_pass(git_diff_foreach(
566 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
567 
568 	cl_assert_equal_i(6, exp.files);
569 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
570 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
571 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
572 
573 	git_diff_free(diff);
574 
575 	/* again with exact match blob */
576 
577 	cl_git_pass(git_oid_fromstr(&id, blobsha));
578 	cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
579 	cl_git_pass(git_buf_set(
580 		&content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob)));
581 	cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
582 	git_blob_free(blob);
583 
584 	cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
585 
586 	opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
587 	cl_git_pass(git_diff_find_similar(diff, &opts));
588 
589 	/*
590 	fprintf(stderr, "\n\n");
591     diff_print_raw(stderr, diff);
592 	*/
593 
594 	memset(&exp, 0, sizeof(exp));
595 	cl_git_pass(git_diff_foreach(
596 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
597 
598 	cl_assert_equal_i(5, exp.files);
599 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
600 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
601 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
602 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
603 
604 	git_diff_free(diff);
605 
606 	git_tree_free(tree);
607 	git_buf_dispose(&content);
608 	git_buf_dispose(&old_content);
609 }
610 
test_diff_rename__patch(void)611 void test_diff_rename__patch(void)
612 {
613 	const char *sha0 = COPY_RENAME_COMMIT;
614 	const char *sha1 = REWRITE_COPY_COMMIT;
615 	git_tree *old_tree, *new_tree;
616 	git_diff *diff;
617 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
618 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
619 	git_patch *patch;
620 	const git_diff_delta *delta;
621 	git_buf buf = GIT_BUF_INIT;
622 	const char *expected = "diff --git a/sixserving.txt b/ikeepsix.txt\nindex ad0a8e5..36020db 100644\n--- a/sixserving.txt\n+++ b/ikeepsix.txt\n@@ -1,3 +1,6 @@\n+I Keep Six Honest Serving-Men\n+=============================\n+\n I KEEP six honest serving-men\n  (They taught me all I knew);\n Their names are What and Why and When\n@@ -21,4 +24,4 @@ She sends'em abroad on her own affairs,\n One million Hows, two million Wheres,\n And seven million Whys!\n \n-                -- Rudyard Kipling\n+  -- Rudyard Kipling\n";
623 
624 	old_tree = resolve_commit_oid_to_tree(g_repo, sha0);
625 	new_tree = resolve_commit_oid_to_tree(g_repo, sha1);
626 
627 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
628 	cl_git_pass(git_diff_tree_to_tree(
629 		&diff, g_repo, old_tree, new_tree, &diffopts));
630 
631 	opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
632 	cl_git_pass(git_diff_find_similar(diff, &opts));
633 
634 	/* == Changes =====================================================
635 	 * sixserving.txt  -> ikeepsix.txt    (copy, add title, >80% match)
636 	 * sevencities.txt                    (no change)
637 	 * sixserving.txt  -> sixserving.txt  (indentation change)
638 	 * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
639 	 */
640 
641 	cl_assert_equal_i(4, (int)git_diff_num_deltas(diff));
642 
643 	cl_git_pass(git_patch_from_diff(&patch, diff, 0));
644 	cl_assert((delta = git_patch_get_delta(patch)) != NULL);
645 	cl_assert_equal_i(GIT_DELTA_COPIED, (int)delta->status);
646 
647 	cl_git_pass(git_patch_to_buf(&buf, patch));
648 	cl_assert_equal_s(expected, buf.ptr);
649 	git_buf_dispose(&buf);
650 
651 	git_patch_free(patch);
652 
653 	cl_assert((delta = git_diff_get_delta(diff, 1)) != NULL);
654 	cl_assert_equal_i(GIT_DELTA_UNMODIFIED, (int)delta->status);
655 
656 	cl_assert((delta = git_diff_get_delta(diff, 2)) != NULL);
657 	cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
658 
659 	cl_assert((delta = git_diff_get_delta(diff, 3)) != NULL);
660 	cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
661 
662 	git_diff_free(diff);
663 	git_tree_free(old_tree);
664 	git_tree_free(new_tree);
665 }
666 
test_diff_rename__file_exchange(void)667 void test_diff_rename__file_exchange(void)
668 {
669 	git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
670 	git_index *index;
671 	git_tree *tree;
672 	git_diff *diff;
673 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
674 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
675 	diff_expects exp;
676 
677 	cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
678 	cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt"));
679 	cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
680 	cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
681 
682 	cl_git_pass(
683 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
684 
685 	cl_git_pass(git_repository_index(&index, g_repo));
686 	cl_git_pass(git_index_read_tree(index, tree));
687 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
688 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
689 
690 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
691 
692 	memset(&exp, 0, sizeof(exp));
693 	cl_git_pass(git_diff_foreach(
694 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
695 	cl_assert_equal_i(2, exp.files);
696 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
697 
698 	opts.flags = GIT_DIFF_FIND_ALL;
699 	cl_git_pass(git_diff_find_similar(diff, &opts));
700 
701 	memset(&exp, 0, sizeof(exp));
702 	cl_git_pass(git_diff_foreach(
703 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
704 	cl_assert_equal_i(2, exp.files);
705 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
706 
707 	git_diff_free(diff);
708 	git_tree_free(tree);
709 	git_index_free(index);
710 
711 	git_buf_dispose(&c1);
712 	git_buf_dispose(&c2);
713 }
714 
test_diff_rename__file_exchange_three(void)715 void test_diff_rename__file_exchange_three(void)
716 {
717 	git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT, c3 = GIT_BUF_INIT;
718 	git_index *index;
719 	git_tree *tree;
720 	git_diff *diff;
721 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
722 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
723 	diff_expects exp;
724 
725 	cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
726 	cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt"));
727 	cl_git_pass(git_futils_readbuffer(&c3, "renames/ikeepsix.txt"));
728 
729 	cl_git_pass(git_futils_writebuffer(&c1, "renames/ikeepsix.txt", 0, 0));
730 	cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
731 	cl_git_pass(git_futils_writebuffer(&c3, "renames/songof7cities.txt", 0, 0));
732 
733 	cl_git_pass(
734 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
735 
736 	cl_git_pass(git_repository_index(&index, g_repo));
737 	cl_git_pass(git_index_read_tree(index, tree));
738 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
739 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
740 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
741 
742 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
743 
744 	memset(&exp, 0, sizeof(exp));
745 	cl_git_pass(git_diff_foreach(
746 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
747 	cl_assert_equal_i(3, exp.files);
748 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
749 
750 	opts.flags = GIT_DIFF_FIND_ALL;
751 	cl_git_pass(git_diff_find_similar(diff, &opts));
752 
753 	memset(&exp, 0, sizeof(exp));
754 	cl_git_pass(git_diff_foreach(
755 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
756 	cl_assert_equal_i(3, exp.files);
757 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]);
758 
759 	git_diff_free(diff);
760 	git_tree_free(tree);
761 	git_index_free(index);
762 
763 	git_buf_dispose(&c1);
764 	git_buf_dispose(&c2);
765 	git_buf_dispose(&c3);
766 }
767 
test_diff_rename__file_partial_exchange(void)768 void test_diff_rename__file_partial_exchange(void)
769 {
770 	git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
771 	git_index *index;
772 	git_tree *tree;
773 	git_diff *diff;
774 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
775 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
776 	diff_expects exp;
777 	int i;
778 
779 	cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
780 	cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
781 	for (i = 0; i < 100; ++i)
782 		cl_git_pass(git_buf_puts(&c2, "this is not the content you are looking for\n"));
783 	cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
784 
785 	cl_git_pass(
786 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
787 
788 	cl_git_pass(git_repository_index(&index, g_repo));
789 	cl_git_pass(git_index_read_tree(index, tree));
790 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
791 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
792 
793 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
794 
795 	memset(&exp, 0, sizeof(exp));
796 	cl_git_pass(git_diff_foreach(
797 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
798 	cl_assert_equal_i(2, exp.files);
799 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
800 
801 	opts.flags = GIT_DIFF_FIND_ALL;
802 	cl_git_pass(git_diff_find_similar(diff, &opts));
803 
804 	memset(&exp, 0, sizeof(exp));
805 	cl_git_pass(git_diff_foreach(
806 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
807 	cl_assert_equal_i(3, exp.files);
808 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
809 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
810 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
811 
812 	git_diff_free(diff);
813 	git_tree_free(tree);
814 	git_index_free(index);
815 
816 	git_buf_dispose(&c1);
817 	git_buf_dispose(&c2);
818 }
819 
test_diff_rename__rename_and_copy_from_same_source(void)820 void test_diff_rename__rename_and_copy_from_same_source(void)
821 {
822 	git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
823 	git_index *index;
824 	git_tree *tree;
825 	git_diff *diff;
826 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
827 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
828 	diff_expects exp;
829 
830 	/* put the first 2/3 of file into one new place
831 	 * and the second 2/3 of file into another new place
832 	 */
833 	cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
834 	cl_git_pass(git_buf_set(&c2, c1.ptr, c1.size));
835 	git_buf_truncate(&c1, c1.size * 2 / 3);
836 	git_buf_consume(&c2, ((char *)c2.ptr) + (c2.size / 3));
837 	cl_git_pass(git_futils_writebuffer(&c1, "renames/song_a.txt", 0, 0));
838 	cl_git_pass(git_futils_writebuffer(&c2, "renames/song_b.txt", 0, 0));
839 
840 	cl_git_pass(
841 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
842 
843 	cl_git_pass(git_repository_index(&index, g_repo));
844 	cl_git_pass(git_index_read_tree(index, tree));
845 	cl_git_pass(git_index_add_bypath(index, "song_a.txt"));
846 	cl_git_pass(git_index_add_bypath(index, "song_b.txt"));
847 
848 	diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
849 
850 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
851 
852 	memset(&exp, 0, sizeof(exp));
853 	cl_git_pass(git_diff_foreach(
854 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
855 	cl_assert_equal_i(6, exp.files);
856 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
857 	cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
858 
859 	opts.flags = GIT_DIFF_FIND_ALL;
860 	cl_git_pass(git_diff_find_similar(diff, &opts));
861 
862 	memset(&exp, 0, sizeof(exp));
863 	cl_git_pass(git_diff_foreach(
864 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
865 	cl_assert_equal_i(6, exp.files);
866 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]);
867 	cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
868 
869 	git_diff_free(diff);
870 	git_tree_free(tree);
871 	git_index_free(index);
872 
873 	git_buf_dispose(&c1);
874 	git_buf_dispose(&c2);
875 }
876 
test_diff_rename__from_deleted_to_split(void)877 void test_diff_rename__from_deleted_to_split(void)
878 {
879 	git_buf c1 = GIT_BUF_INIT;
880 	git_index *index;
881 	git_tree *tree;
882 	git_diff *diff;
883 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
884 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
885 	diff_expects exp;
886 
887 	/* old file is missing, new file is actually old file renamed */
888 
889 	cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
890 	cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
891 
892 	cl_git_pass(
893 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
894 
895 	cl_git_pass(git_repository_index(&index, g_repo));
896 	cl_git_pass(git_index_read_tree(index, tree));
897 	cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
898 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
899 
900 	diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
901 
902 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
903 
904 	memset(&exp, 0, sizeof(exp));
905 	cl_git_pass(git_diff_foreach(
906 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
907 	cl_assert_equal_i(4, exp.files);
908 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
909 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
910 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
911 
912 	opts.flags = GIT_DIFF_FIND_ALL;
913 	cl_git_pass(git_diff_find_similar(diff, &opts));
914 
915 	memset(&exp, 0, sizeof(exp));
916 	cl_git_pass(git_diff_foreach(
917 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
918 	cl_assert_equal_i(4, exp.files);
919 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
920 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
921 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
922 
923 	git_diff_free(diff);
924 	git_tree_free(tree);
925 	git_index_free(index);
926 
927 	git_buf_dispose(&c1);
928 }
929 
930 struct rename_expected
931 {
932 	size_t len;
933 
934 	unsigned int *status;
935 	const char **sources;
936 	const char **targets;
937 
938 	size_t idx;
939 };
940 
test_names_expected(const git_diff_delta * delta,float progress,void * p)941 int test_names_expected(const git_diff_delta *delta, float progress, void *p)
942 {
943 	struct rename_expected *expected = p;
944 
945 	GIT_UNUSED(progress);
946 
947 	cl_assert(expected->idx < expected->len);
948 
949 	cl_assert_equal_i(delta->status, expected->status[expected->idx]);
950 
951 	cl_assert(git__strcmp(expected->sources[expected->idx],
952 		delta->old_file.path) == 0);
953 	cl_assert(git__strcmp(expected->targets[expected->idx],
954 		delta->new_file.path) == 0);
955 
956 	expected->idx++;
957 
958 	return 0;
959 }
960 
test_diff_rename__rejected_match_can_match_others(void)961 void test_diff_rename__rejected_match_can_match_others(void)
962 {
963 	git_reference *head, *selfsimilar;
964 	git_index *index;
965 	git_tree *tree;
966 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
967 	git_diff *diff;
968 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
969 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
970 	git_buf one = GIT_BUF_INIT, two = GIT_BUF_INIT;
971 	unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
972 	const char *sources[] = { "Class1.cs", "Class2.cs" };
973 	const char *targets[] = { "ClassA.cs", "ClassB.cs" };
974 	struct rename_expected expect = { 2, status, sources, targets };
975 	char *ptr;
976 
977 	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
978 	findopts.flags = GIT_DIFF_FIND_RENAMES;
979 
980 	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
981 	cl_git_pass(git_reference_symbolic_set_target(
982 		&selfsimilar, head, "refs/heads/renames_similar", NULL));
983 	cl_git_pass(git_checkout_head(g_repo, &opts));
984 	cl_git_pass(git_repository_index(&index, g_repo));
985 
986 	cl_git_pass(git_futils_readbuffer(&one, "renames/Class1.cs"));
987 	cl_git_pass(git_futils_readbuffer(&two, "renames/Class2.cs"));
988 
989 	cl_git_pass(p_unlink("renames/Class1.cs"));
990 	cl_git_pass(p_unlink("renames/Class2.cs"));
991 
992 	cl_git_pass(git_index_remove_bypath(index, "Class1.cs"));
993 	cl_git_pass(git_index_remove_bypath(index, "Class2.cs"));
994 
995 	cl_assert(ptr = strstr(one.ptr, "Class1"));
996 	ptr[5] = 'A';
997 
998 	cl_assert(ptr = strstr(two.ptr, "Class2"));
999 	ptr[5] = 'B';
1000 
1001 	cl_git_pass(
1002 		git_futils_writebuffer(&one, "renames/ClassA.cs", O_RDWR|O_CREAT, 0777));
1003 	cl_git_pass(
1004 		git_futils_writebuffer(&two, "renames/ClassB.cs", O_RDWR|O_CREAT, 0777));
1005 
1006 	cl_git_pass(git_index_add_bypath(index, "ClassA.cs"));
1007 	cl_git_pass(git_index_add_bypath(index, "ClassB.cs"));
1008 
1009 	cl_git_pass(git_index_write(index));
1010 
1011 	cl_git_pass(
1012 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1013 
1014 	cl_git_pass(
1015 		git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1016 
1017 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1018 
1019 	cl_git_pass(git_diff_foreach(
1020 		diff, test_names_expected, NULL, NULL, NULL, &expect));
1021 
1022 	git_diff_free(diff);
1023 	git_tree_free(tree);
1024 	git_index_free(index);
1025 	git_reference_free(head);
1026 	git_reference_free(selfsimilar);
1027 	git_buf_dispose(&one);
1028 	git_buf_dispose(&two);
1029 }
1030 
write_similarity_file_two(const char * filename,size_t b_lines)1031 static void write_similarity_file_two(const char *filename, size_t b_lines)
1032 {
1033 	git_buf contents = GIT_BUF_INIT;
1034 	size_t i;
1035 
1036 	for (i = 0; i < b_lines; i++)
1037 		git_buf_printf(&contents, "%02d - bbbbb\r\n", (int)(i+1));
1038 
1039 	for (i = b_lines; i < 50; i++)
1040 		git_buf_printf(&contents, "%02d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n"));
1041 
1042 	cl_git_pass(
1043 		git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777));
1044 
1045 	git_buf_dispose(&contents);
1046 }
1047 
test_diff_rename__rejected_match_can_match_others_two(void)1048 void test_diff_rename__rejected_match_can_match_others_two(void)
1049 {
1050 	git_reference *head, *selfsimilar;
1051 	git_index *index;
1052 	git_tree *tree;
1053 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
1054 	git_diff *diff;
1055 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1056 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1057 	unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
1058 	const char *sources[] = { "a.txt", "b.txt" };
1059 	const char *targets[] = { "c.txt", "d.txt" };
1060 	struct rename_expected expect = { 2, status, sources, targets };
1061 
1062 	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
1063 	findopts.flags = GIT_DIFF_FIND_RENAMES;
1064 
1065 	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
1066 	cl_git_pass(git_reference_symbolic_set_target(
1067 		&selfsimilar, head, "refs/heads/renames_similar_two", NULL));
1068 	cl_git_pass(git_checkout_head(g_repo, &opts));
1069 	cl_git_pass(git_repository_index(&index, g_repo));
1070 
1071 	cl_git_pass(p_unlink("renames/a.txt"));
1072 	cl_git_pass(p_unlink("renames/b.txt"));
1073 
1074 	cl_git_pass(git_index_remove_bypath(index, "a.txt"));
1075 	cl_git_pass(git_index_remove_bypath(index, "b.txt"));
1076 
1077 	write_similarity_file_two("renames/c.txt", 7);
1078 	write_similarity_file_two("renames/d.txt", 8);
1079 
1080 	cl_git_pass(git_index_add_bypath(index, "c.txt"));
1081 	cl_git_pass(git_index_add_bypath(index, "d.txt"));
1082 
1083 	cl_git_pass(git_index_write(index));
1084 
1085 	cl_git_pass(
1086 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1087 
1088 	cl_git_pass(
1089 		git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1090 
1091 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1092 
1093 	cl_git_pass(git_diff_foreach(
1094 		diff, test_names_expected, NULL, NULL, NULL, &expect));
1095 	cl_assert(expect.idx > 0);
1096 
1097 	git_diff_free(diff);
1098 	git_tree_free(tree);
1099 	git_index_free(index);
1100 	git_reference_free(head);
1101 	git_reference_free(selfsimilar);
1102 }
1103 
test_diff_rename__rejected_match_can_match_others_three(void)1104 void test_diff_rename__rejected_match_can_match_others_three(void)
1105 {
1106 	git_reference *head, *selfsimilar;
1107 	git_index *index;
1108 	git_tree *tree;
1109 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
1110 	git_diff *diff;
1111 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1112 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1113 
1114 	/* Both cannot be renames from a.txt */
1115 	unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED };
1116 	const char *sources[] = { "0001.txt", "a.txt" };
1117 	const char *targets[] = { "0001.txt", "0002.txt" };
1118 	struct rename_expected expect = { 2, status, sources, targets };
1119 
1120 	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
1121 	findopts.flags = GIT_DIFF_FIND_RENAMES;
1122 
1123 	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
1124 	cl_git_pass(git_reference_symbolic_set_target(
1125 		&selfsimilar, head, "refs/heads/renames_similar_two", NULL));
1126 	cl_git_pass(git_checkout_head(g_repo, &opts));
1127 	cl_git_pass(git_repository_index(&index, g_repo));
1128 
1129 	cl_git_pass(p_unlink("renames/a.txt"));
1130 
1131 	cl_git_pass(git_index_remove_bypath(index, "a.txt"));
1132 
1133 	write_similarity_file_two("renames/0001.txt", 7);
1134 	write_similarity_file_two("renames/0002.txt", 0);
1135 
1136 	cl_git_pass(git_index_add_bypath(index, "0001.txt"));
1137 	cl_git_pass(git_index_add_bypath(index, "0002.txt"));
1138 
1139 	cl_git_pass(git_index_write(index));
1140 
1141 	cl_git_pass(
1142 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1143 
1144 	cl_git_pass(
1145 		git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1146 
1147 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1148 
1149 	cl_git_pass(git_diff_foreach(
1150 		diff, test_names_expected, NULL, NULL, NULL, &expect));
1151 
1152 	cl_assert(expect.idx == expect.len);
1153 
1154 	git_diff_free(diff);
1155 	git_tree_free(tree);
1156 	git_index_free(index);
1157 	git_reference_free(head);
1158 	git_reference_free(selfsimilar);
1159 }
1160 
test_diff_rename__can_rename_from_rewrite(void)1161 void test_diff_rename__can_rename_from_rewrite(void)
1162 {
1163 	git_index *index;
1164 	git_tree *tree;
1165 	git_diff *diff;
1166 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1167 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1168 
1169 	unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
1170 	const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" };
1171 	const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" };
1172 	struct rename_expected expect = { 2, status, sources, targets };
1173 
1174 	cl_git_pass(git_repository_index(&index, g_repo));
1175 
1176 	cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt"));
1177 	cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt"));
1178 
1179 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
1180 
1181 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
1182 	cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt"));
1183 
1184 	cl_git_pass(git_index_write(index));
1185 
1186 	cl_git_pass(
1187 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1188 
1189 	cl_git_pass(
1190 		git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1191 
1192 	findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES |
1193 		GIT_DIFF_FIND_REWRITES |
1194 		GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
1195 
1196 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1197 
1198 	cl_git_pass(git_diff_foreach(
1199 		diff, test_names_expected, NULL, NULL, NULL, &expect));
1200 
1201 	cl_assert(expect.idx == expect.len);
1202 
1203 	git_diff_free(diff);
1204 	git_tree_free(tree);
1205 	git_index_free(index);
1206 }
1207 
test_diff_rename__case_changes_are_split(void)1208 void test_diff_rename__case_changes_are_split(void)
1209 {
1210 	git_index *index;
1211 	git_tree *tree;
1212 	git_diff *diff = NULL;
1213 	diff_expects exp;
1214 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1215 
1216 	cl_git_pass(git_repository_index(&index, g_repo));
1217 
1218 	cl_git_pass(
1219 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1220 
1221 	cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt"));
1222 
1223 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
1224 	cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt"));
1225 	cl_git_pass(git_index_write(index));
1226 
1227 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL));
1228 
1229 	memset(&exp, 0, sizeof(exp));
1230 	cl_git_pass(git_diff_foreach(
1231 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1232 	cl_assert_equal_i(2, exp.files);
1233 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1234 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
1235 
1236 	opts.flags = GIT_DIFF_FIND_ALL;
1237 	cl_git_pass(git_diff_find_similar(diff, &opts));
1238 
1239 	memset(&exp, 0, sizeof(exp));
1240 	cl_git_pass(git_diff_foreach(
1241 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1242 	cl_assert_equal_i(1, exp.files);
1243 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1244 
1245 	git_diff_free(diff);
1246 	git_index_free(index);
1247 	git_tree_free(tree);
1248 }
1249 
test_diff_rename__unmodified_can_be_renamed(void)1250 void test_diff_rename__unmodified_can_be_renamed(void)
1251 {
1252 	git_index *index;
1253 	git_tree *tree;
1254 	git_diff *diff = NULL;
1255 	diff_expects exp;
1256 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1257 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1258 
1259 	cl_git_pass(git_repository_index(&index, g_repo));
1260 	cl_git_pass(
1261 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1262 
1263 	cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt"));
1264 
1265 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
1266 	cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt"));
1267 	cl_git_pass(git_index_write(index));
1268 
1269 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1270 
1271 	memset(&exp, 0, sizeof(exp));
1272 	cl_git_pass(git_diff_foreach(
1273 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1274 	cl_assert_equal_i(2, exp.files);
1275 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1276 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
1277 
1278 	opts.flags = GIT_DIFF_FIND_ALL;
1279 	cl_git_pass(git_diff_find_similar(diff, &opts));
1280 
1281 	memset(&exp, 0, sizeof(exp));
1282 	cl_git_pass(git_diff_foreach(
1283 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1284 	cl_assert_equal_i(1, exp.files);
1285 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1286 
1287 	memset(&exp, 0, sizeof(exp));
1288 	cl_git_pass(git_diff_foreach(
1289 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1290 	cl_assert_equal_i(1, exp.files);
1291 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1292 
1293 	git_diff_free(diff);
1294 	git_index_free(index);
1295 	git_tree_free(tree);
1296 }
1297 
test_diff_rename__rewrite_on_single_file(void)1298 void test_diff_rename__rewrite_on_single_file(void)
1299 {
1300 	git_index *index;
1301 	git_diff *diff = NULL;
1302 	diff_expects exp;
1303 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1304 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1305 
1306 	diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
1307 
1308 	findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED |
1309 		GIT_DIFF_FIND_AND_BREAK_REWRITES |
1310 		GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
1311 
1312 	cl_git_pass(git_repository_index(&index, g_repo));
1313 
1314 	cl_git_rewritefile("renames/ikeepsix.txt",
1315 		"This is enough content for the file to be rewritten.\n" \
1316 		"This is enough content for the file to be rewritten.\n" \
1317 		"This is enough content for the file to be rewritten.\n" \
1318 		"This is enough content for the file to be rewritten.\n" \
1319 		"This is enough content for the file to be rewritten.\n" \
1320 		"This is enough content for the file to be rewritten.\n" \
1321 		"This is enough content for the file to be rewritten.\n" \
1322 		"This is enough content for the file to be rewritten.\n" \
1323 		"This is enough content for the file to be rewritten.\n" \
1324 		"This is enough content for the file to be rewritten.\n" \
1325 		"This is enough content for the file to be rewritten.\n" \
1326 		"This is enough content for the file to be rewritten.\n" \
1327 		"This is enough content for the file to be rewritten.\n" \
1328 		"This is enough content for the file to be rewritten.\n" \
1329 		"This is enough content for the file to be rewritten.\n" \
1330 		"This is enough content for the file to be rewritten.\n");
1331 
1332 	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts));
1333 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1334 
1335 	memset(&exp, 0, sizeof(exp));
1336 
1337 	cl_git_pass(git_diff_foreach(
1338 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1339 	cl_assert_equal_i(2, exp.files);
1340 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1341 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1342 
1343 	git_diff_free(diff);
1344 	git_index_free(index);
1345 }
1346 
test_diff_rename__can_find_copy_to_split(void)1347 void test_diff_rename__can_find_copy_to_split(void)
1348 {
1349 	git_buf c1 = GIT_BUF_INIT;
1350 	git_index *index;
1351 	git_tree *tree;
1352 	git_diff *diff;
1353 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1354 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1355 	diff_expects exp;
1356 
1357 	cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
1358 	cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
1359 
1360 	cl_git_pass(
1361 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1362 
1363 	cl_git_pass(git_repository_index(&index, g_repo));
1364 	cl_git_pass(git_index_read_tree(index, tree));
1365 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
1366 
1367 	diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
1368 
1369 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1370 
1371 	memset(&exp, 0, sizeof(exp));
1372 	cl_git_pass(git_diff_foreach(
1373 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1374 	cl_assert_equal_i(4, exp.files);
1375 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1376 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
1377 
1378 	opts.flags = GIT_DIFF_FIND_ALL;
1379 	cl_git_pass(git_diff_find_similar(diff, &opts));
1380 
1381 	memset(&exp, 0, sizeof(exp));
1382 	cl_git_pass(git_diff_foreach(
1383 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1384 	cl_assert_equal_i(5, exp.files);
1385 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1386 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
1387 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
1388 
1389 	git_diff_free(diff);
1390 	git_tree_free(tree);
1391 	git_index_free(index);
1392 
1393 	git_buf_dispose(&c1);
1394 }
1395 
test_diff_rename__can_delete_unmodified_deltas(void)1396 void test_diff_rename__can_delete_unmodified_deltas(void)
1397 {
1398 	git_buf c1 = GIT_BUF_INIT;
1399 	git_index *index;
1400 	git_tree *tree;
1401 	git_diff *diff;
1402 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1403 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1404 	diff_expects exp;
1405 
1406 	cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
1407 	cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
1408 
1409 	cl_git_pass(
1410 		git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
1411 
1412 	cl_git_pass(git_repository_index(&index, g_repo));
1413 	cl_git_pass(git_index_read_tree(index, tree));
1414 	cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
1415 
1416 	diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
1417 
1418 	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
1419 
1420 	memset(&exp, 0, sizeof(exp));
1421 	cl_git_pass(git_diff_foreach(
1422 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1423 	cl_assert_equal_i(4, exp.files);
1424 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1425 	cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
1426 
1427 	opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED;
1428 	cl_git_pass(git_diff_find_similar(diff, &opts));
1429 
1430 	memset(&exp, 0, sizeof(exp));
1431 	cl_git_pass(git_diff_foreach(
1432 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1433 	cl_assert_equal_i(2, exp.files);
1434 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1435 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
1436 
1437 	git_diff_free(diff);
1438 	git_tree_free(tree);
1439 	git_index_free(index);
1440 
1441 	git_buf_dispose(&c1);
1442 }
1443 
test_diff_rename__matches_config_behavior(void)1444 void test_diff_rename__matches_config_behavior(void)
1445 {
1446 	const char *sha0 = INITIAL_COMMIT;
1447 	const char *sha1 = COPY_RENAME_COMMIT;
1448 	const char *sha2 = REWRITE_COPY_COMMIT;
1449 
1450 	git_tree *tree0, *tree1, *tree2;
1451 	git_config *cfg;
1452 	git_diff *diff;
1453 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1454 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1455 	diff_expects exp;
1456 
1457 	opts.flags = GIT_DIFF_FIND_BY_CONFIG;
1458 	tree0 = resolve_commit_oid_to_tree(g_repo, sha0);
1459 	tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
1460 	tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
1461 
1462 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
1463 	cl_git_pass(git_repository_config(&cfg, g_repo));
1464 
1465 	/* diff.renames = false; no rename detection should happen */
1466 	cl_git_pass(git_config_set_bool(cfg, "diff.renames", false));
1467 	cl_git_pass(git_diff_tree_to_tree(
1468 				&diff, g_repo, tree0, tree1, &diffopts));
1469 	memset(&exp, 0, sizeof(exp));
1470 	cl_git_pass(git_diff_find_similar(diff, &opts));
1471 	cl_git_pass(git_diff_foreach(diff,
1472 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1473 	cl_assert_equal_i(4, exp.files);
1474 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1475 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
1476 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1477 	git_diff_free(diff);
1478 
1479 	/* diff.renames = true; should act like -M */
1480 	cl_git_pass(git_config_set_bool(cfg, "diff.renames", true));
1481 	cl_git_pass(git_diff_tree_to_tree(
1482 				&diff, g_repo, tree0, tree1, &diffopts));
1483 	memset(&exp, 0, sizeof(exp));
1484 	cl_git_pass(git_diff_find_similar(diff, &opts));
1485 	cl_git_pass(git_diff_foreach(diff,
1486 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1487 	cl_assert_equal_i(3, exp.files);
1488 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1489 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
1490 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1491 	git_diff_free(diff);
1492 
1493 	/* diff.renames = copies; should act like -M -C */
1494 	cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
1495 	cl_git_pass(git_diff_tree_to_tree(
1496 				&diff, g_repo, tree1, tree2, &diffopts));
1497 	memset(&exp, 0, sizeof(exp));
1498 	cl_git_pass(git_diff_find_similar(diff, &opts));
1499 	cl_git_pass(git_diff_foreach(diff,
1500 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1501 	cl_assert_equal_i(4, exp.files);
1502 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1503 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
1504 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
1505 	git_diff_free(diff);
1506 
1507 	/* NULL find options is the same as GIT_DIFF_FIND_BY_CONFIG */
1508 	cl_git_pass(git_diff_tree_to_tree(
1509 				&diff, g_repo, tree1, tree2, &diffopts));
1510 	memset(&exp, 0, sizeof(exp));
1511 	cl_git_pass(git_diff_find_similar(diff, NULL));
1512 	cl_git_pass(git_diff_foreach(diff,
1513 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1514 	cl_assert_equal_i(4, exp.files);
1515 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1516 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
1517 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
1518 	git_diff_free(diff);
1519 
1520 	/* Cleanup */
1521 	git_tree_free(tree0);
1522 	git_tree_free(tree1);
1523 	git_tree_free(tree2);
1524 	git_config_free(cfg);
1525 }
1526 
test_diff_rename__can_override_thresholds_when_obeying_config(void)1527 void test_diff_rename__can_override_thresholds_when_obeying_config(void)
1528 {
1529 	const char *sha1 = COPY_RENAME_COMMIT;
1530 	const char *sha2 = REWRITE_COPY_COMMIT;
1531 
1532 	git_tree *tree1, *tree2;
1533 	git_config *cfg;
1534 	git_diff *diff;
1535 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1536 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1537 	diff_expects exp;
1538 
1539 	tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
1540 	tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
1541 
1542 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
1543 	opts.flags = GIT_DIFF_FIND_BY_CONFIG;
1544 
1545 	cl_git_pass(git_repository_config(&cfg, g_repo));
1546 	cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
1547 	git_config_free(cfg);
1548 
1549 	/* copy threshold = 96%, should see creation of ikeepsix.txt */
1550 	opts.copy_threshold = 96;
1551 	cl_git_pass(git_diff_tree_to_tree(
1552 				&diff, g_repo, tree1, tree2, &diffopts));
1553 	memset(&exp, 0, sizeof(exp));
1554 	cl_git_pass(git_diff_find_similar(diff, &opts));
1555 	cl_git_pass(git_diff_foreach(diff,
1556 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1557 	cl_assert_equal_i(4, exp.files);
1558 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1559 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
1560 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
1561 	git_diff_free(diff);
1562 
1563 	/* copy threshold = 20%, should see sixserving.txt => ikeepsix.txt */
1564 	opts.copy_threshold = 20;
1565 	cl_git_pass(git_diff_tree_to_tree(
1566 				&diff, g_repo, tree1, tree2, &diffopts));
1567 	memset(&exp, 0, sizeof(exp));
1568 	cl_git_pass(git_diff_find_similar(diff, &opts));
1569 	cl_git_pass(git_diff_foreach(diff,
1570 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1571 	cl_assert_equal_i(4, exp.files);
1572 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
1573 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
1574 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
1575 	git_diff_free(diff);
1576 
1577 	/* Cleanup */
1578 	git_tree_free(tree1);
1579 	git_tree_free(tree2);
1580 }
1581 
test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void)1582 void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void)
1583 {
1584 	const char *sha1 = REWRITE_COPY_COMMIT;
1585 	const char *sha2 = RENAME_MODIFICATION_COMMIT;
1586 
1587 	git_tree *tree1, *tree2;
1588 	git_config *cfg;
1589 	git_diff *diff;
1590 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1591 	git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
1592 	diff_expects exp;
1593 
1594 	tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
1595 	tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
1596 
1597 	diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
1598 	opts.flags = GIT_DIFF_FIND_BY_CONFIG;
1599 
1600 	cl_git_pass(git_repository_config(&cfg, g_repo));
1601 	cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
1602 	git_config_free(cfg);
1603 
1604 	/* Don't ignore whitespace; this should find a change in sixserving.txt */
1605 	opts.flags |= 0 | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE;
1606 	cl_git_pass(git_diff_tree_to_tree(
1607 				&diff, g_repo, tree1, tree2, &diffopts));
1608 	memset(&exp, 0, sizeof(exp));
1609 	cl_git_pass(git_diff_find_similar(diff, &opts));
1610 	cl_git_pass(git_diff_foreach(diff,
1611 				diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1612 	cl_assert_equal_i(5, exp.files);
1613 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
1614 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1615 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1616 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
1617 	git_diff_free(diff);
1618 
1619 	/* Cleanup */
1620 	git_tree_free(tree1);
1621 	git_tree_free(tree2);
1622 }
1623 
expect_files_renamed(const char * one,const char * two,uint32_t whitespace_flags)1624 static void expect_files_renamed(const char *one, const char *two, uint32_t whitespace_flags)
1625 {
1626 	git_index *index;
1627 	git_diff *diff = NULL;
1628 	diff_expects exp;
1629 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1630 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1631 
1632 	diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
1633 	findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED |
1634 		GIT_DIFF_FIND_AND_BREAK_REWRITES |
1635 		GIT_DIFF_FIND_RENAMES_FROM_REWRITES |
1636 		whitespace_flags;
1637 
1638 	cl_git_pass(git_repository_index(&index, g_repo));
1639 
1640 	cl_git_rewritefile("renames/ikeepsix.txt", one);
1641 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
1642 
1643 	cl_git_rmfile("renames/ikeepsix.txt");
1644 	cl_git_rewritefile("renames/ikeepsix2.txt", two);
1645 
1646 	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts));
1647 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1648 
1649 	memset(&exp, 0, sizeof(exp));
1650 
1651 	cl_git_pass(git_diff_foreach(
1652 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1653 	cl_assert_equal_i(1, exp.files);
1654 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
1655 
1656 	git_diff_free(diff);
1657 	git_index_free(index);
1658 }
1659 
1660 /* test some variations on empty and blank files */
test_diff_rename__empty_files_renamed(void)1661 void test_diff_rename__empty_files_renamed(void)
1662 {
1663 	/* empty files are identical when ignoring whitespace or not */
1664 	expect_files_renamed("", "", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
1665 	expect_files_renamed("", "",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1666 }
1667 
1668 /* test that blank files are similar when ignoring whitespace */
test_diff_rename__blank_files_renamed_when_ignoring_whitespace(void)1669 void test_diff_rename__blank_files_renamed_when_ignoring_whitespace(void)
1670 {
1671 	expect_files_renamed("", "\n\n",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1672 	expect_files_renamed("", "\r\n\r\n",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1673 	expect_files_renamed("\r\n\r\n", "\n\n\n",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1674 
1675 	expect_files_renamed("    ", "\n\n",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1676 	expect_files_renamed("   \n   \n", "\n\n",  GIT_DIFF_FIND_IGNORE_WHITESPACE);
1677 }
1678 
1679 /* blank files are not similar when whitespace is not ignored */
expect_files_not_renamed(const char * one,const char * two,uint32_t whitespace_flags)1680 static void expect_files_not_renamed(const char *one, const char *two, uint32_t whitespace_flags)
1681 {
1682 	git_index *index;
1683 	git_diff *diff = NULL;
1684 	diff_expects exp;
1685 	git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
1686 	git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
1687 
1688 	diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
1689 
1690 	findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED |
1691 		whitespace_flags;
1692 
1693 	cl_git_pass(git_repository_index(&index, g_repo));
1694 
1695 	cl_git_rewritefile("renames/ikeepsix.txt", one);
1696 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
1697 
1698 	cl_git_rmfile("renames/ikeepsix.txt");
1699 	cl_git_rewritefile("renames/ikeepsix2.txt", two);
1700 
1701 	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts));
1702 	cl_git_pass(git_diff_find_similar(diff, &findopts));
1703 
1704 	memset(&exp, 0, sizeof(exp));
1705 
1706 	cl_git_pass(git_diff_foreach(
1707 		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
1708 	cl_assert_equal_i(2, exp.files);
1709 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1710 	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1711 
1712 	git_diff_free(diff);
1713 	git_index_free(index);
1714 }
1715 
1716 /* test that blank files are similar when ignoring renames */
test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void)1717 void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void)
1718 {
1719 	expect_files_not_renamed("", "\r\n\r\n\r\n",  GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
1720 	expect_files_not_renamed("", "\n\n\n\n",  GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
1721 	expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n",  GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
1722 }
1723 
1724 /* test that 100% renames and copies emit the correct patch file
1725  * git diff --find-copies-harder -M100 -B100 \
1726  *          31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
1727  *          2bc7f351d20b53f1c72c16c4b036e491c478c49a
1728  */
test_diff_rename__identical(void)1729 void test_diff_rename__identical(void)
1730 {
1731 	const char *old_sha = INITIAL_COMMIT;
1732 	const char *new_sha = COPY_RENAME_COMMIT;
1733 	git_tree *old_tree, *new_tree;
1734     git_diff *diff;
1735 	git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
1736 	git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
1737 	git_buf diff_buf = GIT_BUF_INIT;
1738 	const char *expected =
1739 		"diff --git a/serving.txt b/sixserving.txt\n"
1740 		"similarity index 100%\n"
1741 		"rename from serving.txt\n"
1742 		"rename to sixserving.txt\n"
1743 		"diff --git a/sevencities.txt b/songofseven.txt\n"
1744 		"similarity index 100%\n"
1745 		"copy from sevencities.txt\n"
1746 		"copy to songofseven.txt\n";
1747 
1748 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
1749 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
1750 
1751 	diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
1752 	find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
1753 		GIT_DIFF_FIND_EXACT_MATCH_ONLY;
1754 
1755 	cl_git_pass(git_diff_tree_to_tree(&diff,
1756 		g_repo, old_tree, new_tree, &diff_opts));
1757 	cl_git_pass(git_diff_find_similar(diff, &find_opts));
1758 
1759 	cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
1760 
1761 	cl_assert_equal_s(expected, diff_buf.ptr);
1762 
1763 	git_buf_dispose(&diff_buf);
1764 	git_diff_free(diff);
1765 	git_tree_free(old_tree);
1766 	git_tree_free(new_tree);
1767 }
1768 
test_diff_rename__rewrite_and_delete(void)1769 void test_diff_rename__rewrite_and_delete(void)
1770 {
1771 	const char *old_sha = RENAME_MODIFICATION_COMMIT;
1772 	const char *new_sha = REWRITE_DELETE_COMMIT;
1773 	git_tree *old_tree, *new_tree;
1774 	git_diff *diff;
1775 	git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
1776 	git_buf diff_buf = GIT_BUF_INIT;
1777 	const char *expected =
1778 		"diff --git a/ikeepsix.txt b/ikeepsix.txt\n"
1779 		"deleted file mode 100644\n"
1780 		"index eaf4a3e..0000000\n"
1781 		"--- a/ikeepsix.txt\n"
1782 		"+++ /dev/null\n"
1783 		"@@ -1,27 +0,0 @@\n"
1784 		"-I Keep Six Honest Serving-Men\n"
1785 		"-=============================\n"
1786 		"-\n"
1787 		"-She sends'em abroad on her own affairs,\n"
1788 		"- From the second she opens her eyes—\n"
1789 		"-One million Hows, two million Wheres,\n"
1790 		"-And seven million Whys!\n"
1791 		"-\n"
1792 		"-I let them rest from nine till five,\n"
1793 		"- For I am busy then,\n"
1794 		"-As well as breakfast, lunch, and tea,\n"
1795 		"- For they are hungry men.\n"
1796 		"-But different folk have different views;\n"
1797 		"-I know a person small—\n"
1798 		"-She keeps ten million serving-men,\n"
1799 		"-Who get no rest at all!\n"
1800 		"-\n"
1801 		"-  -- Rudyard Kipling\n"
1802 		"-\n"
1803 		"-I KEEP six honest serving-men\n"
1804 		"- (They taught me all I knew);\n"
1805 		"-Their names are What and Why and When\n"
1806 		"- And How and Where and Who.\n"
1807 		"-I send them over land and sea,\n"
1808 		"- I send them east and west;\n"
1809 		"-But after they have worked for me,\n"
1810 		"- I give them all a rest.\n"
1811 		"diff --git a/songof7cities.txt b/songof7cities.txt\n"
1812 		"index 4210ffd..95ceb12 100644\n"
1813 		"--- a/songof7cities.txt\n"
1814 		"+++ b/songof7cities.txt\n"
1815 		"@@ -1,45 +1,45 @@\n"
1816 		"-The Song of Seven Cities\n"
1817 		"+THE SONG OF SEVEN CITIES\n"
1818 		" ------------------------\n"
1819 		" \n"
1820 		"-I WAS Lord of Cities very sumptuously builded.\n"
1821 		"-Seven roaring Cities paid me tribute from afar.\n"
1822 		"-Ivory their outposts were--the guardrooms of them gilded,\n"
1823 		"-And garrisoned with Amazons invincible in war.\n"
1824 		"-\n"
1825 		"-All the world went softly when it walked before my Cities--\n"
1826 		"-Neither King nor Army vexed my peoples at their toil,\n"
1827 		"-Never horse nor chariot irked or overbore my Cities,\n"
1828 		"-Never Mob nor Ruler questioned whence they drew their spoil.\n"
1829 		"-\n"
1830 		"-Banded, mailed and arrogant from sunrise unto sunset;\n"
1831 		"-Singing while they sacked it, they possessed the land at large.\n"
1832 		"-Yet when men would rob them, they resisted, they made onset\n"
1833 		"-And pierced the smoke of battle with a thousand-sabred charge.\n"
1834 		"-\n"
1835 		"-So they warred and trafficked only yesterday, my Cities.\n"
1836 		"-To-day there is no mark or mound of where my Cities stood.\n"
1837 		"-For the River rose at midnight and it washed away my Cities.\n"
1838 		"-They are evened with Atlantis and the towns before the Flood.\n"
1839 		"-\n"
1840 		"-Rain on rain-gorged channels raised the water-levels round them,\n"
1841 		"-Freshet backed on freshet swelled and swept their world from sight,\n"
1842 		"-Till the emboldened floods linked arms and, flashing forward, drowned them--\n"
1843 		"-Drowned my Seven Cities and their peoples in one night!\n"
1844 		"-\n"
1845 		"-Low among the alders lie their derelict foundations,\n"
1846 		"-The beams wherein they trusted and the plinths whereon they built--\n"
1847 		"-My rulers and their treasure and their unborn populations,\n"
1848 		"-Dead, destroyed, aborted, and defiled with mud and silt!\n"
1849 		"-\n"
1850 		"-The Daughters of the Palace whom they cherished in my Cities,\n"
1851 		"-My silver-tongued Princesses, and the promise of their May--\n"
1852 		"-Their bridegrooms of the June-tide--all have perished in my Cities,\n"
1853 		"-With the harsh envenomed virgins that can neither love nor play.\n"
1854 		"-\n"
1855 		"-I was Lord of Cities--I will build anew my Cities,\n"
1856 		"-Seven, set on rocks, above the wrath of any flood.\n"
1857 		"-Nor will I rest from search till I have filled anew my Cities\n"
1858 		"-With peoples undefeated of the dark, enduring blood.\n"
1859 		"+I WAS LORD OF CITIES VERY SUMPTUOUSLY BUILDED.\n"
1860 		"+SEVEN ROARING CITIES PAID ME TRIBUTE FROM AFAR.\n"
1861 		"+IVORY THEIR OUTPOSTS WERE--THE GUARDROOMS OF THEM GILDED,\n"
1862 		"+AND GARRISONED WITH AMAZONS INVINCIBLE IN WAR.\n"
1863 		"+\n"
1864 		"+ALL THE WORLD WENT SOFTLY WHEN IT WALKED BEFORE MY CITIES--\n"
1865 		"+NEITHER KING NOR ARMY VEXED MY PEOPLES AT THEIR TOIL,\n"
1866 		"+NEVER HORSE NOR CHARIOT IRKED OR OVERBORE MY CITIES,\n"
1867 		"+NEVER MOB NOR RULER QUESTIONED WHENCE THEY DREW THEIR SPOIL.\n"
1868 		"+\n"
1869 		"+BANDED, MAILED AND ARROGANT FROM SUNRISE UNTO SUNSET;\n"
1870 		"+SINGING WHILE THEY SACKED IT, THEY POSSESSED THE LAND AT LARGE.\n"
1871 		"+YET WHEN MEN WOULD ROB THEM, THEY RESISTED, THEY MADE ONSET\n"
1872 		"+AND PIERCED THE SMOKE OF BATTLE WITH A THOUSAND-SABRED CHARGE.\n"
1873 		"+\n"
1874 		"+SO THEY WARRED AND TRAFFICKED ONLY YESTERDAY, MY CITIES.\n"
1875 		"+TO-DAY THERE IS NO MARK OR MOUND OF WHERE MY CITIES STOOD.\n"
1876 		"+FOR THE RIVER ROSE AT MIDNIGHT AND IT WASHED AWAY MY CITIES.\n"
1877 		"+THEY ARE EVENED WITH ATLANTIS AND THE TOWNS BEFORE THE FLOOD.\n"
1878 		"+\n"
1879 		"+RAIN ON RAIN-GORGED CHANNELS RAISED THE WATER-LEVELS ROUND THEM,\n"
1880 		"+FRESHET BACKED ON FRESHET SWELLED AND SWEPT THEIR WORLD FROM SIGHT,\n"
1881 		"+TILL THE EMBOLDENED FLOODS LINKED ARMS AND, FLASHING FORWARD, DROWNED THEM--\n"
1882 		"+DROWNED MY SEVEN CITIES AND THEIR PEOPLES IN ONE NIGHT!\n"
1883 		"+\n"
1884 		"+LOW AMONG THE ALDERS LIE THEIR DERELICT FOUNDATIONS,\n"
1885 		"+THE BEAMS WHEREIN THEY TRUSTED AND THE PLINTHS WHEREON THEY BUILT--\n"
1886 		"+MY RULERS AND THEIR TREASURE AND THEIR UNBORN POPULATIONS,\n"
1887 		"+DEAD, DESTROYED, ABORTED, AND DEFILED WITH MUD AND SILT!\n"
1888 		"+\n"
1889 		"+THE DAUGHTERS OF THE PALACE WHOM THEY CHERISHED IN MY CITIES,\n"
1890 		"+MY SILVER-TONGUED PRINCESSES, AND THE PROMISE OF THEIR MAY--\n"
1891 		"+THEIR BRIDEGROOMS OF THE JUNE-TIDE--ALL HAVE PERISHED IN MY CITIES,\n"
1892 		"+WITH THE HARSH ENVENOMED VIRGINS THAT CAN NEITHER LOVE NOR PLAY.\n"
1893 		"+\n"
1894 		"+I WAS LORD OF CITIES--I WILL BUILD ANEW MY CITIES,\n"
1895 		"+SEVEN, SET ON ROCKS, ABOVE THE WRATH OF ANY FLOOD.\n"
1896 		"+NOR WILL I REST FROM SEARCH TILL I HAVE FILLED ANEW MY CITIES\n"
1897 		"+WITH PEOPLES UNDEFEATED OF THE DARK, ENDURING BLOOD.\n"
1898 		" \n"
1899 		" To the sound of trumpets shall their seed restore my Cities\n"
1900 		" Wealthy and well-weaponed, that once more may I behold\n";
1901 
1902 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
1903 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
1904 
1905 	find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
1906 
1907 	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL));
1908 	cl_git_pass(git_diff_find_similar(diff, &find_opts));
1909 
1910 	cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
1911 
1912 	cl_assert_equal_s(expected, diff_buf.ptr);
1913 
1914 	git_buf_dispose(&diff_buf);
1915 	git_diff_free(diff);
1916 	git_tree_free(old_tree);
1917 	git_tree_free(new_tree);
1918 }
1919 
test_diff_rename__delete_and_rename(void)1920 void test_diff_rename__delete_and_rename(void)
1921 {
1922 	const char *old_sha = RENAME_MODIFICATION_COMMIT;
1923 	const char *new_sha = DELETE_RENAME_COMMIT;
1924 	git_tree *old_tree, *new_tree;
1925 	git_diff *diff;
1926 	git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
1927 	git_buf diff_buf = GIT_BUF_INIT;
1928 	const char *expected =
1929 		"diff --git a/sixserving.txt b/sixserving.txt\n"
1930 		"deleted file mode 100644\n"
1931 		"index f90d4fc..0000000\n"
1932 		"--- a/sixserving.txt\n"
1933 		"+++ /dev/null\n"
1934 		"@@ -1,25 +0,0 @@\n"
1935 		"-I  KEEP  six  honest  serving-men\n"
1936 		"-  (They  taught  me  all  I  knew);\n"
1937 		"-Their  names  are  What  and  Why  and  When\n"
1938 		"-  And  How  and  Where  and  Who.\n"
1939 		"-I  send  them  over  land  and  sea,\n"
1940 		"-  I  send  them  east  and  west;\n"
1941 		"-But  after  they  have  worked  for  me,\n"
1942 		"-  I  give  them  all  a  rest.\n"
1943 		"-\n"
1944 		"-I  let  them  rest  from  nine  till  five,\n"
1945 		"-  For  I  am  busy  then,\n"
1946 		"-As  well  as  breakfast,  lunch,  and  tea,\n"
1947 		"-  For  they  are  hungry  men.\n"
1948 		"-But  different  folk  have  different  views;\n"
1949 		"-I  know  a  person  small—\n"
1950 		"-She  keeps  ten  million  serving-men,\n"
1951 		"-Who  get  no  rest  at  all!\n"
1952 		"-\n"
1953 		"-She  sends'em  abroad  on  her  own  affairs,\n"
1954 		"-  From  the  second  she  opens  her  eyes—\n"
1955 		"-One  million  Hows,  two  million  Wheres,\n"
1956 		"-And  seven  million  Whys!\n"
1957 		"-\n"
1958 		"-    --  Rudyard  Kipling\n"
1959 		"-\n"
1960 		"diff --git a/songof7cities.txt b/sixserving.txt\n"
1961 		"similarity index 100%\n"
1962 		"rename from songof7cities.txt\n"
1963 		"rename to sixserving.txt\n";
1964 
1965 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
1966 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
1967 
1968 	find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
1969 
1970 	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL));
1971 	cl_git_pass(git_diff_find_similar(diff, &find_opts));
1972 
1973 	cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
1974 
1975 	cl_assert_equal_s(expected, diff_buf.ptr);
1976 
1977 	git_buf_dispose(&diff_buf);
1978 	git_diff_free(diff);
1979 	git_tree_free(old_tree);
1980 	git_tree_free(new_tree);
1981 }
1982 
1983 /*
1984  * The break_rewrite branch contains a testcase reduced from
1985  * a real-world scenario, rather than being "constructed" like
1986  * the above tests seem to be. There are two commits layered
1987  * on top of the repo's initial commit; the base commit which
1988  * clears out the files from the initial commit and installs
1989  * four files. And then there's the modification commit which
1990  * mutates the files in such a way as to trigger the bug in
1991  * libgit2.
1992  * commit db98035f715427eef1f5e17f03e1801c05301e9e
1993  *   serving.txt     (deleted)
1994  *   sevencities.txt (deleted)
1995  *   AAA             (313 lines)
1996  *   BBB             (314 lines)
1997  *   CCC             (704 lines)
1998  *   DDD             (314 lines, identical to BBB)
1999  * commit 7e7bfb88ba9bc65fd700fee1819cf1c317aafa56
2000  *   This deletes CCC and makes slight modifications
2001  *   to AAA, BBB, and DDD. The find_best_matches loop
2002  *   for git_diff_find_similar computes the following:
2003  *   CCC    moved to    AAA     (similarity 91)
2004  *   CCC    copied to   AAA     (similarity 91)
2005  *   DDD    moved to    BBB     (similarity 52)
2006  *   CCC    copied to   BBB     (similarity 90)
2007  *   BBB    moved to    DDD     (similarity 52)
2008  *   CCC    copied to   DDD     (similarity 90)
2009  * The code to rewrite the diffs by resolving these
2010  * copies/renames would resolve the BBB <-> DDD moves
2011  * but then still leave BBB as a rename target for
2012  * the deleted file CCC. Since the split flag on BBB
2013  * was cleared, this would trigger an error.
2014  */
test_diff_rename__break_rewrite(void)2015 void test_diff_rename__break_rewrite(void)
2016 {
2017 	const char *old_sha = BREAK_REWRITE_BASE_COMMIT;
2018 	const char *new_sha = BREAK_REWRITE_COMMIT;
2019 	git_tree *old_tree, *new_tree;
2020 	git_diff *diff;
2021 	git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
2022 
2023 	old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
2024 	new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
2025 
2026 	find_opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | GIT_DIFF_BREAK_REWRITES | GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY;
2027 
2028 	cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL));
2029 	cl_git_pass(git_diff_find_similar(diff, &find_opts));
2030 
2031 	git_diff_free(diff);
2032 	git_tree_free(old_tree);
2033 	git_tree_free(new_tree);
2034 }
2035