]> git.proxmox.com Git - libgit2.git/blame - tests/status/worktree.c
New upstream version 0.28.1+dfsg.1
[libgit2.git] / tests / status / worktree.c
CommitLineData
3fd1520c 1#include "clar_libgit2.h"
11385c3c 2#include "fileops.h"
6a67a812 3#include "ignore.h"
11385c3c 4#include "status_data.h"
95340398 5#include "posix.h"
a56aacf4
RB
6#include "util.h"
7#include "path.h"
b23b112d 8#include "../diff/diff_helpers.h"
e44abe16 9#include "../checkout/checkout_helpers.h"
9c8ed499 10#include "git2/sys/diff.h"
11385c3c 11
11385c3c
VM
12/**
13 * Cleanup
14 *
15 * This will be called once after each test finishes, even
16 * if the test failed
17 */
df297a1f 18void test_status_worktree__cleanup(void)
11385c3c 19{
854eccbb 20 cl_git_sandbox_cleanup();
11385c3c
VM
21}
22
23/**
24 * Tests - Status determination on a working tree
25 */
a56aacf4 26/* this test is equivalent to t18-status.c:statuscb0 */
a5f8c1bd 27void test_status_worktree__whole_repository(void)
11385c3c 28{
dc13f1f7 29 status_entry_counts counts;
854eccbb 30 git_repository *repo = cl_git_sandbox_init("status");
11385c3c 31
dc13f1f7 32 memset(&counts, 0x0, sizeof(status_entry_counts));
11385c3c
VM
33 counts.expected_entry_count = entry_count0;
34 counts.expected_paths = entry_paths0;
35 counts.expected_statuses = entry_statuses0;
36
2014021b 37 cl_git_pass(
854eccbb 38 git_status_foreach(repo, cb_status__normal, &counts)
2014021b 39 );
11385c3c 40
bd4ca902
RB
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);
11385c3c
VM
44}
45
cd424ad5
RB
46void 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)
9b6075b2 53{
54 status_entry_counts counts;
9b6075b2 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
cd424ad5 62 opts.flags = GIT_STATUS_OPT_DEFAULTS | extra_flags;
9b6075b2 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
74void test_status_worktree__show_index_and_workdir(void)
75{
76 assert_show(entry_count0, entry_paths0, entry_statuses0,
cd424ad5 77 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_AND_WORKDIR, 0);
9b6075b2 78}
79
80void test_status_worktree__show_index_only(void)
81{
82 assert_show(entry_count5, entry_paths5, entry_statuses5,
cd424ad5 83 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_ONLY, 0);
9b6075b2 84}
85
86void test_status_worktree__show_workdir_only(void)
87{
88 assert_show(entry_count6, entry_paths6, entry_statuses6,
cd424ad5 89 cl_git_sandbox_init("status"), GIT_STATUS_SHOW_WORKDIR_ONLY, 0);
9b6075b2 90}
91
a56aacf4 92/* this test is equivalent to t18-status.c:statuscb1 */
a5f8c1bd 93void test_status_worktree__empty_repository(void)
11385c3c
VM
94{
95 int count = 0;
854eccbb
RB
96 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
97
98 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
11385c3c 99
bd4ca902 100 cl_assert_equal_i(0, count);
11385c3c 101}
df743c7d 102
a56aacf4
RB
103static 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))
331e7de9 113 cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES));
a56aacf4
RB
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 */
121void test_status_worktree__purged_worktree(void)
122{
dc13f1f7 123 status_entry_counts counts;
a56aacf4
RB
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)));
219d3457 129 cl_git_pass(git_path_direach(&workdir, 0, remove_file_cb, NULL));
ac3d33df 130 git_buf_dispose(&workdir);
a56aacf4
RB
131
132 /* now get status */
dc13f1f7 133 memset(&counts, 0x0, sizeof(status_entry_counts));
a56aacf4
RB
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
bd4ca902
RB
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);
a56aacf4
RB
145}
146
c8838ee9 147/* this test is similar to t18-status.c:statuscb3 */
a56aacf4
RB
148void test_status_worktree__swap_subdir_and_file(void)
149{
dc13f1f7 150 status_entry_counts counts;
a56aacf4 151 git_repository *repo = cl_git_sandbox_init("status");
ec40b7f9 152 git_index *index;
79cfa20d 153 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
ec40b7f9
PK
154 bool ignore_case;
155
156 cl_git_pass(git_repository_index(&index, repo));
ac3d33df 157 ignore_case = (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0;
ec40b7f9 158 git_index_free(index);
a56aacf4
RB
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 */
dc13f1f7 170 memset(&counts, 0x0, sizeof(status_entry_counts));
a56aacf4 171 counts.expected_entry_count = entry_count3;
ec40b7f9
PK
172 counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3;
173 counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3;
a56aacf4 174
c8838ee9
RB
175 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
176 GIT_STATUS_OPT_INCLUDE_IGNORED;
177
a56aacf4 178 cl_git_pass(
c8838ee9 179 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
a56aacf4
RB
180 );
181
bd4ca902
RB
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);
a56aacf4
RB
185}
186
4b136a94
RB
187void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
188{
dc13f1f7 189 status_entry_counts counts;
4b136a94 190 git_repository *repo = cl_git_sandbox_init("status");
79cfa20d 191 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
4b136a94
RB
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");
ac2fba0e 198 cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777));
4b136a94
RB
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 */
dc13f1f7 203 memset(&counts, 0x0, sizeof(status_entry_counts));
4b136a94
RB
204 counts.expected_entry_count = entry_count4;
205 counts.expected_paths = entry_paths4;
206 counts.expected_statuses = entry_statuses4;
207
4b136a94
RB
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
bd4ca902
RB
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);
4b136a94
RB
219}
220
b6204260 221static 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
231void 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
ac3d33df 263 /* We committed zzz_new_dir/new_file above. It shouldn't be reported. */
b6204260 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
a56aacf4 273/* this test is equivalent to t18-status.c:singlestatus0 */
df743c7d
RB
274void test_status_worktree__single_file(void)
275{
276 int i;
277 unsigned int status_flags;
854eccbb 278 git_repository *repo = cl_git_sandbox_init("status");
df743c7d
RB
279
280 for (i = 0; i < (int)entry_count0; i++) {
281 cl_git_pass(
854eccbb 282 git_status_file(&status_flags, repo, entry_paths0[i])
df743c7d
RB
283 );
284 cl_assert(entry_statuses0[i] == status_flags);
285 }
286}
6a67a812 287
a56aacf4
RB
288/* this test is equivalent to t18-status.c:singlestatus1 */
289void 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
98c4613e
RB
300/* this test is equivalent to t18-status.c:singlestatus2 */
301void 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 */
313void 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 */
325void 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);
722c08af 333 cl_assert(error != GIT_ENOTFOUND);
98c4613e
RB
334}
335
a56aacf4 336
6a67a812
RB
337void test_status_worktree__ignores(void)
338{
339 int i, ignored;
854eccbb 340 git_repository *repo = cl_git_sandbox_init("status");
6a67a812
RB
341
342 for (i = 0; i < (int)entry_count0; i++) {
854eccbb 343 cl_git_pass(
dc13f1f7 344 git_status_should_ignore(&ignored, repo, entry_paths0[i])
854eccbb 345 );
e1bcc191 346 cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
6a67a812
RB
347 }
348
854eccbb 349 cl_git_pass(
dc13f1f7 350 git_status_should_ignore(&ignored, repo, "nonexistent_file")
854eccbb 351 );
6a67a812
RB
352 cl_assert(!ignored);
353
854eccbb 354 cl_git_pass(
dc13f1f7 355 git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file")
854eccbb 356 );
6a67a812
RB
357 cl_assert(ignored);
358}
95340398
RB
359
360static int cb_status__check_592(const char *p, unsigned int s, void *payload)
361{
e26b14c0
RB
362 if (s != GIT_STATUS_WT_DELETED ||
363 (payload != NULL && strcmp(p, (const char *)payload) != 0))
95340398
RB
364 return -1;
365
366 return 0;
367}
368
369void 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)));
169dc616 377 cl_assert(!git_path_exists("issue_592/l.txt"));
95340398
RB
378
379 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
380
ac3d33df 381 git_buf_dispose(&path);
95340398
RB
382}
383
384void 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)));
169dc616 392 cl_assert(!git_path_exists("issue_592/c/a.txt"));
95340398
RB
393
394 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
395
ac3d33df 396 git_buf_dispose(&path);
95340398
RB
397}
398
399void 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"));
331e7de9 407 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
169dc616 408 cl_assert(!git_path_exists("issue_592/c/a.txt"));
95340398
RB
409
410 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
411
ac3d33df 412 git_buf_dispose(&path);
95340398
RB
413}
414
415void 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
ac3d33df 427 git_buf_dispose(&path);
95340398
RB
428}
429
430void 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"));
331e7de9 438 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
95340398
RB
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
ac3d33df 443 git_buf_dispose(&path);
95340398 444}
722c08af 445
bd4ca902
RB
446void 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
491void 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
54254a0f 505void test_status_worktree__conflict_with_diff3(void)
151446ca 506{
54254a0f 507 git_repository *repo = cl_git_sandbox_init("status");
151446ca 508 git_index *index;
54254a0f
VM
509 unsigned int status;
510 git_index_entry ancestor_entry, our_entry, their_entry;
151446ca 511
54254a0f
VM
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));
151446ca 515
54254a0f 516 ancestor_entry.path = "modified_file";
d67f270e 517 ancestor_entry.mode = 0100644;
d541170c 518 git_oid_fromstr(&ancestor_entry.id,
54254a0f 519 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
151446ca 520
54254a0f 521 our_entry.path = "modified_file";
d67f270e 522 our_entry.mode = 0100644;
d541170c 523 git_oid_fromstr(&our_entry.id,
54254a0f 524 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
151446ca 525
54254a0f 526 their_entry.path = "modified_file";
d67f270e 527 their_entry.mode = 0100644;
d541170c 528 git_oid_fromstr(&their_entry.id,
54254a0f 529 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
151446ca 530
54254a0f
VM
531 cl_git_pass(git_status_file(&status, repo, "modified_file"));
532 cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
151446ca
AR
533
534 cl_git_pass(git_repository_index(&index, repo));
54254a0f 535 cl_git_pass(git_index_remove(index, "modified_file", 0));
4bf630b6
RB
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);
2a99df69 540
54254a0f 541 cl_git_pass(git_status_file(&status, repo, "modified_file"));
2a99df69 542
7c948014 543 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status);
2a99df69 544}
0abd7244
RB
545
546static 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
557static 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
e9ca852e 568static const int filemode_count = 8;
0abd7244
RB
569
570void test_status_worktree__filemode_changes(void)
571{
572 git_repository *repo = cl_git_sandbox_init("filemodes");
573 status_entry_counts counts;
79cfa20d 574 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
0abd7244
RB
575
576 /* overwrite stored filemode with platform appropriate value */
0abd7244 577 if (cl_is_chmod_supported())
1323c6d1 578 cl_repo_set_bool(repo, "core.filemode", true);
0abd7244 579 else {
e9ca852e 580 int i;
1323c6d1
RB
581
582 cl_repo_set_bool(repo, "core.filemode", false);
0abd7244
RB
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
0abd7244
RB
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);
0abd7244 606}
a1773f9d 607
5dca2010
RB
608static 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
25e0b157 617 return (*count == 8) ? -111 : 0;
5dca2010
RB
618}
619
620void 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(
25e0b157 626 -111, git_status_foreach(repo, cb_status__interrupt, &count)
5dca2010
RB
627 );
628
629 cl_assert_equal_i(8, count);
630}
52462e1c 631
f8e2cc9a
RB
632void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void)
633{
634 git_repository *repo = cl_git_sandbox_init("status");
f8e2cc9a
RB
635 unsigned int status;
636
1323c6d1 637 cl_repo_set_bool(repo, "core.autocrlf", true);
f8e2cc9a
RB
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
8ef4e11a
RB
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);
f8e2cc9a 648}
b2414661 649
b8acb775
SS
650void 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");
b8acb775
SS
653 unsigned int status;
654
1098cfae 655 cl_repo_set_bool(repo, "core.autocrlf", true);
b8acb775
SS
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
b2414661
ET
662void 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
d67f270e 673 ancestor_entry.mode = 0100644;
b2414661 674 ancestor_entry.path = "modified_file";
d541170c 675 git_oid_fromstr(&ancestor_entry.id,
b2414661
ET
676 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
677
d67f270e 678 our_entry.mode = 0100644;
b2414661 679 our_entry.path = "modified_file";
d541170c 680 git_oid_fromstr(&our_entry.id,
b2414661
ET
681 "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
682
d67f270e 683 their_entry.mode = 0100644;
b2414661 684 their_entry.path = "modified_file";
d541170c 685 git_oid_fromstr(&their_entry.id,
b2414661
ET
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"));
7c948014 696 cl_assert_equal_i(GIT_STATUS_CONFLICTED, status);
b2414661
ET
697
698 git_index_free(index);
699}
700
191e97a0
ET
701void test_status_worktree__conflict_has_no_oid(void)
702{
703 git_repository *repo = cl_git_sandbox_init("status");
704 git_index *index;
9f3c18e2 705 git_index_entry entry = {{0}};
191e97a0
ET
706 git_status_list *statuslist;
707 const git_status_entry *status;
9f3c18e2 708 git_oid zero_id = {{0}};
191e97a0
ET
709
710 entry.mode = 0100644;
711 entry.path = "modified_file";
8a5a2e2f 712 git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
191e97a0
ET
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
bcbb1e20 747static void assert_ignore_case(
748 bool should_ignore_case,
749 int expected_lower_cased_file_status,
750 int expected_camel_cased_file_status)
751{
bcbb1e20 752 unsigned int status;
4b181037 753 git_buf lower_case_path = GIT_BUF_INIT, camel_case_path = GIT_BUF_INIT;
bcbb1e20 754 git_repository *repo, *repo2;
4b181037 755
bcbb1e20 756 repo = cl_git_sandbox_init("empty_standard_repo");
757 cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
758
1323c6d1 759 cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case);
bcbb1e20 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
bcbb1e20 770 cl_git_pass(git_status_file(&status, repo2, "plop"));
771 cl_assert_equal_i(GIT_STATUS_CURRENT, status);
772
4b181037
RB
773 cl_git_pass(git_buf_joinpath(&camel_case_path,
774 git_repository_workdir(repo), "Plop"));
775
bcbb1e20 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);
ac3d33df
JK
785 git_buf_dispose(&lower_case_path);
786 git_buf_dispose(&camel_case_path);
bcbb1e20 787}
788
4b181037 789void test_status_worktree__file_status_honors_core_ignorecase_true(void)
bcbb1e20 790{
791 assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT);
792}
793
4b181037 794void test_status_worktree__file_status_honors_core_ignorecase_false(void)
bcbb1e20 795{
796 assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW);
797}
1f9e41ee
RB
798
799void 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}
351888cf
RB
821
822void 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
846void 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}
22b6b82f
RB
869
870static const char *icase_paths[] = { "B", "c", "g", "H" };
871static unsigned int icase_statuses[] = {
872 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
873 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
874};
875
876static const char *case_paths[] = { "B", "H", "c", "g" };
877static unsigned int case_statuses[] = {
878 GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
879 GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED,
880};
881
882void 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 =
ac3d33df 892 (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0;
22b6b82f
RB
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}
8c8a5490
BS
949
950void test_status_worktree__long_filenames(void)
951{
2984f319 952 char path[260*4+1];
8c8a5490
BS
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
ac3d33df 960 /* Create directory with amazingly long filename */
8c8a5490 961 sprintf(path, "empty_standard_repo/%s", longname);
ac2fba0e 962 cl_git_pass(git_futils_mkdir_r(path, 0777));
8c8a5490
BS
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
cd424ad5
RB
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
9c8ed499
RB
986static 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
cd424ad5
RB
996void test_status_worktree__update_stat_cache_0(void)
997{
998 git_repository *repo = cl_git_sandbox_init("status");
9c8ed499
RB
999 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
1000 git_status_list *status;
1001 git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
e44abe16 1002 git_index *index;
cd424ad5 1003
9c8ed499 1004 opts.flags = GIT_STATUS_OPT_DEFAULTS;
cd424ad5 1005
9c8ed499
RB
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);
cd424ad5 1011
9c8ed499 1012 git_status_list_free(status);
cd424ad5 1013
e44abe16
CMN
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
9c8ed499
RB
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);
cd424ad5 1025
9c8ed499 1026 git_status_list_free(status);
cd424ad5 1027
9c8ed499 1028 opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX;
cd424ad5 1029
ff475375
CMN
1030 /* tick again as the index updating from the previous diff might have reset the timestamp */
1031 tick_index(index);
9c8ed499
RB
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);
cd424ad5 1037
9c8ed499 1038 git_status_list_free(status);
cd424ad5 1039}
2b5a99d8 1040
79d5b5c9 1041void test_status_worktree__unreadable(void)
2b5a99d8 1042{
c8402334 1043#ifndef GIT_WIN32
523553f9 1044 const char *expected_paths[] = { "no_permission/foo" };
9532edc0 1045 const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE};
2b5a99d8
AR
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
bbd65ad2
ET
1051 if (geteuid() == 0)
1052 cl_skip();
1053
9532edc0 1054 /* Create directory with no read permission */
ac2fba0e 1055 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
8d3a2d5f
AR
1056 cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy");
1057 p_chmod("empty_standard_repo/no_permission", 0644);
2b5a99d8 1058
2b5a99d8
AR
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;
523553f9 1064 opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE;
2b5a99d8
AR
1065
1066 cl_git_pass(
1067 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
dc4906f1 1068
9532edc0 1069 /* Restore permissions so we can cleanup :) */
dc4906f1
AR
1070 p_chmod("empty_standard_repo/no_permission", 0777);
1071
2b5a99d8
AR
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);
eae0bfdc
PP
1075#else
1076 cl_skip();
c8402334 1077#endif
2b5a99d8 1078}
79d5b5c9 1079
a576a342
AR
1080void test_status_worktree__unreadable_not_included(void)
1081{
c8402334 1082#ifndef GIT_WIN32
a576a342
AR
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 */
ac2fba0e 1091 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
a576a342
AR
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);
eae0bfdc
PP
1111#else
1112 cl_skip();
c8402334 1113#endif
a576a342
AR
1114}
1115
79d5b5c9
AR
1116void 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 */
ac2fba0e 1126 cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777));
79d5b5c9
AR
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
fc656802
ET
1150void 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));
ac3d33df 1166 cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT));
fc656802
ET
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
ae86aa5a
MS
1198static const char *testrepo2_subdir_paths[] = {
1199 "subdir/README",
1200 "subdir/new.txt",
1201 "subdir/subdir2/README",
1202 "subdir/subdir2/new.txt",
1203};
1204
1205static const char *testrepo2_subdir_paths_icase[] = {
1206 "subdir/new.txt",
1207 "subdir/README",
1208 "subdir/subdir2/new.txt",
1209 "subdir/subdir2/README"
1210};
1211
1212void 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;
17442b28 1221 char *subdir_path = "subdir";
ae86aa5a
MS
1222
1223 cl_git_pass(git_repository_index(&index, repo));
1224 native_ignore_case =
ac3d33df 1225 (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0;
ae86aa5a
MS
1226 git_index_free(index);
1227
17442b28 1228 opts.pathspec.strings = &subdir_path;
ae86aa5a 1229 opts.pathspec.count = 1;
ae86aa5a
MS
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
17442b28
ET
1250 git_status_list_free(statuslist);
1251
ae86aa5a
MS
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
17442b28
ET
1267 git_status_list_free(statuslist);
1268
ae86aa5a
MS
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 }
17442b28
ET
1283
1284 git_status_list_free(statuslist);
ae86aa5a 1285}
b6204260 1286
eae0bfdc
PP
1287void 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}