]> git.proxmox.com Git - libgit2.git/blob - tests/status/worktree.c
c6b18c166b0499c47fbfd4629ee1dbb528a58d24
[libgit2.git] / tests / status / worktree.c
1 #include "clar_libgit2.h"
2 #include "fileops.h"
3 #include "ignore.h"
4 #include "status_data.h"
5 #include "posix.h"
6 #include "util.h"
7 #include "path.h"
8 #include "../diff/diff_helpers.h"
9 #include "../checkout/checkout_helpers.h"
10 #include "git2/sys/diff.h"
11
12 /**
13 * Cleanup
14 *
15 * This will be called once after each test finishes, even
16 * if the test failed
17 */
18 void test_status_worktree__cleanup(void)
19 {
20 cl_git_sandbox_cleanup();
21 }
22
23 /**
24 * Tests - Status determination on a working tree
25 */
26 /* this test is equivalent to t18-status.c:statuscb0 */
27 void test_status_worktree__whole_repository(void)
28 {
29 status_entry_counts counts;
30 git_repository *repo = cl_git_sandbox_init("status");
31
32 memset(&counts, 0x0, sizeof(status_entry_counts));
33 counts.expected_entry_count = entry_count0;
34 counts.expected_paths = entry_paths0;
35 counts.expected_statuses = entry_statuses0;
36
37 cl_git_pass(
38 git_status_foreach(repo, cb_status__normal, &counts)
39 );
40
41 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
42 cl_assert_equal_i(0, counts.wrong_status_flags_count);
43 cl_assert_equal_i(0, counts.wrong_sorted_path);
44 }
45
46 void assert_show(
47 const int entry_counts,
48 const char *entry_paths[],
49 const unsigned int entry_statuses[],
50 git_repository *repo,
51 git_status_show_t show,
52 unsigned int extra_flags)
53 {
54 status_entry_counts counts;
55 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
56
57 memset(&counts, 0x0, sizeof(status_entry_counts));
58 counts.expected_entry_count = entry_counts;
59 counts.expected_paths = entry_paths;
60 counts.expected_statuses = entry_statuses;
61
62 opts.flags = GIT_STATUS_OPT_DEFAULTS | extra_flags;
63 opts.show = show;
64
65 cl_git_pass(
66 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
67 );
68
69 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
70 cl_assert_equal_i(0, counts.wrong_status_flags_count);
71 cl_assert_equal_i(0, counts.wrong_sorted_path);
72 }
73
74 void test_status_worktree__show_index_and_workdir(void)
75 {
76 assert_show(entry_count0, entry_paths0, entry_statuses0,
77 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_AND_WORKDIR, 0);
78 }
79
80 void test_status_worktree__show_index_only(void)
81 {
82 assert_show(entry_count5, entry_paths5, entry_statuses5,
83 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_ONLY, 0);
84 }
85
86 void test_status_worktree__show_workdir_only(void)
87 {
88 assert_show(entry_count6, entry_paths6, entry_statuses6,
89 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_WORKDIR_ONLY, 0);
90 }
91
92 /* this test is equivalent to t18-status.c:statuscb1 */
93 void test_status_worktree__empty_repository(void)
94 {
95 int count = 0;
96 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
97
98 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
99
100 cl_assert_equal_i(0, count);
101 }
102
103 static int remove_file_cb(void *data, git_buf *file)
104 {
105 const char *filename = git_buf_cstr(file);
106
107 GIT_UNUSED(data);
108
109 if (git__suffixcmp(filename, ".git") == 0)
110 return 0;
111
112 if (git_path_isdir(filename))
113 cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES));
114 else
115 cl_git_pass(p_unlink(git_buf_cstr(file)));
116
117 return 0;
118 }
119
120 /* this test is equivalent to t18-status.c:statuscb2 */
121 void test_status_worktree__purged_worktree(void)
122 {
123 status_entry_counts counts;
124 git_repository *repo = cl_git_sandbox_init("status");
125 git_buf workdir = GIT_BUF_INIT;
126
127 /* first purge the contents of the worktree */
128 cl_git_pass(git_buf_sets(&workdir, git_repository_workdir(repo)));
129 cl_git_pass(git_path_direach(&workdir, 0, remove_file_cb, NULL));
130 git_buf_free(&workdir);
131
132 /* now get status */
133 memset(&counts, 0x0, sizeof(status_entry_counts));
134 counts.expected_entry_count = entry_count2;
135 counts.expected_paths = entry_paths2;
136 counts.expected_statuses = entry_statuses2;
137
138 cl_git_pass(
139 git_status_foreach(repo, cb_status__normal, &counts)
140 );
141
142 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
143 cl_assert_equal_i(0, counts.wrong_status_flags_count);
144 cl_assert_equal_i(0, counts.wrong_sorted_path);
145 }
146
147 /* this test is similar to t18-status.c:statuscb3 */
148 void test_status_worktree__swap_subdir_and_file(void)
149 {
150 status_entry_counts counts;
151 git_repository *repo = cl_git_sandbox_init("status");
152 git_index *index;
153 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
154 bool ignore_case;
155
156 cl_git_pass(git_repository_index(&index, repo));
157 ignore_case = (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
158 git_index_free(index);
159
160 /* first alter the contents of the worktree */
161 cl_git_pass(p_rename("status/current_file", "status/swap"));
162 cl_git_pass(p_rename("status/subdir", "status/current_file"));
163 cl_git_pass(p_rename("status/swap", "status/subdir"));
164
165 cl_git_mkfile("status/.HEADER", "dummy");
166 cl_git_mkfile("status/42-is-not-prime.sigh", "dummy");
167 cl_git_mkfile("status/README.md", "dummy");
168
169 /* now get status */
170 memset(&counts, 0x0, sizeof(status_entry_counts));
171 counts.expected_entry_count = entry_count3;
172 counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3;
173 counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3;
174
175 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
176 GIT_STATUS_OPT_INCLUDE_IGNORED;
177
178 cl_git_pass(
179 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
180 );
181
182 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
183 cl_assert_equal_i(0, counts.wrong_status_flags_count);
184 cl_assert_equal_i(0, counts.wrong_sorted_path);
185 }
186
187 void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
188 {
189 status_entry_counts counts;
190 git_repository *repo = cl_git_sandbox_init("status");
191 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
192
193 /* first alter the contents of the worktree */
194 cl_git_pass(p_rename("status/current_file", "status/swap"));
195 cl_git_pass(p_rename("status/subdir", "status/current_file"));
196 cl_git_pass(p_rename("status/swap", "status/subdir"));
197 cl_git_mkfile("status/.new_file", "dummy");
198 cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777));
199 cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
200 cl_git_mkfile("status/zzz_new_file", "dummy");
201
202 /* now get status */
203 memset(&counts, 0x0, sizeof(status_entry_counts));
204 counts.expected_entry_count = entry_count4;
205 counts.expected_paths = entry_paths4;
206 counts.expected_statuses = entry_statuses4;
207
208 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
209 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
210 /* TODO: set pathspec to "current_file" eventually */
211
212 cl_git_pass(
213 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
214 );
215
216 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
217 cl_assert_equal_i(0, counts.wrong_status_flags_count);
218 cl_assert_equal_i(0, counts.wrong_sorted_path);
219 }
220
221 static void stage_and_commit(git_repository *repo, const char *path)
222 {
223 git_index *index;
224
225 cl_git_pass(git_repository_index(&index, repo));
226 cl_git_pass(git_index_add_bypath(index, path));
227 cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n");
228 git_index_free(index);
229 }
230
231 void test_status_worktree__within_subdir(void)
232 {
233 status_entry_counts counts;
234 git_repository *repo = cl_git_sandbox_init("status");
235 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
236 char *paths[] = { "zzz_new_dir" };
237 git_strarray pathsArray;
238
239 /* first alter the contents of the worktree */
240 cl_git_mkfile("status/.new_file", "dummy");
241 cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777));
242 cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
243 cl_git_mkfile("status/zzz_new_file", "dummy");
244 cl_git_mkfile("status/wut", "dummy");
245
246 stage_and_commit(repo, "zzz_new_dir/new_file");
247
248 /* now get status */
249 memset(&counts, 0x0, sizeof(status_entry_counts));
250 counts.expected_entry_count = entry_count4;
251 counts.expected_paths = entry_paths4;
252 counts.expected_statuses = entry_statuses4;
253 counts.debug = true;
254
255 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
256 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
257 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
258
259 pathsArray.count = 1;
260 pathsArray.strings = paths;
261 opts.pathspec = pathsArray;
262
263 // We committed zzz_new_dir/new_file above. It shouldn't be reported.
264 cl_git_pass(
265 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
266 );
267
268 cl_assert_equal_i(0, counts.entry_count);
269 cl_assert_equal_i(0, counts.wrong_status_flags_count);
270 cl_assert_equal_i(0, counts.wrong_sorted_path);
271 }
272
273 /* this test is equivalent to t18-status.c:singlestatus0 */
274 void test_status_worktree__single_file(void)
275 {
276 int i;
277 unsigned int status_flags;
278 git_repository *repo = cl_git_sandbox_init("status");
279
280 for (i = 0; i < (int)entry_count0; i++) {
281 cl_git_pass(
282 git_status_file(&status_flags, repo, entry_paths0[i])
283 );
284 cl_assert(entry_statuses0[i] == status_flags);
285 }
286 }
287
288 /* this test is equivalent to t18-status.c:singlestatus1 */
289 void test_status_worktree__single_nonexistent_file(void)
290 {
291 int error;
292 unsigned int status_flags;
293 git_repository *repo = cl_git_sandbox_init("status");
294
295 error = git_status_file(&status_flags, repo, "nonexistent");
296 cl_git_fail(error);
297 cl_assert(error == GIT_ENOTFOUND);
298 }
299
300 /* this test is equivalent to t18-status.c:singlestatus2 */
301 void test_status_worktree__single_nonexistent_file_empty_repo(void)
302 {
303 int error;
304 unsigned int status_flags;
305 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
306
307 error = git_status_file(&status_flags, repo, "nonexistent");
308 cl_git_fail(error);
309 cl_assert(error == GIT_ENOTFOUND);
310 }
311
312 /* this test is equivalent to t18-status.c:singlestatus3 */
313 void test_status_worktree__single_file_empty_repo(void)
314 {
315 unsigned int status_flags;
316 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
317
318 cl_git_mkfile("empty_standard_repo/new_file", "new_file\n");
319
320 cl_git_pass(git_status_file(&status_flags, repo, "new_file"));
321 cl_assert(status_flags == GIT_STATUS_WT_NEW);
322 }
323
324 /* this test is equivalent to t18-status.c:singlestatus4 */
325 void test_status_worktree__single_folder(void)
326 {
327 int error;
328 unsigned int status_flags;
329 git_repository *repo = cl_git_sandbox_init("status");
330
331 error = git_status_file(&status_flags, repo, "subdir");
332 cl_git_fail(error);
333 cl_assert(error != GIT_ENOTFOUND);
334 }
335
336
337 void test_status_worktree__ignores(void)
338 {
339 int i, ignored;
340 git_repository *repo = cl_git_sandbox_init("status");
341
342 for (i = 0; i < (int)entry_count0; i++) {
343 cl_git_pass(
344 git_status_should_ignore(&ignored, repo, entry_paths0[i])
345 );
346 cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
347 }
348
349 cl_git_pass(
350 git_status_should_ignore(&ignored, repo, "nonexistent_file")
351 );
352 cl_assert(!ignored);
353
354 cl_git_pass(
355 git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file")
356 );
357 cl_assert(ignored);
358 }
359
360 static int cb_status__check_592(const char *p, unsigned int s, void *payload)
361 {
362 if (s != GIT_STATUS_WT_DELETED ||
363 (payload != NULL && strcmp(p, (const char *)payload) != 0))
364 return -1;
365
366 return 0;
367 }
368
369 void test_status_worktree__issue_592(void)
370 {
371 git_repository *repo;
372 git_buf path = GIT_BUF_INIT;
373
374 repo = cl_git_sandbox_init("issue_592");
375 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "l.txt"));
376 cl_git_pass(p_unlink(git_buf_cstr(&path)));
377 cl_assert(!git_path_exists("issue_592/l.txt"));
378
379 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
380
381 git_buf_free(&path);
382 }
383
384 void test_status_worktree__issue_592_2(void)
385 {
386 git_repository *repo;
387 git_buf path = GIT_BUF_INIT;
388
389 repo = cl_git_sandbox_init("issue_592");
390 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c/a.txt"));
391 cl_git_pass(p_unlink(git_buf_cstr(&path)));
392 cl_assert(!git_path_exists("issue_592/c/a.txt"));
393
394 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
395
396 git_buf_free(&path);
397 }
398
399 void test_status_worktree__issue_592_3(void)
400 {
401 git_repository *repo;
402 git_buf path = GIT_BUF_INIT;
403
404 repo = cl_git_sandbox_init("issue_592");
405
406 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
407 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
408 cl_assert(!git_path_exists("issue_592/c/a.txt"));
409
410 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
411
412 git_buf_free(&path);
413 }
414
415 void test_status_worktree__issue_592_4(void)
416 {
417 git_repository *repo;
418 git_buf path = GIT_BUF_INIT;
419
420 repo = cl_git_sandbox_init("issue_592");
421
422 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t/b.txt"));
423 cl_git_pass(p_unlink(git_buf_cstr(&path)));
424
425 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt"));
426
427 git_buf_free(&path);
428 }
429
430 void test_status_worktree__issue_592_5(void)
431 {
432 git_repository *repo;
433 git_buf path = GIT_BUF_INIT;
434
435 repo = cl_git_sandbox_init("issue_592");
436
437 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
438 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
439 cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
440
441 cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
442
443 git_buf_free(&path);
444 }
445
446 void test_status_worktree__issue_592_ignores_0(void)
447 {
448 int count = 0;
449 status_entry_single st;
450 git_repository *repo = cl_git_sandbox_init("issue_592");
451
452 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
453 cl_assert_equal_i(0, count);
454
455 cl_git_rewritefile("issue_592/.gitignore",
456 ".gitignore\n*.txt\nc/\n[tT]*/\n");
457
458 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
459 cl_assert_equal_i(1, count);
460
461 /* This is a situation where the behavior of libgit2 is
462 * different from core git. Core git will show ignored.txt
463 * in the list of ignored files, even though the directory
464 * "t" is ignored and the file is untracked because we have
465 * the explicit "*.txt" ignore rule. Libgit2 just excludes
466 * all untracked files that are contained within ignored
467 * directories without explicitly listing them.
468 */
469 cl_git_rewritefile("issue_592/t/ignored.txt", "ping");
470
471 memset(&st, 0, sizeof(st));
472 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
473 cl_assert_equal_i(1, st.count);
474 cl_assert(st.status == GIT_STATUS_IGNORED);
475
476 cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping");
477
478 memset(&st, 0, sizeof(st));
479 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
480 cl_assert_equal_i(1, st.count);
481 cl_assert(st.status == GIT_STATUS_IGNORED);
482
483 cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping");
484
485 memset(&st, 0, sizeof(st));
486 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
487 cl_assert_equal_i(1, st.count);
488 cl_assert(st.status == GIT_STATUS_IGNORED);
489 }
490
491 void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void)
492 {
493 int count = 0;
494 git_repository *repo = cl_git_sandbox_init("issue_592b");
495
496 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
497 cl_assert_equal_i(1, count);
498
499 /* if we are really mimicking core git, then only ignored1.txt
500 * at the top level will show up in the ignores list here.
501 * everything else will be unmodified or skipped completely.
502 */
503 }
504
505 void test_status_worktree__conflict_with_diff3(void)
506 {
507 git_repository *repo = cl_git_sandbox_init("status");
508 git_index *index;
509 unsigned int status;
510 git_index_entry ancestor_entry, our_entry, their_entry;
511
512 memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
513 memset(&our_entry, 0x0, sizeof(git_index_entry));
514 memset(&their_entry, 0x0, sizeof(git_index_entry));
515
516 ancestor_entry.path = "modified_file";
517 ancestor_entry.mode = 0100644;
518 git_oid_fromstr(&ancestor_entry.id,
519 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
520
521 our_entry.path = "modified_file";
522 our_entry.mode = 0100644;
523 git_oid_fromstr(&our_entry.id,
524 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
525
526 their_entry.path = "modified_file";
527 their_entry.mode = 0100644;
528 git_oid_fromstr(&their_entry.id,
529 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
530
531 cl_git_pass(git_status_file(&status, repo, "modified_file"));
532 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
533
534 cl_git_pass(git_repository_index(&index, repo));
535 cl_git_pass(git_index_remove(index, "modified_file", 0));
536 cl_git_pass(git_index_conflict_add(
537 index, &ancestor_entry, &our_entry, &their_entry));
538 cl_git_pass(git_index_write(index));
539 git_index_free(index);
540
541 cl_git_pass(git_status_file(&status, repo, "modified_file"));
542
543 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status);
544 }
545
546 static const char *filemode_paths[] = {
547 "exec_off",
548 "exec_off2on_staged",
549 "exec_off2on_workdir",
550 "exec_off_untracked",
551 "exec_on",
552 "exec_on2off_staged",
553 "exec_on2off_workdir",
554 "exec_on_untracked",
555 };
556
557 static unsigned int filemode_statuses[] = {
558 GIT_STATUS_CURRENT,
559 GIT_STATUS_INDEX_MODIFIED,
560 GIT_STATUS_WT_MODIFIED,
561 GIT_STATUS_WT_NEW,
562 GIT_STATUS_CURRENT,
563 GIT_STATUS_INDEX_MODIFIED,
564 GIT_STATUS_WT_MODIFIED,
565 GIT_STATUS_WT_NEW
566 };
567
568 static const int filemode_count = 8;
569
570 void test_status_worktree__filemode_changes(void)
571 {
572 git_repository *repo = cl_git_sandbox_init("filemodes");
573 status_entry_counts counts;
574 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
575
576 /* overwrite stored filemode with platform appropriate value */
577 if (cl_is_chmod_supported())
578 cl_repo_set_bool(repo, "core.filemode", true);
579 else {
580 int i;
581
582 cl_repo_set_bool(repo, "core.filemode", false);
583
584 /* won't trust filesystem mode diffs, so these will appear unchanged */
585 for (i = 0; i < filemode_count; ++i)
586 if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
587 filemode_statuses[i] = GIT_STATUS_CURRENT;
588 }
589
590 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
591 GIT_STATUS_OPT_INCLUDE_IGNORED |
592 GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
593
594 memset(&counts, 0, sizeof(counts));
595 counts.expected_entry_count = filemode_count;
596 counts.expected_paths = filemode_paths;
597 counts.expected_statuses = filemode_statuses;
598
599 cl_git_pass(
600 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
601 );
602
603 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
604 cl_assert_equal_i(0, counts.wrong_status_flags_count);
605 cl_assert_equal_i(0, counts.wrong_sorted_path);
606 }
607
608 static int cb_status__interrupt(const char *p, unsigned int s, void *payload)
609 {
610 volatile int *count = (int *)payload;
611
612 GIT_UNUSED(p);
613 GIT_UNUSED(s);
614
615 (*count)++;
616
617 return (*count == 8) ? -111 : 0;
618 }
619
620 void test_status_worktree__interruptable_foreach(void)
621 {
622 int count = 0;
623 git_repository *repo = cl_git_sandbox_init("status");
624
625 cl_assert_equal_i(
626 -111, git_status_foreach(repo, cb_status__interrupt, &count)
627 );
628
629 cl_assert_equal_i(8, count);
630 }
631
632 void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void)
633 {
634 git_repository *repo = cl_git_sandbox_init("status");
635 unsigned int status;
636
637 cl_repo_set_bool(repo, "core.autocrlf", true);
638
639 cl_git_rewritefile("status/current_file", "current_file\r\n");
640
641 cl_git_pass(git_status_file(&status, repo, "current_file"));
642
643 /* stat data on file should no longer match stat cache, even though
644 * file diff will be empty because of line-ending conversion - matches
645 * the Git command-line behavior here.
646 */
647 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
648 }
649
650 void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void)
651 {
652 git_repository *repo = cl_git_sandbox_init("issue_1397");
653 unsigned int status;
654
655 cl_repo_set_bool(repo, "core.autocrlf", true);
656
657 cl_git_pass(git_status_file(&status, repo, "crlf_file.txt"));
658
659 cl_assert_equal_i(GIT_STATUS_CURRENT, status);
660 }
661
662 void test_status_worktree__conflicted_item(void)
663 {
664 git_repository *repo = cl_git_sandbox_init("status");
665 git_index *index;
666 unsigned int status;
667 git_index_entry ancestor_entry, our_entry, their_entry;
668
669 memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
670 memset(&our_entry, 0x0, sizeof(git_index_entry));
671 memset(&their_entry, 0x0, sizeof(git_index_entry));
672
673 ancestor_entry.mode = 0100644;
674 ancestor_entry.path = "modified_file";
675 git_oid_fromstr(&ancestor_entry.id,
676 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
677
678 our_entry.mode = 0100644;
679 our_entry.path = "modified_file";
680 git_oid_fromstr(&our_entry.id,
681 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
682
683 their_entry.mode = 0100644;
684 their_entry.path = "modified_file";
685 git_oid_fromstr(&their_entry.id,
686 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
687
688 cl_git_pass(git_status_file(&status, repo, "modified_file"));
689 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
690
691 cl_git_pass(git_repository_index(&index, repo));
692 cl_git_pass(git_index_conflict_add(index, &ancestor_entry,
693 &our_entry, &their_entry));
694
695 cl_git_pass(git_status_file(&status, repo, "modified_file"));
696 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status);
697
698 git_index_free(index);
699 }
700
701 void test_status_worktree__conflict_has_no_oid(void)
702 {
703 git_repository *repo = cl_git_sandbox_init("status");
704 git_index *index;
705 git_index_entry entry = {{0}};
706 git_status_list *statuslist;
707 const git_status_entry *status;
708 git_oid zero_id = {{0}};
709
710 entry.mode = 0100644;
711 entry.path = "modified_file";
712 git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
713
714 cl_git_pass(git_repository_index(&index, repo));
715 cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry));
716
717 git_status_list_new(&statuslist, repo, NULL);
718
719 cl_assert_equal_i(16, git_status_list_entrycount(statuslist));
720
721 status = git_status_byindex(statuslist, 2);
722
723 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status->status);
724 cl_assert_equal_s("modified_file", status->head_to_index->old_file.path);
725 cl_assert(!git_oid_equal(&zero_id, &status->head_to_index->old_file.id));
726 cl_assert(0 != status->head_to_index->old_file.mode);
727 cl_assert_equal_s("modified_file", status->head_to_index->new_file.path);
728 cl_assert_equal_oid(&zero_id, &status->head_to_index->new_file.id);
729 cl_assert_equal_i(0, status->head_to_index->new_file.mode);
730 cl_assert_equal_i(0, status->head_to_index->new_file.size);
731
732 cl_assert_equal_s("modified_file", status->index_to_workdir->old_file.path);
733 cl_assert_equal_oid(&zero_id, &status->index_to_workdir->old_file.id);
734 cl_assert_equal_i(0, status->index_to_workdir->old_file.mode);
735 cl_assert_equal_i(0, status->index_to_workdir->old_file.size);
736 cl_assert_equal_s("modified_file", status->index_to_workdir->new_file.path);
737 cl_assert(
738 !git_oid_equal(&zero_id, &status->index_to_workdir->new_file.id) ||
739 !(status->index_to_workdir->new_file.flags & GIT_DIFF_FLAG_VALID_ID));
740 cl_assert(0 != status->index_to_workdir->new_file.mode);
741 cl_assert(0 != status->index_to_workdir->new_file.size);
742
743 git_index_free(index);
744 git_status_list_free(statuslist);
745 }
746
747 static void assert_ignore_case(
748 bool should_ignore_case,
749 int expected_lower_cased_file_status,
750 int expected_camel_cased_file_status)
751 {
752 unsigned int status;
753 git_buf lower_case_path = GIT_BUF_INIT, camel_case_path = GIT_BUF_INIT;
754 git_repository *repo, *repo2;
755
756 repo = cl_git_sandbox_init("empty_standard_repo");
757 cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
758
759 cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case);
760
761 cl_git_pass(git_buf_joinpath(&lower_case_path,
762 git_repository_workdir(repo), "plop"));
763
764 cl_git_mkfile(git_buf_cstr(&lower_case_path), "");
765
766 stage_and_commit(repo, "plop");
767
768 cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo"));
769
770 cl_git_pass(git_status_file(&status, repo2, "plop"));
771 cl_assert_equal_i(GIT_STATUS_CURRENT, status);
772
773 cl_git_pass(git_buf_joinpath(&camel_case_path,
774 git_repository_workdir(repo), "Plop"));
775
776 cl_git_pass(p_rename(git_buf_cstr(&lower_case_path), git_buf_cstr(&camel_case_path)));
777
778 cl_git_pass(git_status_file(&status, repo2, "plop"));
779 cl_assert_equal_i(expected_lower_cased_file_status, status);
780
781 cl_git_pass(git_status_file(&status, repo2, "Plop"));
782 cl_assert_equal_i(expected_camel_cased_file_status, status);
783
784 git_repository_free(repo2);
785 git_buf_free(&lower_case_path);
786 git_buf_free(&camel_case_path);
787 }
788
789 void test_status_worktree__file_status_honors_core_ignorecase_true(void)
790 {
791 assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT);
792 }
793
794 void test_status_worktree__file_status_honors_core_ignorecase_false(void)
795 {
796 assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW);
797 }
798
799 void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracked_files(void)
800 {
801 git_repository *repo = cl_git_sandbox_init("status");
802 unsigned int status;
803 git_index *index;
804
805 cl_repo_set_bool(repo, "core.ignorecase", false);
806
807 repo = cl_git_sandbox_reopen();
808
809 /* Actually returns GIT_STATUS_IGNORED on Windows */
810 cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
811
812 cl_git_pass(git_repository_index(&index, repo));
813
814 cl_git_pass(git_index_add_bypath(index, "new_file"));
815 cl_git_pass(git_index_write(index));
816 git_index_free(index);
817
818 /* Actually returns GIT_STATUS_IGNORED on Windows */
819 cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
820 }
821
822 void test_status_worktree__simple_delete(void)
823 {
824 git_repository *repo = cl_git_sandbox_init("renames");
825 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
826 int count;
827
828 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
829 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
830 GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
831 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
832
833 count = 0;
834 cl_git_pass(
835 git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
836 cl_assert_equal_i(0, count);
837
838 cl_must_pass(p_unlink("renames/untimely.txt"));
839
840 count = 0;
841 cl_git_pass(
842 git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
843 cl_assert_equal_i(1, count);
844 }
845
846 void test_status_worktree__simple_delete_indexed(void)
847 {
848 git_repository *repo = cl_git_sandbox_init("renames");
849 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
850 git_status_list *status;
851
852 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
853 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
854 GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
855 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
856
857 cl_git_pass(git_status_list_new(&status, repo, &opts));
858 cl_assert_equal_sz(0, git_status_list_entrycount(status));
859 git_status_list_free(status);
860
861 cl_must_pass(p_unlink("renames/untimely.txt"));
862
863 cl_git_pass(git_status_list_new(&status, repo, &opts));
864 cl_assert_equal_sz(1, git_status_list_entrycount(status));
865 cl_assert_equal_i(
866 GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
867 git_status_list_free(status);
868 }
869
870 static const char *icase_paths[] = { "B", "c", "g", "H" };
871 static unsigned int icase_statuses[] = {
872 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
873 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
874 };
875
876 static const char *case_paths[] = { "B", "H", "c", "g" };
877 static unsigned int case_statuses[] = {
878 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
879 GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED,
880 };
881
882 void test_status_worktree__sorting_by_case(void)
883 {
884 git_repository *repo = cl_git_sandbox_init("icase");
885 git_index *index;
886 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
887 bool native_ignore_case;
888 status_entry_counts counts;
889
890 cl_git_pass(git_repository_index(&index, repo));
891 native_ignore_case =
892 (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
893 git_index_free(index);
894
895 memset(&counts, 0, sizeof(counts));
896 counts.expected_entry_count = 0;
897 counts.expected_paths = NULL;
898 counts.expected_statuses = NULL;
899 cl_git_pass(
900 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
901 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
902 cl_assert_equal_i(0, counts.wrong_status_flags_count);
903 cl_assert_equal_i(0, counts.wrong_sorted_path);
904
905 cl_git_rewritefile("icase/B", "new stuff");
906 cl_must_pass(p_unlink("icase/c"));
907 cl_git_rewritefile("icase/g", "new stuff");
908 cl_must_pass(p_unlink("icase/H"));
909
910 memset(&counts, 0, sizeof(counts));
911 counts.expected_entry_count = 4;
912 if (native_ignore_case) {
913 counts.expected_paths = icase_paths;
914 counts.expected_statuses = icase_statuses;
915 } else {
916 counts.expected_paths = case_paths;
917 counts.expected_statuses = case_statuses;
918 }
919 cl_git_pass(
920 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
921 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
922 cl_assert_equal_i(0, counts.wrong_status_flags_count);
923 cl_assert_equal_i(0, counts.wrong_sorted_path);
924
925 opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
926
927 memset(&counts, 0, sizeof(counts));
928 counts.expected_entry_count = 4;
929 counts.expected_paths = case_paths;
930 counts.expected_statuses = case_statuses;
931 cl_git_pass(
932 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
933 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
934 cl_assert_equal_i(0, counts.wrong_status_flags_count);
935 cl_assert_equal_i(0, counts.wrong_sorted_path);
936
937 opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
938
939 memset(&counts, 0, sizeof(counts));
940 counts.expected_entry_count = 4;
941 counts.expected_paths = icase_paths;
942 counts.expected_statuses = icase_statuses;
943 cl_git_pass(
944 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
945 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
946 cl_assert_equal_i(0, counts.wrong_status_flags_count);
947 cl_assert_equal_i(0, counts.wrong_sorted_path);
948 }
949
950 void test_status_worktree__long_filenames(void)
951 {
952 char path[260*4+1];
953 const char *expected_paths[] = {path};
954 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
955
956 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
957 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
958 status_entry_counts counts = {0};
959
960 // Create directory with amazingly long filename
961 sprintf(path, "empty_standard_repo/%s", longname);
962 cl_git_pass(git_futils_mkdir_r(path, 0777));
963 sprintf(path, "empty_standard_repo/%s/foo", longname);
964 cl_git_mkfile(path, "dummy");
965
966 sprintf(path, "%s/foo", longname);
967 counts.expected_entry_count = 1;
968 counts.expected_paths = expected_paths;
969 counts.expected_statuses = expected_statuses;
970
971 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
972 opts.flags = GIT_STATUS_OPT_DEFAULTS;
973
974 cl_git_pass(
975 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
976 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
977 cl_assert_equal_i(0, counts.wrong_status_flags_count);
978 cl_assert_equal_i(0, counts.wrong_sorted_path);
979 }
980
981 /* The update stat cache tests mostly just mirror other tests and try
982 * to make sure that updating the stat cache doesn't change the results
983 * while reducing the amount of work that needs to be done
984 */
985
986 static void check_status0(git_status_list *status)
987 {
988 size_t i, max_i = git_status_list_entrycount(status);
989 cl_assert_equal_sz(entry_count0, max_i);
990 for (i = 0; i < max_i; ++i) {
991 const git_status_entry *entry = git_status_byindex(status, i);
992 cl_assert_equal_i(entry_statuses0[i], entry->status);
993 }
994 }
995
996 void test_status_worktree__update_stat_cache_0(void)
997 {
998 git_repository *repo = cl_git_sandbox_init("status");
999 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1000 git_status_list *status;
1001 git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
1002 git_index *index;
1003
1004 opts.flags = GIT_STATUS_OPT_DEFAULTS;
1005
1006 cl_git_pass(git_status_list_new(&status, repo, &opts));
1007 check_status0(status);
1008 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1009 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1010 cl_assert_equal_sz(5, perf.oid_calculations);
1011
1012 git_status_list_free(status);
1013
1014 /* tick the index so we avoid recalculating racily-clean entries */
1015 cl_git_pass(git_repository_index__weakptr(&index, repo));
1016 tick_index(index);
1017
1018 opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX;
1019
1020 cl_git_pass(git_status_list_new(&status, repo, &opts));
1021 check_status0(status);
1022 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1023 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1024 cl_assert_equal_sz(5, perf.oid_calculations);
1025
1026 git_status_list_free(status);
1027
1028 opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX;
1029
1030 /* tick again as the index updating from the previous diff might have reset the timestamp */
1031 tick_index(index);
1032 cl_git_pass(git_status_list_new(&status, repo, &opts));
1033 check_status0(status);
1034 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1035 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1036 cl_assert_equal_sz(0, perf.oid_calculations);
1037
1038 git_status_list_free(status);
1039 }
1040
1041 void test_status_worktree__unreadable(void)
1042 {
1043 #ifndef GIT_WIN32
1044 const char *expected_paths[] = { "no_permission/foo" };
1045 const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE};
1046
1047 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1048 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1049 status_entry_counts counts = {0};
1050
1051 if (geteuid() == 0)
1052 cl_skip();
1053
1054 /* Create directory with no read permission */
1055 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1056 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1057 p_chmod("empty_standard_repo/no_permission", 0644);
1058
1059 counts.expected_entry_count = 1;
1060 counts.expected_paths = expected_paths;
1061 counts.expected_statuses = expected_statuses;
1062
1063 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1064 opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE;
1065
1066 cl_git_pass(
1067 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1068
1069 /* Restore permissions so we can cleanup :) */
1070 p_chmod("empty_standard_repo/no_permission", 0777);
1071
1072 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1073 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1074 cl_assert_equal_i(0, counts.wrong_sorted_path);
1075 #else
1076 cl_skip();
1077 #endif
1078 }
1079
1080 void test_status_worktree__unreadable_not_included(void)
1081 {
1082 #ifndef GIT_WIN32
1083 const char *expected_paths[] = { "no_permission/" };
1084 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1085
1086 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1087 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1088 status_entry_counts counts = {0};
1089
1090 /* Create directory with no read permission */
1091 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1092 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1093 p_chmod("empty_standard_repo/no_permission", 0644);
1094
1095 counts.expected_entry_count = 1;
1096 counts.expected_paths = expected_paths;
1097 counts.expected_statuses = expected_statuses;
1098
1099 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1100 opts.flags = (GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED);
1101
1102 cl_git_pass(
1103 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1104
1105 /* Restore permissions so we can cleanup :) */
1106 p_chmod("empty_standard_repo/no_permission", 0777);
1107
1108 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1109 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1110 cl_assert_equal_i(0, counts.wrong_sorted_path);
1111 #else
1112 cl_skip();
1113 #endif
1114 }
1115
1116 void test_status_worktree__unreadable_as_untracked(void)
1117 {
1118 const char *expected_paths[] = { "no_permission/foo" };
1119 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1120
1121 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1122 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1123 status_entry_counts counts = {0};
1124
1125 /* Create directory with no read permission */
1126 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1127 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1128 p_chmod("empty_standard_repo/no_permission", 0644);
1129
1130 counts.expected_entry_count = 1;
1131 counts.expected_paths = expected_paths;
1132 counts.expected_statuses = expected_statuses;
1133
1134 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1135 opts.flags = GIT_STATUS_OPT_DEFAULTS |
1136 GIT_STATUS_OPT_INCLUDE_UNREADABLE |
1137 GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED;
1138
1139 cl_git_pass(
1140 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1141
1142 /* Restore permissions so we can cleanup :) */
1143 p_chmod("empty_standard_repo/no_permission", 0777);
1144
1145 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1146 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1147 cl_assert_equal_i(0, counts.wrong_sorted_path);
1148 }
1149
1150 void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void)
1151 {
1152 git_repository *repo = cl_git_sandbox_init("testrepo");
1153 git_reference *head;
1154 git_object *head_object;
1155 git_index *index;
1156 const git_index_entry *idx_entry;
1157 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1158 status_entry_counts counts = {0};
1159 const char *expected_paths[] = { "README" };
1160 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1161
1162 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1163 opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_UPDATE_INDEX;
1164
1165 cl_git_pass(git_repository_head(&head, repo));
1166 cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT));
1167
1168 cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL));
1169
1170 cl_git_rewritefile("testrepo/README", "This was rewritten.");
1171
1172 /* this status rewrites the index because we have changed the
1173 * contents of a tracked file
1174 */
1175 counts.expected_entry_count = 1;
1176 counts.expected_paths = expected_paths;
1177 counts.expected_statuses = expected_statuses;
1178
1179 cl_git_pass(
1180 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
1181 cl_assert_equal_i(1, counts.entry_count);
1182
1183 /* now ensure that the status's rewrite of the index did not screw
1184 * up the mode of the symlink `link_to_new.txt`, particularly
1185 * on platforms that don't support symlinks
1186 */
1187 cl_git_pass(git_repository_index(&index, repo));
1188 cl_git_pass(git_index_read(index, true));
1189
1190 cl_assert(idx_entry = git_index_get_bypath(index, "link_to_new.txt", 0));
1191 cl_assert(S_ISLNK(idx_entry->mode));
1192
1193 git_index_free(index);
1194 git_object_free(head_object);
1195 git_reference_free(head);
1196 }
1197
1198 static const char *testrepo2_subdir_paths[] = {
1199 "subdir/README",
1200 "subdir/new.txt",
1201 "subdir/subdir2/README",
1202 "subdir/subdir2/new.txt",
1203 };
1204
1205 static const char *testrepo2_subdir_paths_icase[] = {
1206 "subdir/new.txt",
1207 "subdir/README",
1208 "subdir/subdir2/new.txt",
1209 "subdir/subdir2/README"
1210 };
1211
1212 void test_status_worktree__with_directory_in_pathlist(void)
1213 {
1214 git_repository *repo = cl_git_sandbox_init("testrepo2");
1215 git_index *index;
1216 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1217 git_status_list *statuslist;
1218 const git_status_entry *status;
1219 size_t i, entrycount;
1220 bool native_ignore_case;
1221 char *subdir_path = "subdir";
1222
1223 cl_git_pass(git_repository_index(&index, repo));
1224 native_ignore_case =
1225 (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
1226 git_index_free(index);
1227
1228 opts.pathspec.strings = &subdir_path;
1229 opts.pathspec.count = 1;
1230 opts.flags =
1231 GIT_STATUS_OPT_DEFAULTS |
1232 GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
1233 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
1234
1235 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1236 git_status_list_new(&statuslist, repo, &opts);
1237
1238 entrycount = git_status_list_entrycount(statuslist);
1239 cl_assert_equal_i(4, entrycount);
1240
1241 for (i = 0; i < entrycount; i++) {
1242 status = git_status_byindex(statuslist, i);
1243 cl_assert_equal_i(0, status->status);
1244 cl_assert_equal_s(native_ignore_case ?
1245 testrepo2_subdir_paths_icase[i] :
1246 testrepo2_subdir_paths[i],
1247 status->index_to_workdir->old_file.path);
1248 }
1249
1250 git_status_list_free(statuslist);
1251
1252 opts.show = GIT_STATUS_SHOW_INDEX_ONLY;
1253 git_status_list_new(&statuslist, repo, &opts);
1254
1255 entrycount = git_status_list_entrycount(statuslist);
1256 cl_assert_equal_i(4, entrycount);
1257
1258 for (i = 0; i < entrycount; i++) {
1259 status = git_status_byindex(statuslist, i);
1260 cl_assert_equal_i(0, status->status);
1261 cl_assert_equal_s(native_ignore_case ?
1262 testrepo2_subdir_paths_icase[i] :
1263 testrepo2_subdir_paths[i],
1264 status->head_to_index->old_file.path);
1265 }
1266
1267 git_status_list_free(statuslist);
1268
1269 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1270 git_status_list_new(&statuslist, repo, &opts);
1271
1272 entrycount = git_status_list_entrycount(statuslist);
1273 cl_assert_equal_i(4, entrycount);
1274
1275 for (i = 0; i < entrycount; i++) {
1276 status = git_status_byindex(statuslist, i);
1277 cl_assert_equal_i(0, status->status);
1278 cl_assert_equal_s(native_ignore_case ?
1279 testrepo2_subdir_paths_icase[i] :
1280 testrepo2_subdir_paths[i],
1281 status->index_to_workdir->old_file.path);
1282 }
1283
1284 git_status_list_free(statuslist);
1285 }
1286
1287 void test_status_worktree__at_head_parent(void)
1288 {
1289 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1290 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1291 git_status_list *statuslist;
1292 git_tree *parent_tree;
1293 const git_status_entry *status;
1294
1295 cl_git_mkfile("empty_standard_repo/file1", "ping");
1296 stage_and_commit(repo, "file1");
1297
1298 cl_git_pass(git_repository_head_tree(&parent_tree, repo));
1299
1300 cl_git_mkfile("empty_standard_repo/file2", "pong");
1301 stage_and_commit(repo, "file2");
1302
1303 cl_git_rewritefile("empty_standard_repo/file2", "pyng");
1304
1305 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1306 opts.baseline = parent_tree;
1307 cl_git_pass(git_status_list_new(&statuslist, repo, &opts));
1308
1309 cl_assert_equal_sz(1, git_status_list_entrycount(statuslist));
1310 status = git_status_byindex(statuslist, 0);
1311 cl_assert(status != NULL);
1312 cl_assert_equal_s("file2", status->index_to_workdir->old_file.path);
1313 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, status->status);
1314
1315 git_tree_free(parent_tree);
1316 git_status_list_free(statuslist);
1317 }