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