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