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