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