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