]> git.proxmox.com Git - libgit2.git/blob - tests/diff/workdir.c
Merge pull request #3185 from libgit2/cmn/foreach-cancel-loose
[libgit2.git] / tests / diff / workdir.c
1 #include "clar_libgit2.h"
2 #include "diff_helpers.h"
3 #include "repository.h"
4 #include "git2/sys/diff.h"
5
6 static git_repository *g_repo = NULL;
7
8 void test_diff_workdir__cleanup(void)
9 {
10 cl_git_sandbox_cleanup();
11 }
12
13 void test_diff_workdir__to_index(void)
14 {
15 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
16 git_diff *diff = NULL;
17 diff_expects exp;
18 int use_iterator;
19
20 g_repo = cl_git_sandbox_init("status");
21
22 opts.context_lines = 3;
23 opts.interhunk_lines = 1;
24 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
25
26 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
27
28 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
29 memset(&exp, 0, sizeof(exp));
30
31 if (use_iterator)
32 cl_git_pass(diff_foreach_via_iterator(
33 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
34 else
35 cl_git_pass(git_diff_foreach(
36 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
37
38 /* to generate these values:
39 * - cd to tests/resources/status,
40 * - mv .gitted .git
41 * - git diff --name-status
42 * - git diff
43 * - mv .git .gitted
44 */
45 cl_assert_equal_i(13, exp.files);
46 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
47 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
48 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
49 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
50 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
51
52 cl_assert_equal_i(8, exp.hunks);
53
54 cl_assert_equal_i(14, exp.lines);
55 cl_assert_equal_i(5, exp.line_ctxt);
56 cl_assert_equal_i(4, exp.line_adds);
57 cl_assert_equal_i(5, exp.line_dels);
58 }
59
60 {
61 git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
62 cl_git_pass(git_diff_get_perfdata(&perf, diff));
63 cl_assert_equal_sz(
64 13 /* in root */ + 3 /* in subdir */, perf.stat_calls);
65 cl_assert_equal_sz(5, perf.oid_calculations);
66 }
67
68 git_diff_free(diff);
69 }
70
71 void test_diff_workdir__to_index_with_conflicts(void)
72 {
73 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
74 git_diff *diff = NULL;
75 git_index *index;
76 git_index_entry our_entry = {{0}}, their_entry = {{0}};
77 diff_expects exp = {0};
78
79 g_repo = cl_git_sandbox_init("status");
80
81 opts.context_lines = 3;
82 opts.interhunk_lines = 1;
83
84 /* Adding an entry that represents a rename gets two files in conflict */
85 our_entry.path = "subdir/modified_file";
86 our_entry.mode = 0100644;
87
88 their_entry.path = "subdir/rename_conflict";
89 their_entry.mode = 0100644;
90
91 cl_git_pass(git_repository_index(&index, g_repo));
92 cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry));
93
94 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts));
95
96 cl_git_pass(diff_foreach_via_iterator(
97 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
98
99 cl_assert_equal_i(9, exp.files);
100 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
101 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
102 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
103 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_CONFLICTED]);
104
105 cl_assert_equal_i(7, exp.hunks);
106
107 cl_assert_equal_i(12, exp.lines);
108 cl_assert_equal_i(4, exp.line_ctxt);
109 cl_assert_equal_i(3, exp.line_adds);
110 cl_assert_equal_i(5, exp.line_dels);
111
112 git_diff_free(diff);
113 git_index_free(index);
114 }
115
116 void test_diff_workdir__to_index_with_assume_unchanged(void)
117 {
118 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
119 git_diff *diff = NULL;
120 git_index *idx = NULL;
121 diff_expects exp;
122 const git_index_entry *iep;
123 git_index_entry ie;
124
125 g_repo = cl_git_sandbox_init("status");
126
127 /* do initial diff */
128
129 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
130 memset(&exp, 0, sizeof(exp));
131 cl_git_pass(git_diff_foreach(
132 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
133 cl_assert_equal_i(8, exp.files);
134 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
135 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
136 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
137 git_diff_free(diff);
138
139 /* mark a couple of entries with ASSUME_UNCHANGED */
140
141 cl_git_pass(git_repository_index(&idx, g_repo));
142
143 cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL);
144 memcpy(&ie, iep, sizeof(ie));
145 ie.flags |= GIT_IDXENTRY_VALID;
146 cl_git_pass(git_index_add(idx, &ie));
147
148 cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL);
149 memcpy(&ie, iep, sizeof(ie));
150 ie.flags |= GIT_IDXENTRY_VALID;
151 cl_git_pass(git_index_add(idx, &ie));
152
153 cl_git_pass(git_index_write(idx));
154 git_index_free(idx);
155
156 /* redo diff and see that entries are skipped */
157
158 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
159 memset(&exp, 0, sizeof(exp));
160 cl_git_pass(git_diff_foreach(
161 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
162 cl_assert_equal_i(6, exp.files);
163 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
164 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]);
165 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
166 git_diff_free(diff);
167
168 }
169
170 void test_diff_workdir__to_tree(void)
171 {
172 /* grabbed a couple of commit oids from the history of the attr repo */
173 const char *a_commit = "26a125ee1bf"; /* the current HEAD */
174 const char *b_commit = "0017bd4ab1ec3"; /* the start */
175 git_tree *a, *b;
176 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
177 git_diff *diff = NULL;
178 git_diff *diff2 = NULL;
179 diff_expects exp;
180 int use_iterator;
181
182 g_repo = cl_git_sandbox_init("status");
183
184 a = resolve_commit_oid_to_tree(g_repo, a_commit);
185 b = resolve_commit_oid_to_tree(g_repo, b_commit);
186
187 opts.context_lines = 3;
188 opts.interhunk_lines = 1;
189 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
190
191 /* You can't really generate the equivalent of git_diff_tree_to_workdir()
192 * using C git. It really wants to interpose the index into the diff.
193 *
194 * To validate the following results with command line git, I ran the
195 * following:
196 * - git ls-tree 26a125
197 * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
198 * The results are documented at the bottom of this file in the
199 * long comment entitled "PREPARATION OF TEST DATA".
200 */
201 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
202
203 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
204 memset(&exp, 0, sizeof(exp));
205
206 if (use_iterator)
207 cl_git_pass(diff_foreach_via_iterator(
208 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
209 else
210 cl_git_pass(git_diff_foreach(
211 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
212
213 cl_assert_equal_i(14, exp.files);
214 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
215 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
216 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
217 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
218 cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNTRACKED]);
219 }
220
221 /* Since there is no git diff equivalent, let's just assume that the
222 * text diffs produced by git_diff_foreach are accurate here. We will
223 * do more apples-to-apples test comparison below.
224 */
225
226 git_diff_free(diff);
227 diff = NULL;
228 memset(&exp, 0, sizeof(exp));
229
230 /* This is a compatible emulation of "git diff <sha>" which looks like
231 * a workdir to tree diff (even though it is not really). This is what
232 * you would get from "git diff --name-status 26a125ee1bf"
233 */
234 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
235 cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
236 cl_git_pass(git_diff_merge(diff, diff2));
237 git_diff_free(diff2);
238
239 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
240 memset(&exp, 0, sizeof(exp));
241
242 if (use_iterator)
243 cl_git_pass(diff_foreach_via_iterator(
244 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
245 else
246 cl_git_pass(git_diff_foreach(
247 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
248
249 cl_assert_equal_i(15, exp.files);
250 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
251 cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
252 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
253 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
254 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
255
256 cl_assert_equal_i(11, exp.hunks);
257
258 cl_assert_equal_i(17, exp.lines);
259 cl_assert_equal_i(4, exp.line_ctxt);
260 cl_assert_equal_i(8, exp.line_adds);
261 cl_assert_equal_i(5, exp.line_dels);
262 }
263
264 git_diff_free(diff);
265 diff = NULL;
266 memset(&exp, 0, sizeof(exp));
267
268 /* Again, emulating "git diff <sha>" for testing purposes using
269 * "git diff --name-status 0017bd4ab1ec3" instead.
270 */
271 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
272 cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
273 cl_git_pass(git_diff_merge(diff, diff2));
274 git_diff_free(diff2);
275
276 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
277 memset(&exp, 0, sizeof(exp));
278
279 if (use_iterator)
280 cl_git_pass(diff_foreach_via_iterator(
281 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
282 else
283 cl_git_pass(git_diff_foreach(
284 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
285
286 cl_assert_equal_i(16, exp.files);
287 cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]);
288 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
289 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
290 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
291 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
292
293 cl_assert_equal_i(12, exp.hunks);
294
295 cl_assert_equal_i(19, exp.lines);
296 cl_assert_equal_i(3, exp.line_ctxt);
297 cl_assert_equal_i(12, exp.line_adds);
298 cl_assert_equal_i(4, exp.line_dels);
299 }
300
301 git_diff_free(diff);
302
303 /* Let's try that once more with a reversed diff */
304
305 opts.flags |= GIT_DIFF_REVERSE;
306
307 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
308 cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
309 cl_git_pass(git_diff_merge(diff, diff2));
310 git_diff_free(diff2);
311
312 memset(&exp, 0, sizeof(exp));
313
314 cl_git_pass(git_diff_foreach(
315 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
316
317 cl_assert_equal_i(16, exp.files);
318 cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
319 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]);
320 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
321 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
322 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
323
324 cl_assert_equal_i(12, exp.hunks);
325
326 cl_assert_equal_i(19, exp.lines);
327 cl_assert_equal_i(3, exp.line_ctxt);
328 cl_assert_equal_i(12, exp.line_dels);
329 cl_assert_equal_i(4, exp.line_adds);
330
331 git_diff_free(diff);
332
333 /* all done now */
334
335 git_tree_free(a);
336 git_tree_free(b);
337 }
338
339 void test_diff_workdir__to_index_with_pathspec(void)
340 {
341 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
342 git_diff *diff = NULL;
343 diff_expects exp;
344 char *pathspec = NULL;
345 int use_iterator;
346
347 g_repo = cl_git_sandbox_init("status");
348
349 opts.context_lines = 3;
350 opts.interhunk_lines = 1;
351 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
352 opts.pathspec.strings = &pathspec;
353 opts.pathspec.count = 1;
354
355 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
356
357 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
358 memset(&exp, 0, sizeof(exp));
359
360 if (use_iterator)
361 cl_git_pass(diff_foreach_via_iterator(
362 diff, diff_file_cb, NULL, NULL, &exp));
363 else
364 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
365
366 cl_assert_equal_i(13, exp.files);
367 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
368 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
369 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
370 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
371 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
372 }
373
374 git_diff_free(diff);
375
376 pathspec = "modified_file";
377
378 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
379
380 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
381 memset(&exp, 0, sizeof(exp));
382
383 if (use_iterator)
384 cl_git_pass(diff_foreach_via_iterator(
385 diff, diff_file_cb, NULL, NULL, &exp));
386 else
387 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
388
389 cl_assert_equal_i(1, exp.files);
390 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
391 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
392 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
393 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
394 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
395 }
396
397 git_diff_free(diff);
398
399 pathspec = "subdir";
400
401 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
402
403 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
404 memset(&exp, 0, sizeof(exp));
405
406 if (use_iterator)
407 cl_git_pass(diff_foreach_via_iterator(
408 diff, diff_file_cb, NULL, NULL, &exp));
409 else
410 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
411
412 cl_assert_equal_i(3, exp.files);
413 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
414 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
415 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
416 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
417 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
418 }
419
420 git_diff_free(diff);
421
422 pathspec = "*_deleted";
423
424 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
425
426 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
427 memset(&exp, 0, sizeof(exp));
428
429 if (use_iterator)
430 cl_git_pass(diff_foreach_via_iterator(
431 diff, diff_file_cb, NULL, NULL, &exp));
432 else
433 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
434
435 cl_assert_equal_i(2, exp.files);
436 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
437 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
438 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
439 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
440 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
441 }
442
443 git_diff_free(diff);
444 }
445
446 void test_diff_workdir__filemode_changes(void)
447 {
448 git_diff *diff = NULL;
449 diff_expects exp;
450 int use_iterator;
451
452 if (!cl_is_chmod_supported())
453 return;
454
455 g_repo = cl_git_sandbox_init("issue_592");
456
457 cl_repo_set_bool(g_repo, "core.filemode", true);
458
459 /* test once with no mods */
460
461 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
462
463 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
464 memset(&exp, 0, sizeof(exp));
465
466 if (use_iterator)
467 cl_git_pass(diff_foreach_via_iterator(
468 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
469 else
470 cl_git_pass(git_diff_foreach(
471 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
472
473 cl_assert_equal_i(0, exp.files);
474 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
475 cl_assert_equal_i(0, exp.hunks);
476 }
477
478 git_diff_free(diff);
479
480 /* chmod file and test again */
481
482 cl_assert(cl_toggle_filemode("issue_592/a.txt"));
483
484 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
485
486 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
487 memset(&exp, 0, sizeof(exp));
488
489 if (use_iterator)
490 cl_git_pass(diff_foreach_via_iterator(
491 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
492 else
493 cl_git_pass(git_diff_foreach(
494 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
495
496 cl_assert_equal_i(1, exp.files);
497 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
498 cl_assert_equal_i(0, exp.hunks);
499 }
500
501 git_diff_free(diff);
502
503 cl_assert(cl_toggle_filemode("issue_592/a.txt"));
504 }
505
506 void test_diff_workdir__filemode_changes_with_filemode_false(void)
507 {
508 git_diff *diff = NULL;
509 diff_expects exp;
510
511 if (!cl_is_chmod_supported())
512 return;
513
514 g_repo = cl_git_sandbox_init("issue_592");
515
516 cl_repo_set_bool(g_repo, "core.filemode", false);
517
518 /* test once with no mods */
519
520 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
521
522 memset(&exp, 0, sizeof(exp));
523 cl_git_pass(git_diff_foreach(
524 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
525
526 cl_assert_equal_i(0, exp.files);
527 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
528 cl_assert_equal_i(0, exp.hunks);
529
530 git_diff_free(diff);
531
532 /* chmod file and test again */
533
534 cl_assert(cl_toggle_filemode("issue_592/a.txt"));
535
536 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
537
538 memset(&exp, 0, sizeof(exp));
539 cl_git_pass(git_diff_foreach(
540 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
541
542 cl_assert_equal_i(0, exp.files);
543 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
544 cl_assert_equal_i(0, exp.hunks);
545
546 git_diff_free(diff);
547
548 cl_assert(cl_toggle_filemode("issue_592/a.txt"));
549 }
550
551 void test_diff_workdir__head_index_and_workdir_all_differ(void)
552 {
553 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
554 git_diff *diff_i2t = NULL, *diff_w2i = NULL;
555 diff_expects exp;
556 char *pathspec = "staged_changes_modified_file";
557 git_tree *tree;
558 int use_iterator;
559
560 /* For this file,
561 * - head->index diff has 1 line of context, 1 line of diff
562 * - index->workdir diff has 2 lines of context, 1 line of diff
563 * but
564 * - head->workdir diff has 1 line of context, 2 lines of diff
565 * Let's make sure the right one is returned from each fn.
566 */
567
568 g_repo = cl_git_sandbox_init("status");
569
570 tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
571
572 opts.pathspec.strings = &pathspec;
573 opts.pathspec.count = 1;
574
575 cl_git_pass(git_diff_tree_to_index(&diff_i2t, g_repo, tree, NULL, &opts));
576 cl_git_pass(git_diff_index_to_workdir(&diff_w2i, g_repo, NULL, &opts));
577
578 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
579 memset(&exp, 0, sizeof(exp));
580
581 if (use_iterator)
582 cl_git_pass(diff_foreach_via_iterator(
583 diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
584 else
585 cl_git_pass(git_diff_foreach(
586 diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
587
588 cl_assert_equal_i(1, exp.files);
589 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
590 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
591 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
592 cl_assert_equal_i(1, exp.hunks);
593 cl_assert_equal_i(2, exp.lines);
594 cl_assert_equal_i(1, exp.line_ctxt);
595 cl_assert_equal_i(1, exp.line_adds);
596 cl_assert_equal_i(0, exp.line_dels);
597 }
598
599 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
600 memset(&exp, 0, sizeof(exp));
601
602 if (use_iterator)
603 cl_git_pass(diff_foreach_via_iterator(
604 diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
605 else
606 cl_git_pass(git_diff_foreach(
607 diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
608
609 cl_assert_equal_i(1, exp.files);
610 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
611 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
612 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
613 cl_assert_equal_i(1, exp.hunks);
614 cl_assert_equal_i(3, exp.lines);
615 cl_assert_equal_i(2, exp.line_ctxt);
616 cl_assert_equal_i(1, exp.line_adds);
617 cl_assert_equal_i(0, exp.line_dels);
618 }
619
620 cl_git_pass(git_diff_merge(diff_i2t, diff_w2i));
621
622 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
623 memset(&exp, 0, sizeof(exp));
624
625 if (use_iterator)
626 cl_git_pass(diff_foreach_via_iterator(
627 diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
628 else
629 cl_git_pass(git_diff_foreach(
630 diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
631
632 cl_assert_equal_i(1, exp.files);
633 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
634 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
635 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
636 cl_assert_equal_i(1, exp.hunks);
637 cl_assert_equal_i(3, exp.lines);
638 cl_assert_equal_i(1, exp.line_ctxt);
639 cl_assert_equal_i(2, exp.line_adds);
640 cl_assert_equal_i(0, exp.line_dels);
641 }
642
643 git_diff_free(diff_i2t);
644 git_diff_free(diff_w2i);
645
646 git_tree_free(tree);
647 }
648
649 void test_diff_workdir__eof_newline_changes(void)
650 {
651 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
652 git_diff *diff = NULL;
653 diff_expects exp;
654 char *pathspec = "current_file";
655 int use_iterator;
656
657 g_repo = cl_git_sandbox_init("status");
658
659 opts.pathspec.strings = &pathspec;
660 opts.pathspec.count = 1;
661
662 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
663
664 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
665 memset(&exp, 0, sizeof(exp));
666
667 if (use_iterator)
668 cl_git_pass(diff_foreach_via_iterator(
669 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
670 else
671 cl_git_pass(git_diff_foreach(
672 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
673
674 cl_assert_equal_i(0, exp.files);
675 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
676 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
677 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
678 cl_assert_equal_i(0, exp.hunks);
679 cl_assert_equal_i(0, exp.lines);
680 cl_assert_equal_i(0, exp.line_ctxt);
681 cl_assert_equal_i(0, exp.line_adds);
682 cl_assert_equal_i(0, exp.line_dels);
683 }
684
685 git_diff_free(diff);
686
687 cl_git_append2file("status/current_file", "\n");
688
689 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
690
691 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
692 memset(&exp, 0, sizeof(exp));
693
694 if (use_iterator)
695 cl_git_pass(diff_foreach_via_iterator(
696 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
697 else
698 cl_git_pass(git_diff_foreach(
699 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
700
701 cl_assert_equal_i(1, exp.files);
702 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
703 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
704 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
705 cl_assert_equal_i(1, exp.hunks);
706 cl_assert_equal_i(2, exp.lines);
707 cl_assert_equal_i(1, exp.line_ctxt);
708 cl_assert_equal_i(1, exp.line_adds);
709 cl_assert_equal_i(0, exp.line_dels);
710 }
711
712 git_diff_free(diff);
713
714 cl_git_rewritefile("status/current_file", "current_file");
715
716 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
717
718 for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
719 memset(&exp, 0, sizeof(exp));
720
721 if (use_iterator)
722 cl_git_pass(diff_foreach_via_iterator(
723 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
724 else
725 cl_git_pass(git_diff_foreach(
726 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
727
728 cl_assert_equal_i(1, exp.files);
729 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
730 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
731 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
732 cl_assert_equal_i(1, exp.hunks);
733 cl_assert_equal_i(3, exp.lines);
734 cl_assert_equal_i(0, exp.line_ctxt);
735 cl_assert_equal_i(1, exp.line_adds);
736 cl_assert_equal_i(2, exp.line_dels);
737 }
738
739 git_diff_free(diff);
740 }
741
742 /* PREPARATION OF TEST DATA
743 *
744 * Since there is no command line equivalent of git_diff_tree_to_workdir,
745 * it was a bit of a pain to confirm that I was getting the expected
746 * results in the first part of this tests. Here is what I ended up
747 * doing to set my expectation for the file counts and results:
748 *
749 * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows:
750 *
751 * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
752 * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted
753 * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file
754 * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes
755 * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted
756 * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file
757 * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted
758 * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
759 * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
760 * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file
761 * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file
762 * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
763 *
764 * --------
765 *
766 * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
767 *
768 * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
769 * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file
770 * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file
771 * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file
772 * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes
773 * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file
774 * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
775 * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file
776 * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file
777 * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
778 * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file
779 * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file
780 * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
781 *
782 * --------
783 *
784 * A - current_file (UNMODIFIED) -> not in results
785 * B D file_deleted
786 * M I ignored_file (IGNORED)
787 * C M modified_file
788 * N U new_file (UNTRACKED)
789 * D M staged_changes
790 * E D staged_changes_file_deleted
791 * F M staged_changes_modified_file
792 * G D staged_delete_file_deleted
793 * H - staged_delete_modified_file (UNMODIFIED) -> not in results
794 * O U staged_new_file
795 * P U staged_new_file_modified_file
796 * I - subdir/current_file (UNMODIFIED) -> not in results
797 * J D subdir/deleted_file
798 * K M subdir/modified_file
799 * Q U subdir/new_file
800 * L - subdir.txt (UNMODIFIED) -> not in results
801 *
802 * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR
803 */
804
805
806 void test_diff_workdir__larger_hunks(void)
807 {
808 const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69";
809 const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
810 git_tree *a, *b;
811 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
812 size_t i, d, num_d, h, num_h, l, num_l;
813
814 g_repo = cl_git_sandbox_init("diff");
815
816 cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
817 cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
818
819 opts.context_lines = 1;
820 opts.interhunk_lines = 0;
821
822 for (i = 0; i <= 2; ++i) {
823 git_diff *diff = NULL;
824 git_patch *patch;
825 const git_diff_hunk *hunk;
826 const git_diff_line *line;
827
828 /* okay, this is a bit silly, but oh well */
829 switch (i) {
830 case 0:
831 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
832 break;
833 case 1:
834 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
835 break;
836 case 2:
837 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, b, &opts));
838 break;
839 }
840
841 num_d = git_diff_num_deltas(diff);
842 cl_assert_equal_i(2, (int)num_d);
843
844 for (d = 0; d < num_d; ++d) {
845 cl_git_pass(git_patch_from_diff(&patch, diff, d));
846 cl_assert(patch);
847
848 num_h = git_patch_num_hunks(patch);
849 for (h = 0; h < num_h; h++) {
850 cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h));
851
852 for (l = 0; l < num_l; ++l) {
853 cl_git_pass(
854 git_patch_get_line_in_hunk(&line, patch, h, l));
855 cl_assert(line);
856 }
857
858 /* confirm fail after the last item */
859 cl_git_fail(
860 git_patch_get_line_in_hunk(&line, patch, h, num_l));
861 }
862
863 /* confirm fail after the last item */
864 cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h));
865
866 git_patch_free(patch);
867 }
868
869 git_diff_free(diff);
870 }
871
872 git_tree_free(a);
873 git_tree_free(b);
874 }
875
876 /* Set up a test that exercises this code. The easiest test using existing
877 * test data is probably to create a sandbox of submod2 and then run a
878 * git_diff_tree_to_workdir against tree
879 * 873585b94bdeabccea991ea5e3ec1a277895b698. As for what you should actually
880 * test, you can start by just checking that the number of lines of diff
881 * content matches the actual output of git diff. That will at least
882 * demonstrate that the submodule content is being used to generate somewhat
883 * comparable outputs. It is a test that would fail without this code and
884 * will succeed with it.
885 */
886
887 #include "../submodule/submodule_helpers.h"
888
889 void test_diff_workdir__submodules(void)
890 {
891 const char *a_commit = "873585b94bdeabccea991ea5e3ec1a277895b698";
892 git_tree *a;
893 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
894 git_diff *diff = NULL;
895 diff_expects exp;
896
897 g_repo = setup_fixture_submod2();
898
899 a = resolve_commit_oid_to_tree(g_repo, a_commit);
900
901 opts.flags =
902 GIT_DIFF_INCLUDE_UNTRACKED |
903 GIT_DIFF_INCLUDE_IGNORED |
904 GIT_DIFF_RECURSE_UNTRACKED_DIRS |
905 GIT_DIFF_SHOW_UNTRACKED_CONTENT;
906
907 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
908
909 /* diff_print(stderr, diff); */
910
911 /* essentially doing: git diff 873585b94bdeabccea991ea5e3ec1a277895b698 */
912
913 memset(&exp, 0, sizeof(exp));
914
915 cl_git_pass(git_diff_foreach(
916 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
917
918 /* so "git diff 873585" returns:
919 * M .gitmodules
920 * A just_a_dir/contents
921 * A just_a_file
922 * A sm_added_and_uncommited
923 * A sm_changed_file
924 * A sm_changed_head
925 * A sm_changed_index
926 * A sm_changed_untracked_file
927 * M sm_missing_commits
928 * A sm_unchanged
929 * which is a little deceptive because of the difference between the
930 * "git diff <treeish>" results from "git_diff_tree_to_workdir". The
931 * only significant difference is that those Added items will show up
932 * as Untracked items in the pure libgit2 diff.
933 *
934 * Then add in the two extra untracked items "not" and "not-submodule"
935 * to get the 12 files reported here.
936 */
937
938 cl_assert_equal_i(12, exp.files);
939
940 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
941 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
942 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
943 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
944 cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]);
945
946 /* the following numbers match "git diff 873585" exactly */
947
948 cl_assert_equal_i(9, exp.hunks);
949
950 cl_assert_equal_i(33, exp.lines);
951 cl_assert_equal_i(2, exp.line_ctxt);
952 cl_assert_equal_i(30, exp.line_adds);
953 cl_assert_equal_i(1, exp.line_dels);
954
955 git_diff_free(diff);
956 git_tree_free(a);
957 }
958
959 void test_diff_workdir__cannot_diff_against_a_bare_repository(void)
960 {
961 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
962 git_diff *diff = NULL;
963 git_tree *tree;
964
965 g_repo = cl_git_sandbox_init("testrepo.git");
966
967 cl_assert_equal_i(
968 GIT_EBAREREPO, git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
969
970 cl_git_pass(git_repository_head_tree(&tree, g_repo));
971
972 cl_assert_equal_i(
973 GIT_EBAREREPO, git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
974
975 git_tree_free(tree);
976 }
977
978 void test_diff_workdir__to_null_tree(void)
979 {
980 git_diff *diff;
981 diff_expects exp;
982 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
983
984 opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
985 GIT_DIFF_RECURSE_UNTRACKED_DIRS;
986
987 g_repo = cl_git_sandbox_init("status");
988
989 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
990
991 memset(&exp, 0, sizeof(exp));
992
993 cl_git_pass(git_diff_foreach(
994 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
995
996 cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]);
997
998 git_diff_free(diff);
999 }
1000
1001 void test_diff_workdir__checks_options_version(void)
1002 {
1003 git_diff *diff;
1004 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1005 const git_error *err;
1006
1007 g_repo = cl_git_sandbox_init("status");
1008
1009 opts.version = 0;
1010 cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
1011 err = giterr_last();
1012 cl_assert_equal_i(GITERR_INVALID, err->klass);
1013
1014 giterr_clear();
1015 opts.version = 1024;
1016 cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
1017 err = giterr_last();
1018 cl_assert_equal_i(GITERR_INVALID, err->klass);
1019 }
1020
1021 void test_diff_workdir__can_diff_empty_file(void)
1022 {
1023 git_diff *diff;
1024 git_tree *tree;
1025 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1026 struct stat st;
1027 git_patch *patch;
1028
1029 g_repo = cl_git_sandbox_init("attr_index");
1030
1031 tree = resolve_commit_oid_to_tree(g_repo, "3812cfef3661"); /* HEAD */
1032
1033 /* baseline - make sure there are no outstanding diffs */
1034
1035 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
1036 cl_assert_equal_i(2, (int)git_diff_num_deltas(diff));
1037 git_diff_free(diff);
1038
1039 /* empty contents of file */
1040
1041 cl_git_rewritefile("attr_index/README.txt", "");
1042 cl_git_pass(git_path_lstat("attr_index/README.txt", &st));
1043 cl_assert_equal_i(0, (int)st.st_size);
1044
1045 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
1046 cl_assert_equal_i(3, (int)git_diff_num_deltas(diff));
1047 /* diffs are: .gitattributes, README.txt, sub/sub/.gitattributes */
1048 cl_git_pass(git_patch_from_diff(&patch, diff, 1));
1049 git_patch_free(patch);
1050 git_diff_free(diff);
1051
1052 /* remove a file altogether */
1053
1054 cl_git_pass(p_unlink("attr_index/README.txt"));
1055 cl_assert(!git_path_exists("attr_index/README.txt"));
1056
1057 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
1058 cl_assert_equal_i(3, (int)git_diff_num_deltas(diff));
1059 cl_git_pass(git_patch_from_diff(&patch, diff, 1));
1060 git_patch_free(patch);
1061 git_diff_free(diff);
1062
1063 git_tree_free(tree);
1064 }
1065
1066 void test_diff_workdir__to_index_issue_1397(void)
1067 {
1068 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1069 git_diff *diff = NULL;
1070 diff_expects exp;
1071
1072 g_repo = cl_git_sandbox_init("issue_1397");
1073
1074 cl_repo_set_bool(g_repo, "core.autocrlf", true);
1075
1076 opts.context_lines = 3;
1077 opts.interhunk_lines = 1;
1078
1079 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1080
1081 memset(&exp, 0, sizeof(exp));
1082 cl_git_pass(git_diff_foreach(
1083 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1084
1085 cl_assert_equal_i(0, exp.files);
1086 cl_assert_equal_i(0, exp.hunks);
1087 cl_assert_equal_i(0, exp.lines);
1088
1089 git_diff_free(diff);
1090 diff = NULL;
1091
1092 cl_git_rewritefile("issue_1397/crlf_file.txt",
1093 "first line\r\nsecond line modified\r\nboth with crlf");
1094
1095 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1096
1097 memset(&exp, 0, sizeof(exp));
1098 cl_git_pass(git_diff_foreach(
1099 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1100
1101 cl_assert_equal_i(1, exp.files);
1102 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1103
1104 cl_assert_equal_i(1, exp.hunks);
1105
1106 cl_assert_equal_i(5, exp.lines);
1107 cl_assert_equal_i(3, exp.line_ctxt);
1108 cl_assert_equal_i(1, exp.line_adds);
1109 cl_assert_equal_i(1, exp.line_dels);
1110
1111 git_diff_free(diff);
1112 }
1113
1114 void test_diff_workdir__to_tree_issue_1397(void)
1115 {
1116 const char *a_commit = "7f483a738"; /* the current HEAD */
1117 git_tree *a;
1118 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1119 git_diff *diff = NULL;
1120 git_diff *diff2 = NULL;
1121 diff_expects exp;
1122
1123 g_repo = cl_git_sandbox_init("issue_1397");
1124
1125 cl_repo_set_bool(g_repo, "core.autocrlf", true);
1126
1127 a = resolve_commit_oid_to_tree(g_repo, a_commit);
1128
1129 opts.context_lines = 3;
1130 opts.interhunk_lines = 1;
1131
1132 cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
1133
1134 memset(&exp, 0, sizeof(exp));
1135 cl_git_pass(git_diff_foreach(
1136 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1137
1138 cl_assert_equal_i(0, exp.files);
1139 cl_assert_equal_i(0, exp.hunks);
1140 cl_assert_equal_i(0, exp.lines);
1141
1142 git_diff_free(diff);
1143 diff = NULL;
1144
1145 cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
1146 cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
1147 cl_git_pass(git_diff_merge(diff, diff2));
1148 git_diff_free(diff2);
1149
1150 memset(&exp, 0, sizeof(exp));
1151 cl_git_pass(git_diff_foreach(
1152 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1153
1154 cl_assert_equal_i(0, exp.files);
1155 cl_assert_equal_i(0, exp.hunks);
1156 cl_assert_equal_i(0, exp.lines);
1157
1158 git_diff_free(diff);
1159 git_tree_free(a);
1160 }
1161
1162 void test_diff_workdir__untracked_directory_scenarios(void)
1163 {
1164 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1165 git_diff *diff = NULL;
1166 diff_expects exp;
1167 char *pathspec = NULL;
1168 static const char *files0[] = {
1169 "subdir/deleted_file",
1170 "subdir/modified_file",
1171 "subdir/new_file",
1172 NULL
1173 };
1174 static const char *files1[] = {
1175 "subdir/deleted_file",
1176 "subdir/directory/",
1177 "subdir/modified_file",
1178 "subdir/new_file",
1179 NULL
1180 };
1181 static const char *files2[] = {
1182 "subdir/deleted_file",
1183 "subdir/directory/more/notignored",
1184 "subdir/modified_file",
1185 "subdir/new_file",
1186 NULL
1187 };
1188
1189 g_repo = cl_git_sandbox_init("status");
1190 cl_git_mkfile("status/.gitignore", "ignored\n");
1191
1192 opts.context_lines = 3;
1193 opts.interhunk_lines = 1;
1194 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
1195 opts.pathspec.strings = &pathspec;
1196 opts.pathspec.count = 1;
1197 pathspec = "subdir";
1198
1199 /* baseline for "subdir" pathspec */
1200
1201 memset(&exp, 0, sizeof(exp));
1202 exp.names = files0;
1203
1204 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1205
1206 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1207
1208 cl_assert_equal_i(3, exp.files);
1209 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1210 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1211 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1212 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
1213 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1214
1215 git_diff_free(diff);
1216
1217 /* empty directory */
1218
1219 cl_git_pass(p_mkdir("status/subdir/directory", 0777));
1220
1221 memset(&exp, 0, sizeof(exp));
1222 exp.names = files1;
1223
1224 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1225
1226 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1227
1228 cl_assert_equal_i(4, exp.files);
1229 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1230 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1231 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1232 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
1233 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1234
1235 git_diff_free(diff);
1236
1237 /* empty directory in empty directory */
1238
1239 cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777));
1240
1241 memset(&exp, 0, sizeof(exp));
1242 exp.names = files1;
1243
1244 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1245
1246 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1247
1248 cl_assert_equal_i(4, exp.files);
1249 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1250 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1251 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1252 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
1253 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1254
1255 git_diff_free(diff);
1256
1257 /* directory with only ignored files */
1258
1259 cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777));
1260 cl_git_mkfile("status/subdir/directory/deeper/ignored", "ignore me\n");
1261
1262 cl_git_pass(p_mkdir("status/subdir/directory/another", 0777));
1263 cl_git_mkfile("status/subdir/directory/another/ignored", "ignore me\n");
1264
1265 memset(&exp, 0, sizeof(exp));
1266 exp.names = files1;
1267
1268 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1269
1270 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1271
1272 cl_assert_equal_i(4, exp.files);
1273 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1274 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1275 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1276 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
1277 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1278
1279 git_diff_free(diff);
1280
1281 /* directory with ignored directory (contents irrelevant) */
1282
1283 cl_git_pass(p_mkdir("status/subdir/directory/more", 0777));
1284 cl_git_pass(p_mkdir("status/subdir/directory/more/ignored", 0777));
1285 cl_git_mkfile("status/subdir/directory/more/ignored/notignored",
1286 "inside ignored dir\n");
1287
1288 memset(&exp, 0, sizeof(exp));
1289 exp.names = files1;
1290
1291 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1292
1293 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1294
1295 cl_assert_equal_i(4, exp.files);
1296 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1297 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1298 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1299 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
1300 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
1301
1302 git_diff_free(diff);
1303
1304 /* quick version avoids directory scan */
1305
1306 opts.flags = opts.flags | GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS;
1307
1308 memset(&exp, 0, sizeof(exp));
1309 exp.names = files1;
1310
1311 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1312
1313 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1314
1315 cl_assert_equal_i(4, exp.files);
1316 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1317 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1318 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1319 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
1320 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
1321
1322 git_diff_free(diff);
1323
1324 /* directory with nested non-ignored content */
1325
1326 opts.flags = opts.flags & ~GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS;
1327
1328 cl_git_mkfile("status/subdir/directory/more/notignored",
1329 "not ignored deep under untracked\n");
1330
1331 memset(&exp, 0, sizeof(exp));
1332 exp.names = files1;
1333
1334 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1335
1336 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1337
1338 cl_assert_equal_i(4, exp.files);
1339 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1340 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1341 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1342 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
1343 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
1344
1345 git_diff_free(diff);
1346
1347 /* use RECURSE_UNTRACKED_DIRS to get actual untracked files (no ignores) */
1348
1349 opts.flags = opts.flags & ~GIT_DIFF_INCLUDE_IGNORED;
1350 opts.flags = opts.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
1351
1352 memset(&exp, 0, sizeof(exp));
1353 exp.names = files2;
1354
1355 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1356
1357 cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
1358
1359 cl_assert_equal_i(4, exp.files);
1360 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1361 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
1362 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
1363 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
1364 cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
1365
1366 git_diff_free(diff);
1367 }
1368
1369
1370 void test_diff_workdir__untracked_directory_comes_last(void)
1371 {
1372 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1373 git_diff *diff = NULL;
1374
1375 g_repo = cl_git_sandbox_init("renames");
1376
1377 cl_git_mkfile("renames/.gitignore", "*.ign\n");
1378 cl_git_pass(p_mkdir("renames/zzz_untracked", 0777));
1379 cl_git_mkfile("renames/zzz_untracked/an.ign", "ignore me please");
1380 cl_git_mkfile("renames/zzz_untracked/skip.ign", "ignore me really");
1381 cl_git_mkfile("renames/zzz_untracked/test.ign", "ignore me now");
1382
1383 opts.context_lines = 3;
1384 opts.interhunk_lines = 1;
1385 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
1386
1387 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1388
1389 cl_assert(diff != NULL);
1390
1391 git_diff_free(diff);
1392 }
1393
1394 void test_diff_workdir__untracked_with_bom(void)
1395 {
1396 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1397 git_diff *diff = NULL;
1398 const git_diff_delta *delta;
1399
1400 g_repo = cl_git_sandbox_init("empty_standard_repo");
1401 cl_repo_set_bool(g_repo, "core.autocrlf", true);
1402
1403 cl_git_write2file("empty_standard_repo/bom.txt",
1404 "\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664);
1405
1406 opts.flags =
1407 GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT;
1408
1409 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1410
1411 cl_assert_equal_i(1, git_diff_num_deltas(diff));
1412 cl_assert((delta = git_diff_get_delta(diff, 0)) != NULL);
1413 cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status);
1414
1415 /* not known at this point
1416 * cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
1417 */
1418
1419 git_diff_free(diff);
1420 }
1421
1422 void test_diff_workdir__patience_diff(void)
1423 {
1424 git_index *index;
1425 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1426 git_diff *diff = NULL;
1427 git_patch *patch = NULL;
1428 git_buf buf = GIT_BUF_INIT;
1429 const char *expected_normal = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n-how to create\n-a patience diff\n I did not know\n how to create\n+a patience diff\n another problem\n-I did not know\n-how to create\n a minimal diff\n";
1430 const char *expected_patience = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n+I did not know\n how to create\n a patience diff\n-I did not know\n-how to create\n another problem\n-I did not know\n-how to create\n a minimal diff\n";
1431
1432 g_repo = cl_git_sandbox_init("empty_standard_repo");
1433 cl_repo_set_bool(g_repo, "core.autocrlf", true);
1434 cl_git_pass(git_repository_index(&index, g_repo));
1435
1436 cl_git_mkfile(
1437 "empty_standard_repo/test.txt",
1438 "When I wrote this\nI did not know\nhow to create\na patience diff\nI did not know\nhow to create\nanother problem\nI did not know\nhow to create\na minimal diff\n");
1439 cl_git_pass(git_index_add_bypath(index, "test.txt"));
1440 cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "Base");
1441 git_index_free(index);
1442
1443 cl_git_rewritefile(
1444 "empty_standard_repo/test.txt",
1445 "When I wrote this\nI did not know\nI did not know\nhow to create\na patience diff\nanother problem\na minimal diff\n");
1446
1447 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1448 cl_assert_equal_i(1, git_diff_num_deltas(diff));
1449 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
1450 cl_git_pass(git_patch_to_buf(&buf, patch));
1451
1452 cl_assert_equal_s(expected_normal, buf.ptr);
1453 git_buf_clear(&buf);
1454 git_patch_free(patch);
1455 git_diff_free(diff);
1456
1457 opts.flags |= GIT_DIFF_PATIENCE;
1458
1459 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1460 cl_assert_equal_i(1, git_diff_num_deltas(diff));
1461 cl_git_pass(git_patch_from_diff(&patch, diff, 0));
1462 cl_git_pass(git_patch_to_buf(&buf, patch));
1463
1464 cl_assert_equal_s(expected_patience, buf.ptr);
1465 git_buf_clear(&buf);
1466
1467 git_buf_free(&buf);
1468 git_patch_free(patch);
1469 git_diff_free(diff);
1470 }
1471
1472 void test_diff_workdir__with_stale_index(void)
1473 {
1474 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1475 git_diff *diff = NULL;
1476 git_index *idx = NULL;
1477 diff_expects exp;
1478
1479 g_repo = cl_git_sandbox_init("status");
1480 cl_git_pass(git_repository_index(&idx, g_repo));
1481
1482 /* make the in-memory index invalid */
1483 {
1484 git_repository *r2;
1485 git_index *idx2;
1486 cl_git_pass(git_repository_open(&r2, "status"));
1487 cl_git_pass(git_repository_index(&idx2, r2));
1488 cl_git_pass(git_index_add_bypath(idx2, "new_file"));
1489 cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file"));
1490 cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file"));
1491 cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted"));
1492 cl_git_pass(git_index_write(idx2));
1493 git_index_free(idx2);
1494 git_repository_free(r2);
1495 }
1496
1497 opts.context_lines = 3;
1498 opts.interhunk_lines = 1;
1499 opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED;
1500
1501 /* first try with index pointer which should prevent reload */
1502
1503 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts));
1504
1505 memset(&exp, 0, sizeof(exp));
1506
1507 cl_git_pass(git_diff_foreach(
1508 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1509
1510 cl_assert_equal_i(17, exp.files);
1511 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1512 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
1513 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
1514 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
1515 cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]);
1516
1517 git_diff_free(diff);
1518
1519 /* now let's try without the index pointer which should trigger reload */
1520
1521 /* two files that were UNTRACKED should have become UNMODIFIED */
1522 /* one file that was UNMODIFIED should now have become UNTRACKED */
1523 /* one file that was DELETED should now be gone completely */
1524
1525 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
1526
1527 memset(&exp, 0, sizeof(exp));
1528
1529 cl_git_pass(git_diff_foreach(
1530 diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1531
1532 git_diff_free(diff);
1533
1534 cl_assert_equal_i(16, exp.files);
1535 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1536 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]);
1537 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
1538 cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
1539 cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]);
1540
1541 git_index_free(idx);
1542 }
1543
1544 static int touch_file(void *payload, git_buf *path)
1545 {
1546 int fd;
1547 char b;
1548
1549 GIT_UNUSED(payload);
1550 if (git_path_isdir(path->ptr))
1551 return 0;
1552
1553 cl_assert((fd = p_open(path->ptr, O_RDWR)) >= 0);
1554 cl_assert_equal_i(1, p_read(fd, &b, 1));
1555 cl_must_pass(p_lseek(fd, 0, SEEK_SET));
1556 cl_must_pass(p_write(fd, &b, 1));
1557 cl_must_pass(p_close(fd));
1558
1559 return 0;
1560 }
1561
1562 static void basic_diff_status(git_diff **out, const git_diff_options *opts)
1563 {
1564 diff_expects exp;
1565
1566 cl_git_pass(git_diff_index_to_workdir(out, g_repo, NULL, opts));
1567
1568 memset(&exp, 0, sizeof(exp));
1569
1570 cl_git_pass(git_diff_foreach(
1571 *out, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
1572
1573 cl_assert_equal_i(13, exp.files);
1574 cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
1575 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
1576 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
1577 cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
1578 cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
1579 }
1580
1581 void test_diff_workdir__can_update_index(void)
1582 {
1583 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
1584 git_diff *diff = NULL;
1585 git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
1586
1587 g_repo = cl_git_sandbox_init("status");
1588
1589 /* touch all the files so stat times are different */
1590 {
1591 git_buf path = GIT_BUF_INIT;
1592 cl_git_pass(git_buf_sets(&path, "status"));
1593 cl_git_pass(git_path_direach(&path, 0, touch_file, NULL));
1594 git_buf_free(&path);
1595 }
1596
1597 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
1598
1599 basic_diff_status(&diff, &opts);
1600
1601 cl_git_pass(git_diff_get_perfdata(&perf, diff));
1602 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1603 cl_assert_equal_sz(5, perf.oid_calculations);
1604
1605 git_diff_free(diff);
1606
1607 /* now allow diff to update stat cache */
1608 opts.flags |= GIT_DIFF_UPDATE_INDEX;
1609
1610 basic_diff_status(&diff, &opts);
1611
1612 cl_git_pass(git_diff_get_perfdata(&perf, diff));
1613 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1614 cl_assert_equal_sz(5, perf.oid_calculations);
1615
1616 git_diff_free(diff);
1617
1618 /* now if we do it again, we should see fewer OID calculations */
1619
1620 basic_diff_status(&diff, &opts);
1621
1622 cl_git_pass(git_diff_get_perfdata(&perf, diff));
1623 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1624 cl_assert_equal_sz(0, perf.oid_calculations);
1625
1626 git_diff_free(diff);
1627 }
1628
1629 #define STR7 "0123456"
1630 #define STR8 "01234567"
1631 #define STR40 STR8 STR8 STR8 STR8 STR8
1632 #define STR200 STR40 STR40 STR40 STR40 STR40
1633 #define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \
1634 STR8 STR8 STR8 STR8 STR7 "\0"
1635 #define STR1000 STR200 STR200 STR200 STR200 STR200
1636 #define STR3999Z STR1000 STR1000 STR1000 STR999Z
1637 #define STR4000 STR1000 STR1000 STR1000 STR1000
1638
1639 static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary)
1640 {
1641 git_patch *patch;
1642 const git_diff_delta *delta;
1643
1644 cl_git_pass(git_patch_from_diff(&patch, diff, idx));
1645 delta = git_patch_get_delta(patch);
1646 cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary);
1647 git_patch_free(patch);
1648 }
1649
1650 void test_diff_workdir__binary_detection(void)
1651 {
1652 git_index *idx;
1653 git_diff *diff = NULL;
1654 git_buf b = GIT_BUF_INIT;
1655 int i;
1656 git_buf data[10] = {
1657 { "1234567890", 0, 0 }, /* 0 - all ascii text control */
1658 { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 0 }, /* 1 - UTF-8 multibyte text */
1659 { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 0 }, /* 2 - UTF-8 with BOM */
1660 { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */
1661 { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */
1662 { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */
1663 { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */
1664 { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8"
1665 "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */
1666 { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
1667 "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d",
1668 0, 26 }, /* 8 - All non-printable characters (no NUL) */
1669 { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04"
1670 "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */
1671 };
1672
1673 g_repo = cl_git_sandbox_init("empty_standard_repo");
1674 cl_git_pass(git_repository_index(&idx, g_repo));
1675
1676 /* We start with ASCII in index and test data in workdir,
1677 * then we will try with test data in index and ASCII in workdir.
1678 */
1679
1680 cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0"));
1681 for (i = 0; i < 10; ++i) {
1682 b.ptr[b.size - 1] = '0' + i;
1683 cl_git_mkfile(b.ptr, "baseline");
1684 cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1]));
1685
1686 if (data[i].size == 0)
1687 data[i].size = strlen(data[i].ptr);
1688 cl_git_write2file(
1689 b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664);
1690 }
1691 git_index_write(idx);
1692
1693 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
1694
1695 cl_assert_equal_i(10, git_diff_num_deltas(diff));
1696
1697 /* using diff binary detection (i.e. looking for NUL byte) */
1698 assert_delta_binary(diff, 0, false);
1699 assert_delta_binary(diff, 1, false);
1700 assert_delta_binary(diff, 2, false);
1701 assert_delta_binary(diff, 3, true);
1702 assert_delta_binary(diff, 4, true);
1703 assert_delta_binary(diff, 5, true);
1704 assert_delta_binary(diff, 6, false);
1705 assert_delta_binary(diff, 7, true);
1706 assert_delta_binary(diff, 8, false);
1707 assert_delta_binary(diff, 9, false);
1708 /* The above have been checked to match command-line Git */
1709
1710 git_diff_free(diff);
1711
1712 cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0"));
1713 for (i = 0; i < 10; ++i) {
1714 b.ptr[b.size - 1] = '0' + i;
1715 cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1]));
1716
1717 cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664);
1718 }
1719 git_index_write(idx);
1720
1721 cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
1722
1723 cl_assert_equal_i(10, git_diff_num_deltas(diff));
1724
1725 /* using diff binary detection (i.e. looking for NUL byte) */
1726 assert_delta_binary(diff, 0, false);
1727 assert_delta_binary(diff, 1, false);
1728 assert_delta_binary(diff, 2, false);
1729 assert_delta_binary(diff, 3, true);
1730 assert_delta_binary(diff, 4, true);
1731 assert_delta_binary(diff, 5, true);
1732 assert_delta_binary(diff, 6, false);
1733 assert_delta_binary(diff, 7, true);
1734 assert_delta_binary(diff, 8, false);
1735 assert_delta_binary(diff, 9, false);
1736
1737 git_diff_free(diff);
1738
1739 git_index_free(idx);
1740 git_buf_free(&b);
1741 }