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