]> git.proxmox.com Git - libgit2.git/blob - tests/libgit2/status/worktree.c
New upstream version 1.5.0+ds
[libgit2.git] / tests / libgit2 / status / worktree.c
1 #include "clar_libgit2.h"
2 #include "futils.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 static 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_str *file)
104 {
105 const char *filename = git_str_cstr(file);
106
107 GIT_UNUSED(data);
108
109 if (git__suffixcmp(filename, ".git") == 0)
110 return 0;
111
112 if (git_fs_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_str_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_str workdir = GIT_STR_INIT;
126
127 /* first purge the contents of the worktree */
128 cl_git_pass(git_str_sets(&workdir, git_repository_workdir(repo)));
129 cl_git_pass(git_fs_path_direach(&workdir, 0, remove_file_cb, NULL));
130 git_str_dispose(&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_INDEX_CAPABILITY_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_str path = GIT_STR_INIT;
373
374 repo = cl_git_sandbox_init("issue_592");
375 cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "l.txt"));
376 cl_git_pass(p_unlink(git_str_cstr(&path)));
377 cl_assert(!git_fs_path_exists("issue_592/l.txt"));
378
379 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
380
381 git_str_dispose(&path);
382 }
383
384 void test_status_worktree__issue_592_2(void)
385 {
386 git_repository *repo;
387 git_str path = GIT_STR_INIT;
388
389 repo = cl_git_sandbox_init("issue_592");
390 cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c/a.txt"));
391 cl_git_pass(p_unlink(git_str_cstr(&path)));
392 cl_assert(!git_fs_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_str_dispose(&path);
397 }
398
399 void test_status_worktree__issue_592_3(void)
400 {
401 git_repository *repo;
402 git_str path = GIT_STR_INIT;
403
404 repo = cl_git_sandbox_init("issue_592");
405
406 cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c"));
407 cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
408 cl_assert(!git_fs_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_str_dispose(&path);
413 }
414
415 void test_status_worktree__issue_592_4(void)
416 {
417 git_repository *repo;
418 git_str path = GIT_STR_INIT;
419
420 repo = cl_git_sandbox_init("issue_592");
421
422 cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t/b.txt"));
423 cl_git_pass(p_unlink(git_str_cstr(&path)));
424
425 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt"));
426
427 git_str_dispose(&path);
428 }
429
430 void test_status_worktree__issue_592_5(void)
431 {
432 git_repository *repo;
433 git_str path = GIT_STR_INIT;
434
435 repo = cl_git_sandbox_init("issue_592");
436
437 cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t"));
438 cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
439 cl_git_pass(p_mkdir(git_str_cstr(&path), 0777));
440
441 cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
442
443 git_str_dispose(&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 void test_status_worktree__filemode_non755(void)
609 {
610 git_repository *repo = cl_git_sandbox_init("filemodes");
611 status_entry_counts counts;
612 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
613 git_str executable_path = GIT_STR_INIT;
614 git_str nonexecutable_path = GIT_STR_INIT;
615
616 if (!cl_is_chmod_supported())
617 return;
618
619 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
620 GIT_STATUS_OPT_INCLUDE_IGNORED |
621 GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
622
623 git_str_joinpath(&executable_path, git_repository_workdir(repo), "exec_on");
624 cl_must_pass(p_chmod(git_str_cstr(&executable_path), 0744));
625 git_str_dispose(&executable_path);
626
627 git_str_joinpath(&nonexecutable_path, git_repository_workdir(repo), "exec_off");
628
629 cl_must_pass(p_chmod(git_str_cstr(&nonexecutable_path), 0655));
630 git_str_dispose(&nonexecutable_path);
631
632 memset(&counts, 0, sizeof(counts));
633 counts.expected_entry_count = filemode_count;
634 counts.expected_paths = filemode_paths;
635 counts.expected_statuses = filemode_statuses;
636
637 cl_git_pass(
638 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
639 );
640
641 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
642 cl_assert_equal_i(0, counts.wrong_status_flags_count);
643 cl_assert_equal_i(0, counts.wrong_sorted_path);
644 }
645
646
647 static int cb_status__interrupt(const char *p, unsigned int s, void *payload)
648 {
649 volatile int *count = (int *)payload;
650
651 GIT_UNUSED(p);
652 GIT_UNUSED(s);
653
654 (*count)++;
655
656 return (*count == 8) ? -111 : 0;
657 }
658
659 void test_status_worktree__interruptable_foreach(void)
660 {
661 int count = 0;
662 git_repository *repo = cl_git_sandbox_init("status");
663
664 cl_assert_equal_i(
665 -111, git_status_foreach(repo, cb_status__interrupt, &count)
666 );
667
668 cl_assert_equal_i(8, count);
669 }
670
671 void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void)
672 {
673 git_repository *repo = cl_git_sandbox_init("status");
674 unsigned int status;
675
676 cl_repo_set_bool(repo, "core.autocrlf", true);
677
678 cl_git_rewritefile("status/current_file", "current_file\r\n");
679
680 cl_git_pass(git_status_file(&status, repo, "current_file"));
681
682 /* stat data on file should no longer match stat cache, even though
683 * file diff will be empty because of line-ending conversion - matches
684 * the Git command-line behavior here.
685 */
686 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
687 }
688
689 void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void)
690 {
691 git_repository *repo = cl_git_sandbox_init("issue_1397");
692 unsigned int status;
693
694 cl_repo_set_bool(repo, "core.autocrlf", true);
695
696 cl_git_pass(git_status_file(&status, repo, "crlf_file.txt"));
697
698 cl_assert_equal_i(GIT_STATUS_CURRENT, status);
699 }
700
701 void test_status_worktree__conflicted_item(void)
702 {
703 git_repository *repo = cl_git_sandbox_init("status");
704 git_index *index;
705 unsigned int status;
706 git_index_entry ancestor_entry, our_entry, their_entry;
707
708 memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
709 memset(&our_entry, 0x0, sizeof(git_index_entry));
710 memset(&their_entry, 0x0, sizeof(git_index_entry));
711
712 ancestor_entry.mode = 0100644;
713 ancestor_entry.path = "modified_file";
714 git_oid_fromstr(&ancestor_entry.id,
715 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
716
717 our_entry.mode = 0100644;
718 our_entry.path = "modified_file";
719 git_oid_fromstr(&our_entry.id,
720 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
721
722 their_entry.mode = 0100644;
723 their_entry.path = "modified_file";
724 git_oid_fromstr(&their_entry.id,
725 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
726
727 cl_git_pass(git_status_file(&status, repo, "modified_file"));
728 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
729
730 cl_git_pass(git_repository_index(&index, repo));
731 cl_git_pass(git_index_conflict_add(index, &ancestor_entry,
732 &our_entry, &their_entry));
733
734 cl_git_pass(git_status_file(&status, repo, "modified_file"));
735 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status);
736
737 git_index_free(index);
738 }
739
740 void test_status_worktree__conflict_has_no_oid(void)
741 {
742 git_repository *repo = cl_git_sandbox_init("status");
743 git_index *index;
744 git_index_entry entry = {{0}};
745 git_status_list *statuslist;
746 const git_status_entry *status;
747 git_oid zero_id = {{0}};
748
749 entry.mode = 0100644;
750 entry.path = "modified_file";
751 git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
752
753 cl_git_pass(git_repository_index(&index, repo));
754 cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry));
755
756 git_status_list_new(&statuslist, repo, NULL);
757
758 cl_assert_equal_i(16, git_status_list_entrycount(statuslist));
759
760 status = git_status_byindex(statuslist, 2);
761
762 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status->status);
763 cl_assert_equal_s("modified_file", status->head_to_index->old_file.path);
764 cl_assert(!git_oid_equal(&zero_id, &status->head_to_index->old_file.id));
765 cl_assert(0 != status->head_to_index->old_file.mode);
766 cl_assert_equal_s("modified_file", status->head_to_index->new_file.path);
767 cl_assert_equal_oid(&zero_id, &status->head_to_index->new_file.id);
768 cl_assert_equal_i(0, status->head_to_index->new_file.mode);
769 cl_assert_equal_i(0, status->head_to_index->new_file.size);
770
771 cl_assert_equal_s("modified_file", status->index_to_workdir->old_file.path);
772 cl_assert_equal_oid(&zero_id, &status->index_to_workdir->old_file.id);
773 cl_assert_equal_i(0, status->index_to_workdir->old_file.mode);
774 cl_assert_equal_i(0, status->index_to_workdir->old_file.size);
775 cl_assert_equal_s("modified_file", status->index_to_workdir->new_file.path);
776 cl_assert(
777 !git_oid_equal(&zero_id, &status->index_to_workdir->new_file.id) ||
778 !(status->index_to_workdir->new_file.flags & GIT_DIFF_FLAG_VALID_ID));
779 cl_assert(0 != status->index_to_workdir->new_file.mode);
780 cl_assert(0 != status->index_to_workdir->new_file.size);
781
782 git_index_free(index);
783 git_status_list_free(statuslist);
784 }
785
786 static void assert_ignore_case(
787 bool should_ignore_case,
788 int expected_lower_cased_file_status,
789 int expected_camel_cased_file_status)
790 {
791 unsigned int status;
792 git_str lower_case_path = GIT_STR_INIT, camel_case_path = GIT_STR_INIT;
793 git_repository *repo, *repo2;
794
795 repo = cl_git_sandbox_init("empty_standard_repo");
796 cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
797
798 cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case);
799
800 cl_git_pass(git_str_joinpath(&lower_case_path,
801 git_repository_workdir(repo), "plop"));
802
803 cl_git_mkfile(git_str_cstr(&lower_case_path), "");
804
805 stage_and_commit(repo, "plop");
806
807 cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo"));
808
809 cl_git_pass(git_status_file(&status, repo2, "plop"));
810 cl_assert_equal_i(GIT_STATUS_CURRENT, status);
811
812 cl_git_pass(git_str_joinpath(&camel_case_path,
813 git_repository_workdir(repo), "Plop"));
814
815 cl_git_pass(p_rename(git_str_cstr(&lower_case_path), git_str_cstr(&camel_case_path)));
816
817 cl_git_pass(git_status_file(&status, repo2, "plop"));
818 cl_assert_equal_i(expected_lower_cased_file_status, status);
819
820 cl_git_pass(git_status_file(&status, repo2, "Plop"));
821 cl_assert_equal_i(expected_camel_cased_file_status, status);
822
823 git_repository_free(repo2);
824 git_str_dispose(&lower_case_path);
825 git_str_dispose(&camel_case_path);
826 }
827
828 void test_status_worktree__file_status_honors_core_ignorecase_true(void)
829 {
830 assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT);
831 }
832
833 void test_status_worktree__file_status_honors_core_ignorecase_false(void)
834 {
835 assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW);
836 }
837
838 void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracked_files(void)
839 {
840 git_repository *repo = cl_git_sandbox_init("status");
841 unsigned int status;
842 git_index *index;
843
844 cl_repo_set_bool(repo, "core.ignorecase", false);
845
846 repo = cl_git_sandbox_reopen();
847
848 /* Actually returns GIT_STATUS_IGNORED on Windows */
849 cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
850
851 cl_git_pass(git_repository_index(&index, repo));
852
853 cl_git_pass(git_index_add_bypath(index, "new_file"));
854 cl_git_pass(git_index_write(index));
855 git_index_free(index);
856
857 /* Actually returns GIT_STATUS_IGNORED on Windows */
858 cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
859 }
860
861 void test_status_worktree__simple_delete(void)
862 {
863 git_repository *repo = cl_git_sandbox_init("renames");
864 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
865 int count;
866
867 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
868 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
869 GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
870 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
871
872 count = 0;
873 cl_git_pass(
874 git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
875 cl_assert_equal_i(0, count);
876
877 cl_must_pass(p_unlink("renames/untimely.txt"));
878
879 count = 0;
880 cl_git_pass(
881 git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
882 cl_assert_equal_i(1, count);
883 }
884
885 void test_status_worktree__simple_delete_indexed(void)
886 {
887 git_repository *repo = cl_git_sandbox_init("renames");
888 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
889 git_status_list *status;
890
891 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
892 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
893 GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
894 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
895
896 cl_git_pass(git_status_list_new(&status, repo, &opts));
897 cl_assert_equal_sz(0, git_status_list_entrycount(status));
898 git_status_list_free(status);
899
900 cl_must_pass(p_unlink("renames/untimely.txt"));
901
902 cl_git_pass(git_status_list_new(&status, repo, &opts));
903 cl_assert_equal_sz(1, git_status_list_entrycount(status));
904 cl_assert_equal_i(
905 GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
906 git_status_list_free(status);
907 }
908
909 static const char *icase_paths[] = { "B", "c", "g", "H" };
910 static unsigned int icase_statuses[] = {
911 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
912 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
913 };
914
915 static const char *case_paths[] = { "B", "H", "c", "g" };
916 static unsigned int case_statuses[] = {
917 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
918 GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED,
919 };
920
921 void test_status_worktree__sorting_by_case(void)
922 {
923 git_repository *repo = cl_git_sandbox_init("icase");
924 git_index *index;
925 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
926 bool native_ignore_case;
927 status_entry_counts counts;
928
929 cl_git_pass(git_repository_index(&index, repo));
930 native_ignore_case =
931 (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0;
932 git_index_free(index);
933
934 memset(&counts, 0, sizeof(counts));
935 counts.expected_entry_count = 0;
936 counts.expected_paths = NULL;
937 counts.expected_statuses = NULL;
938 cl_git_pass(
939 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
940 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
941 cl_assert_equal_i(0, counts.wrong_status_flags_count);
942 cl_assert_equal_i(0, counts.wrong_sorted_path);
943
944 cl_git_rewritefile("icase/B", "new stuff");
945 cl_must_pass(p_unlink("icase/c"));
946 cl_git_rewritefile("icase/g", "new stuff");
947 cl_must_pass(p_unlink("icase/H"));
948
949 memset(&counts, 0, sizeof(counts));
950 counts.expected_entry_count = 4;
951 if (native_ignore_case) {
952 counts.expected_paths = icase_paths;
953 counts.expected_statuses = icase_statuses;
954 } else {
955 counts.expected_paths = case_paths;
956 counts.expected_statuses = case_statuses;
957 }
958 cl_git_pass(
959 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
960 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
961 cl_assert_equal_i(0, counts.wrong_status_flags_count);
962 cl_assert_equal_i(0, counts.wrong_sorted_path);
963
964 opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
965
966 memset(&counts, 0, sizeof(counts));
967 counts.expected_entry_count = 4;
968 counts.expected_paths = case_paths;
969 counts.expected_statuses = case_statuses;
970 cl_git_pass(
971 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
972 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
973 cl_assert_equal_i(0, counts.wrong_status_flags_count);
974 cl_assert_equal_i(0, counts.wrong_sorted_path);
975
976 opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
977
978 memset(&counts, 0, sizeof(counts));
979 counts.expected_entry_count = 4;
980 counts.expected_paths = icase_paths;
981 counts.expected_statuses = icase_statuses;
982 cl_git_pass(
983 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
984 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
985 cl_assert_equal_i(0, counts.wrong_status_flags_count);
986 cl_assert_equal_i(0, counts.wrong_sorted_path);
987 }
988
989 void test_status_worktree__long_filenames(void)
990 {
991 char path[260*4+1] = {0};
992 const char *expected_paths[] = {path};
993 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
994
995 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
996 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
997 status_entry_counts counts = {0};
998
999 /* Create directory with amazingly long filename */
1000 sprintf(path, "empty_standard_repo/%s", longname);
1001 cl_git_pass(git_futils_mkdir_r(path, 0777));
1002 sprintf(path, "empty_standard_repo/%s/foo", longname);
1003 cl_git_mkfile(path, "dummy");
1004
1005 sprintf(path, "%s/foo", longname);
1006 counts.expected_entry_count = 1;
1007 counts.expected_paths = expected_paths;
1008 counts.expected_statuses = expected_statuses;
1009
1010 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1011 opts.flags = GIT_STATUS_OPT_DEFAULTS;
1012
1013 cl_git_pass(
1014 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1015 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1016 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1017 cl_assert_equal_i(0, counts.wrong_sorted_path);
1018 }
1019
1020 /* The update stat cache tests mostly just mirror other tests and try
1021 * to make sure that updating the stat cache doesn't change the results
1022 * while reducing the amount of work that needs to be done
1023 */
1024
1025 static void check_status0(git_status_list *status)
1026 {
1027 size_t i, max_i = git_status_list_entrycount(status);
1028 cl_assert_equal_sz(entry_count0, max_i);
1029 for (i = 0; i < max_i; ++i) {
1030 const git_status_entry *entry = git_status_byindex(status, i);
1031 cl_assert_equal_i(entry_statuses0[i], entry->status);
1032 }
1033 }
1034
1035 void test_status_worktree__update_stat_cache_0(void)
1036 {
1037 git_repository *repo = cl_git_sandbox_init("status");
1038 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1039 git_status_list *status;
1040 git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
1041 git_index *index;
1042
1043 opts.flags = GIT_STATUS_OPT_DEFAULTS;
1044
1045 cl_git_pass(git_status_list_new(&status, repo, &opts));
1046 check_status0(status);
1047 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1048 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1049 cl_assert_equal_sz(5, perf.oid_calculations);
1050
1051 git_status_list_free(status);
1052
1053 /* tick the index so we avoid recalculating racily-clean entries */
1054 cl_git_pass(git_repository_index__weakptr(&index, repo));
1055 tick_index(index);
1056
1057 opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX;
1058
1059 cl_git_pass(git_status_list_new(&status, repo, &opts));
1060 check_status0(status);
1061 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1062 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1063 cl_assert_equal_sz(5, perf.oid_calculations);
1064
1065 git_status_list_free(status);
1066
1067 opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX;
1068
1069 /* tick again as the index updating from the previous diff might have reset the timestamp */
1070 tick_index(index);
1071 cl_git_pass(git_status_list_new(&status, repo, &opts));
1072 check_status0(status);
1073 cl_git_pass(git_status_list_get_perfdata(&perf, status));
1074 cl_assert_equal_sz(13 + 3, perf.stat_calls);
1075 cl_assert_equal_sz(0, perf.oid_calculations);
1076
1077 git_status_list_free(status);
1078 }
1079
1080 void test_status_worktree__unreadable(void)
1081 {
1082 #ifndef GIT_WIN32
1083 const char *expected_paths[] = { "no_permission/foo" };
1084 const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE};
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 if (geteuid() == 0)
1091 cl_skip();
1092
1093 /* Create directory with no read permission */
1094 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1095 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1096 p_chmod("empty_standard_repo/no_permission", 0644);
1097
1098 counts.expected_entry_count = 1;
1099 counts.expected_paths = expected_paths;
1100 counts.expected_statuses = expected_statuses;
1101
1102 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1103 opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE;
1104
1105 cl_git_pass(
1106 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1107
1108 /* Restore permissions so we can cleanup :) */
1109 p_chmod("empty_standard_repo/no_permission", 0777);
1110
1111 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1112 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1113 cl_assert_equal_i(0, counts.wrong_sorted_path);
1114 #else
1115 cl_skip();
1116 #endif
1117 }
1118
1119 void test_status_worktree__unreadable_not_included(void)
1120 {
1121 #ifndef GIT_WIN32
1122 const char *expected_paths[] = { "no_permission/" };
1123 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1124
1125 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1126 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1127 status_entry_counts counts = {0};
1128
1129 /* Create directory with no read permission */
1130 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1131 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1132 p_chmod("empty_standard_repo/no_permission", 0644);
1133
1134 counts.expected_entry_count = 1;
1135 counts.expected_paths = expected_paths;
1136 counts.expected_statuses = expected_statuses;
1137
1138 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1139 opts.flags = (GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED);
1140
1141 cl_git_pass(
1142 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1143
1144 /* Restore permissions so we can cleanup :) */
1145 p_chmod("empty_standard_repo/no_permission", 0777);
1146
1147 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1148 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1149 cl_assert_equal_i(0, counts.wrong_sorted_path);
1150 #else
1151 cl_skip();
1152 #endif
1153 }
1154
1155 void test_status_worktree__unreadable_as_untracked(void)
1156 {
1157 const char *expected_paths[] = { "no_permission/foo" };
1158 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1159
1160 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1161 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1162 status_entry_counts counts = {0};
1163
1164 /* Create directory with no read permission */
1165 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
1166 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1167 p_chmod("empty_standard_repo/no_permission", 0644);
1168
1169 counts.expected_entry_count = 1;
1170 counts.expected_paths = expected_paths;
1171 counts.expected_statuses = expected_statuses;
1172
1173 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1174 opts.flags = GIT_STATUS_OPT_DEFAULTS |
1175 GIT_STATUS_OPT_INCLUDE_UNREADABLE |
1176 GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED;
1177
1178 cl_git_pass(
1179 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
1180
1181 /* Restore permissions so we can cleanup :) */
1182 p_chmod("empty_standard_repo/no_permission", 0777);
1183
1184 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
1185 cl_assert_equal_i(0, counts.wrong_status_flags_count);
1186 cl_assert_equal_i(0, counts.wrong_sorted_path);
1187 }
1188
1189 void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void)
1190 {
1191 git_repository *repo = cl_git_sandbox_init("testrepo");
1192 git_reference *head;
1193 git_object *head_object;
1194 git_index *index;
1195 const git_index_entry *idx_entry;
1196 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1197 status_entry_counts counts = {0};
1198 const char *expected_paths[] = { "README" };
1199 const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
1200
1201 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1202 opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_UPDATE_INDEX;
1203
1204 cl_git_pass(git_repository_head(&head, repo));
1205 cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT));
1206
1207 cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL));
1208
1209 cl_git_rewritefile("testrepo/README", "This was rewritten.");
1210
1211 /* this status rewrites the index because we have changed the
1212 * contents of a tracked file
1213 */
1214 counts.expected_entry_count = 1;
1215 counts.expected_paths = expected_paths;
1216 counts.expected_statuses = expected_statuses;
1217
1218 cl_git_pass(
1219 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
1220 cl_assert_equal_i(1, counts.entry_count);
1221
1222 /* now ensure that the status's rewrite of the index did not screw
1223 * up the mode of the symlink `link_to_new.txt`, particularly
1224 * on platforms that don't support symlinks
1225 */
1226 cl_git_pass(git_repository_index(&index, repo));
1227 cl_git_pass(git_index_read(index, true));
1228
1229 cl_assert(idx_entry = git_index_get_bypath(index, "link_to_new.txt", 0));
1230 cl_assert(S_ISLNK(idx_entry->mode));
1231
1232 git_index_free(index);
1233 git_object_free(head_object);
1234 git_reference_free(head);
1235 }
1236
1237 static const char *testrepo2_subdir_paths[] = {
1238 "subdir/README",
1239 "subdir/new.txt",
1240 "subdir/subdir2/README",
1241 "subdir/subdir2/new.txt",
1242 };
1243
1244 static const char *testrepo2_subdir_paths_icase[] = {
1245 "subdir/new.txt",
1246 "subdir/README",
1247 "subdir/subdir2/new.txt",
1248 "subdir/subdir2/README"
1249 };
1250
1251 void test_status_worktree__with_directory_in_pathlist(void)
1252 {
1253 git_repository *repo = cl_git_sandbox_init("testrepo2");
1254 git_index *index;
1255 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1256 git_status_list *statuslist;
1257 const git_status_entry *status;
1258 size_t i, entrycount;
1259 bool native_ignore_case;
1260 char *subdir_path = "subdir";
1261
1262 cl_git_pass(git_repository_index(&index, repo));
1263 native_ignore_case =
1264 (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0;
1265 git_index_free(index);
1266
1267 opts.pathspec.strings = &subdir_path;
1268 opts.pathspec.count = 1;
1269 opts.flags =
1270 GIT_STATUS_OPT_DEFAULTS |
1271 GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
1272 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
1273
1274 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1275 git_status_list_new(&statuslist, repo, &opts);
1276
1277 entrycount = git_status_list_entrycount(statuslist);
1278 cl_assert_equal_i(4, entrycount);
1279
1280 for (i = 0; i < entrycount; i++) {
1281 status = git_status_byindex(statuslist, i);
1282 cl_assert_equal_i(0, status->status);
1283 cl_assert_equal_s(native_ignore_case ?
1284 testrepo2_subdir_paths_icase[i] :
1285 testrepo2_subdir_paths[i],
1286 status->index_to_workdir->old_file.path);
1287 }
1288
1289 git_status_list_free(statuslist);
1290
1291 opts.show = GIT_STATUS_SHOW_INDEX_ONLY;
1292 git_status_list_new(&statuslist, repo, &opts);
1293
1294 entrycount = git_status_list_entrycount(statuslist);
1295 cl_assert_equal_i(4, entrycount);
1296
1297 for (i = 0; i < entrycount; i++) {
1298 status = git_status_byindex(statuslist, i);
1299 cl_assert_equal_i(0, status->status);
1300 cl_assert_equal_s(native_ignore_case ?
1301 testrepo2_subdir_paths_icase[i] :
1302 testrepo2_subdir_paths[i],
1303 status->head_to_index->old_file.path);
1304 }
1305
1306 git_status_list_free(statuslist);
1307
1308 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1309 git_status_list_new(&statuslist, repo, &opts);
1310
1311 entrycount = git_status_list_entrycount(statuslist);
1312 cl_assert_equal_i(4, entrycount);
1313
1314 for (i = 0; i < entrycount; i++) {
1315 status = git_status_byindex(statuslist, i);
1316 cl_assert_equal_i(0, status->status);
1317 cl_assert_equal_s(native_ignore_case ?
1318 testrepo2_subdir_paths_icase[i] :
1319 testrepo2_subdir_paths[i],
1320 status->index_to_workdir->old_file.path);
1321 }
1322
1323 git_status_list_free(statuslist);
1324 }
1325
1326 void test_status_worktree__at_head_parent(void)
1327 {
1328 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
1329 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1330 git_status_list *statuslist;
1331 git_tree *parent_tree;
1332 const git_status_entry *status;
1333
1334 cl_git_mkfile("empty_standard_repo/file1", "ping");
1335 stage_and_commit(repo, "file1");
1336
1337 cl_git_pass(git_repository_head_tree(&parent_tree, repo));
1338
1339 cl_git_mkfile("empty_standard_repo/file2", "pong");
1340 stage_and_commit(repo, "file2");
1341
1342 cl_git_rewritefile("empty_standard_repo/file2", "pyng");
1343
1344 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1345 opts.baseline = parent_tree;
1346 cl_git_pass(git_status_list_new(&statuslist, repo, &opts));
1347
1348 cl_assert_equal_sz(1, git_status_list_entrycount(statuslist));
1349 status = git_status_byindex(statuslist, 0);
1350 cl_assert(status != NULL);
1351 cl_assert_equal_s("file2", status->index_to_workdir->old_file.path);
1352 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, status->status);
1353
1354 git_tree_free(parent_tree);
1355 git_status_list_free(statuslist);
1356 }