]> git.proxmox.com Git - libgit2.git/blob - tests-clar/status/worktree.c
Test case to reproduce issue #690.
[libgit2.git] / tests-clar / status / worktree.c
1 #include "clar_libgit2.h"
2 #include "fileops.h"
3 #include "ignore.h"
4 #include "status_data.h"
5 #include "posix.h"
6 #include "util.h"
7 #include "path.h"
8
9 /**
10 * Initializer
11 *
12 * Not all of the tests in this file use the same fixtures, so we allow each
13 * test to load their fixture at the top of the test function.
14 */
15 void test_status_worktree__initialize(void)
16 {
17 }
18
19 /**
20 * Cleanup
21 *
22 * This will be called once after each test finishes, even
23 * if the test failed
24 */
25 void test_status_worktree__cleanup(void)
26 {
27 cl_git_sandbox_cleanup();
28 }
29
30 /**
31 * Tests - Status determination on a working tree
32 */
33 /* this test is equivalent to t18-status.c:statuscb0 */
34 void test_status_worktree__whole_repository(void)
35 {
36 status_entry_counts counts;
37 git_repository *repo = cl_git_sandbox_init("status");
38
39 memset(&counts, 0x0, sizeof(status_entry_counts));
40 counts.expected_entry_count = entry_count0;
41 counts.expected_paths = entry_paths0;
42 counts.expected_statuses = entry_statuses0;
43
44 cl_git_pass(
45 git_status_foreach(repo, cb_status__normal, &counts)
46 );
47
48 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
49 cl_assert_equal_i(0, counts.wrong_status_flags_count);
50 cl_assert_equal_i(0, counts.wrong_sorted_path);
51 }
52
53 /* this test is equivalent to t18-status.c:statuscb1 */
54 void test_status_worktree__empty_repository(void)
55 {
56 int count = 0;
57 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
58
59 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
60
61 cl_assert_equal_i(0, count);
62 }
63
64 static int remove_file_cb(void *data, git_buf *file)
65 {
66 const char *filename = git_buf_cstr(file);
67
68 GIT_UNUSED(data);
69
70 if (git__suffixcmp(filename, ".git") == 0)
71 return 0;
72
73 if (git_path_isdir(filename))
74 cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS));
75 else
76 cl_git_pass(p_unlink(git_buf_cstr(file)));
77
78 return 0;
79 }
80
81 /* this test is equivalent to t18-status.c:statuscb2 */
82 void test_status_worktree__purged_worktree(void)
83 {
84 status_entry_counts counts;
85 git_repository *repo = cl_git_sandbox_init("status");
86 git_buf workdir = GIT_BUF_INIT;
87
88 /* first purge the contents of the worktree */
89 cl_git_pass(git_buf_sets(&workdir, git_repository_workdir(repo)));
90 cl_git_pass(git_path_direach(&workdir, remove_file_cb, NULL));
91 git_buf_free(&workdir);
92
93 /* now get status */
94 memset(&counts, 0x0, sizeof(status_entry_counts));
95 counts.expected_entry_count = entry_count2;
96 counts.expected_paths = entry_paths2;
97 counts.expected_statuses = entry_statuses2;
98
99 cl_git_pass(
100 git_status_foreach(repo, cb_status__normal, &counts)
101 );
102
103 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
104 cl_assert_equal_i(0, counts.wrong_status_flags_count);
105 cl_assert_equal_i(0, counts.wrong_sorted_path);
106 }
107
108 /* this test is similar to t18-status.c:statuscb3 */
109 void test_status_worktree__swap_subdir_and_file(void)
110 {
111 status_entry_counts counts;
112 git_repository *repo = cl_git_sandbox_init("status");
113 git_status_options opts;
114
115 /* first alter the contents of the worktree */
116 cl_git_pass(p_rename("status/current_file", "status/swap"));
117 cl_git_pass(p_rename("status/subdir", "status/current_file"));
118 cl_git_pass(p_rename("status/swap", "status/subdir"));
119
120 cl_git_mkfile("status/.HEADER", "dummy");
121 cl_git_mkfile("status/42-is-not-prime.sigh", "dummy");
122 cl_git_mkfile("status/README.md", "dummy");
123
124 /* now get status */
125 memset(&counts, 0x0, sizeof(status_entry_counts));
126 counts.expected_entry_count = entry_count3;
127 counts.expected_paths = entry_paths3;
128 counts.expected_statuses = entry_statuses3;
129
130 memset(&opts, 0, sizeof(opts));
131 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
132 GIT_STATUS_OPT_INCLUDE_IGNORED;
133
134 cl_git_pass(
135 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
136 );
137
138 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
139 cl_assert_equal_i(0, counts.wrong_status_flags_count);
140 cl_assert_equal_i(0, counts.wrong_sorted_path);
141 }
142
143 void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
144 {
145 status_entry_counts counts;
146 git_repository *repo = cl_git_sandbox_init("status");
147 git_status_options opts;
148
149 /* first alter the contents of the worktree */
150 cl_git_pass(p_rename("status/current_file", "status/swap"));
151 cl_git_pass(p_rename("status/subdir", "status/current_file"));
152 cl_git_pass(p_rename("status/swap", "status/subdir"));
153 cl_git_mkfile("status/.new_file", "dummy");
154 cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777));
155 cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
156 cl_git_mkfile("status/zzz_new_file", "dummy");
157
158 /* now get status */
159 memset(&counts, 0x0, sizeof(status_entry_counts));
160 counts.expected_entry_count = entry_count4;
161 counts.expected_paths = entry_paths4;
162 counts.expected_statuses = entry_statuses4;
163
164 memset(&opts, 0, sizeof(opts));
165 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
166 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
167 /* TODO: set pathspec to "current_file" eventually */
168
169 cl_git_pass(
170 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
171 );
172
173 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
174 cl_assert_equal_i(0, counts.wrong_status_flags_count);
175 cl_assert_equal_i(0, counts.wrong_sorted_path);
176 }
177
178 /* this test is equivalent to t18-status.c:singlestatus0 */
179 void test_status_worktree__single_file(void)
180 {
181 int i;
182 unsigned int status_flags;
183 git_repository *repo = cl_git_sandbox_init("status");
184
185 for (i = 0; i < (int)entry_count0; i++) {
186 cl_git_pass(
187 git_status_file(&status_flags, repo, entry_paths0[i])
188 );
189 cl_assert(entry_statuses0[i] == status_flags);
190 }
191 }
192
193 /* this test is equivalent to t18-status.c:singlestatus1 */
194 void test_status_worktree__single_nonexistent_file(void)
195 {
196 int error;
197 unsigned int status_flags;
198 git_repository *repo = cl_git_sandbox_init("status");
199
200 error = git_status_file(&status_flags, repo, "nonexistent");
201 cl_git_fail(error);
202 cl_assert(error == GIT_ENOTFOUND);
203 }
204
205 /* this test is equivalent to t18-status.c:singlestatus2 */
206 void test_status_worktree__single_nonexistent_file_empty_repo(void)
207 {
208 int error;
209 unsigned int status_flags;
210 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
211
212 error = git_status_file(&status_flags, repo, "nonexistent");
213 cl_git_fail(error);
214 cl_assert(error == GIT_ENOTFOUND);
215 }
216
217 /* this test is equivalent to t18-status.c:singlestatus3 */
218 void test_status_worktree__single_file_empty_repo(void)
219 {
220 unsigned int status_flags;
221 git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
222
223 cl_git_mkfile("empty_standard_repo/new_file", "new_file\n");
224
225 cl_git_pass(git_status_file(&status_flags, repo, "new_file"));
226 cl_assert(status_flags == GIT_STATUS_WT_NEW);
227 }
228
229 /* this test is equivalent to t18-status.c:singlestatus4 */
230 void test_status_worktree__single_folder(void)
231 {
232 int error;
233 unsigned int status_flags;
234 git_repository *repo = cl_git_sandbox_init("status");
235
236 error = git_status_file(&status_flags, repo, "subdir");
237 cl_git_fail(error);
238 cl_assert(error != GIT_ENOTFOUND);
239 }
240
241
242 void test_status_worktree__ignores(void)
243 {
244 int i, ignored;
245 git_repository *repo = cl_git_sandbox_init("status");
246
247 for (i = 0; i < (int)entry_count0; i++) {
248 cl_git_pass(
249 git_status_should_ignore(&ignored, repo, entry_paths0[i])
250 );
251 cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
252 }
253
254 cl_git_pass(
255 git_status_should_ignore(&ignored, repo, "nonexistent_file")
256 );
257 cl_assert(!ignored);
258
259 cl_git_pass(
260 git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file")
261 );
262 cl_assert(ignored);
263 }
264
265 static int cb_status__check_592(const char *p, unsigned int s, void *payload)
266 {
267 GIT_UNUSED(payload);
268
269 if (s != GIT_STATUS_WT_DELETED || (payload != NULL && strcmp(p, (const char *)payload) != 0))
270 return -1;
271
272 return 0;
273 }
274
275 void test_status_worktree__issue_592(void)
276 {
277 git_repository *repo;
278 git_buf path = GIT_BUF_INIT;
279
280 repo = cl_git_sandbox_init("issue_592");
281 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "l.txt"));
282 cl_git_pass(p_unlink(git_buf_cstr(&path)));
283
284 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
285
286 git_buf_free(&path);
287 }
288
289 void test_status_worktree__issue_592_2(void)
290 {
291 git_repository *repo;
292 git_buf path = GIT_BUF_INIT;
293
294 repo = cl_git_sandbox_init("issue_592");
295 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c/a.txt"));
296 cl_git_pass(p_unlink(git_buf_cstr(&path)));
297
298 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
299
300 git_buf_free(&path);
301 }
302
303 void test_status_worktree__issue_592_3(void)
304 {
305 git_repository *repo;
306 git_buf path = GIT_BUF_INIT;
307
308 repo = cl_git_sandbox_init("issue_592");
309
310 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
311 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
312
313 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
314
315 git_buf_free(&path);
316 }
317
318 void test_status_worktree__issue_592_4(void)
319 {
320 git_repository *repo;
321 git_buf path = GIT_BUF_INIT;
322
323 repo = cl_git_sandbox_init("issue_592");
324
325 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t/b.txt"));
326 cl_git_pass(p_unlink(git_buf_cstr(&path)));
327
328 cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt"));
329
330 git_buf_free(&path);
331 }
332
333 void test_status_worktree__issue_592_5(void)
334 {
335 git_repository *repo;
336 git_buf path = GIT_BUF_INIT;
337
338 repo = cl_git_sandbox_init("issue_592");
339
340 cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
341 cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
342 cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
343
344 cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
345
346 git_buf_free(&path);
347 }
348
349 void test_status_worktree__issue_592_ignores_0(void)
350 {
351 int count = 0;
352 status_entry_single st;
353 git_repository *repo = cl_git_sandbox_init("issue_592");
354
355 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
356 cl_assert_equal_i(0, count);
357
358 cl_git_rewritefile("issue_592/.gitignore",
359 ".gitignore\n*.txt\nc/\n[tT]*/\n");
360
361 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
362 cl_assert_equal_i(1, count);
363
364 /* This is a situation where the behavior of libgit2 is
365 * different from core git. Core git will show ignored.txt
366 * in the list of ignored files, even though the directory
367 * "t" is ignored and the file is untracked because we have
368 * the explicit "*.txt" ignore rule. Libgit2 just excludes
369 * all untracked files that are contained within ignored
370 * directories without explicitly listing them.
371 */
372 cl_git_rewritefile("issue_592/t/ignored.txt", "ping");
373
374 memset(&st, 0, sizeof(st));
375 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
376 cl_assert_equal_i(1, st.count);
377 cl_assert(st.status == GIT_STATUS_IGNORED);
378
379 cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping");
380
381 memset(&st, 0, sizeof(st));
382 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
383 cl_assert_equal_i(1, st.count);
384 cl_assert(st.status == GIT_STATUS_IGNORED);
385
386 cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping");
387
388 memset(&st, 0, sizeof(st));
389 cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
390 cl_assert_equal_i(1, st.count);
391 cl_assert(st.status == GIT_STATUS_IGNORED);
392 }
393
394 void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void)
395 {
396 int count = 0;
397 git_repository *repo = cl_git_sandbox_init("issue_592b");
398
399 cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
400 cl_assert_equal_i(1, count);
401
402 /* if we are really mimicking core git, then only ignored1.txt
403 * at the top level will show up in the ignores list here.
404 * everything else will be unmodified or skipped completely.
405 */
406 }
407
408 void test_status_worktree__cannot_retrieve_the_status_of_a_bare_repository(void)
409 {
410 git_repository *repo;
411 int error;
412 unsigned int status = 0;
413
414 cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
415
416 error = git_status_file(&status, repo, "dummy");
417
418 cl_git_fail(error);
419 cl_assert(error != GIT_ENOTFOUND);
420
421 git_repository_free(repo);
422 }
423
424 void test_status_worktree__first_commit_in_progress(void)
425 {
426 git_repository *repo;
427 git_index *index;
428 status_entry_single result;
429
430 cl_git_pass(git_repository_init(&repo, "getting_started", 0));
431 cl_git_mkfile("getting_started/testfile.txt", "content\n");
432
433 memset(&result, 0, sizeof(result));
434 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
435 cl_assert_equal_i(1, result.count);
436 cl_assert(result.status == GIT_STATUS_WT_NEW);
437
438 cl_git_pass(git_repository_index(&index, repo));
439 cl_git_pass(git_index_add(index, "testfile.txt", 0));
440 cl_git_pass(git_index_write(index));
441
442 memset(&result, 0, sizeof(result));
443 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
444 cl_assert_equal_i(1, result.count);
445 cl_assert(result.status == GIT_STATUS_INDEX_NEW);
446
447 git_index_free(index);
448 git_repository_free(repo);
449 }
450
451
452
453 void test_status_worktree__status_file_without_index_or_workdir(void)
454 {
455 git_repository *repo;
456 unsigned int status = 0;
457 git_index *index;
458
459 cl_git_pass(p_mkdir("wd", 0777));
460
461 cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
462 cl_git_pass(git_repository_set_workdir(repo, "wd", false));
463
464 cl_git_pass(git_index_open(&index, "empty-index"));
465 cl_assert_equal_i(0, git_index_entrycount(index));
466 git_repository_set_index(repo, index);
467
468 cl_git_pass(git_status_file(&status, repo, "branch_file.txt"));
469
470 cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status);
471
472 git_repository_free(repo);
473 git_index_free(index);
474 cl_git_pass(p_rmdir("wd"));
475 }
476
477 static void fill_index_wth_head_entries(git_repository *repo, git_index *index)
478 {
479 git_oid oid;
480 git_commit *commit;
481 git_tree *tree;
482
483 cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD"));
484 cl_git_pass(git_commit_lookup(&commit, repo, &oid));
485 cl_git_pass(git_commit_tree(&tree, commit));
486
487 cl_git_pass(git_index_read_tree(index, tree, NULL));
488 cl_git_pass(git_index_write(index));
489
490 git_tree_free(tree);
491 git_commit_free(commit);
492 }
493
494 void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void)
495 {
496 git_repository *repo;
497 unsigned int status = 0;
498 git_index *index;
499
500 cl_git_pass(p_mkdir("wd", 0777));
501
502 cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
503 cl_git_pass(git_repository_set_workdir(repo, "wd", false));
504
505 cl_git_pass(git_index_open(&index, "my-index"));
506 fill_index_wth_head_entries(repo, index);
507
508 git_repository_set_index(repo, index);
509
510 cl_git_pass(git_status_file(&status, repo, "branch_file.txt"));
511
512 cl_assert_equal_i(GIT_STATUS_WT_DELETED, status);
513
514 git_repository_free(repo);
515 git_index_free(index);
516 cl_git_pass(p_rmdir("wd"));
517 cl_git_pass(p_unlink("my-index"));
518 }
519
520 void test_status_worktree__bracket_in_filename(void)
521 {
522 git_repository *repo;
523 git_index *index;
524 status_entry_single result;
525 unsigned int status_flags;
526 int error;
527
528 #define FILE_WITH_BRACKET "LICENSE[1].md"
529 #define FILE_WITHOUT_BRACKET "LICENSE1.md"
530
531 cl_git_pass(git_repository_init(&repo, "with_bracket", 0));
532 cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n");
533
534 /* file is new to working directory */
535
536 memset(&result, 0, sizeof(result));
537 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
538 cl_assert_equal_i(1, result.count);
539 cl_assert(result.status == GIT_STATUS_WT_NEW);
540
541 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
542 cl_assert(status_flags == GIT_STATUS_WT_NEW);
543
544 /* ignore the file */
545
546 cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n");
547
548 memset(&result, 0, sizeof(result));
549 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
550 cl_assert_equal_i(2, result.count);
551 cl_assert(result.status == GIT_STATUS_IGNORED);
552
553 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
554 cl_assert(status_flags == GIT_STATUS_IGNORED);
555
556 /* don't ignore the file */
557
558 cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n");
559
560 memset(&result, 0, sizeof(result));
561 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
562 cl_assert_equal_i(2, result.count);
563 cl_assert(result.status == GIT_STATUS_WT_NEW);
564
565 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
566 cl_assert(status_flags == GIT_STATUS_WT_NEW);
567
568 /* add the file to the index */
569
570 cl_git_pass(git_repository_index(&index, repo));
571 cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0));
572 cl_git_pass(git_index_write(index));
573
574 memset(&result, 0, sizeof(result));
575 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
576 cl_assert_equal_i(2, result.count);
577 cl_assert(result.status == GIT_STATUS_INDEX_NEW);
578
579 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
580 cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
581
582 /* Create file without bracket */
583
584 cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n");
585
586 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET));
587 cl_assert(status_flags == GIT_STATUS_WT_NEW);
588
589 cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"));
590 cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
591
592 error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET);
593 cl_git_fail(error);
594 cl_assert_equal_i(GIT_EAMBIGUOUS, error);
595
596 git_index_free(index);
597 git_repository_free(repo);
598 }
599
600 void test_status_worktree__space_in_filename(void)
601 {
602 git_repository *repo;
603 git_index *index;
604 status_entry_single result;
605 unsigned int status_flags;
606
607 #define FILE_WITH_SPACE "LICENSE - copy.md"
608
609 cl_git_pass(git_repository_init(&repo, "with_space", 0));
610 cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n");
611
612 /* file is new to working directory */
613
614 memset(&result, 0, sizeof(result));
615 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
616 cl_assert_equal_i(1, result.count);
617 cl_assert(result.status == GIT_STATUS_WT_NEW);
618
619 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
620 cl_assert(status_flags == GIT_STATUS_WT_NEW);
621
622 /* ignore the file */
623
624 cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n");
625
626 memset(&result, 0, sizeof(result));
627 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
628 cl_assert_equal_i(2, result.count);
629 cl_assert(result.status == GIT_STATUS_IGNORED);
630
631 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
632 cl_assert(status_flags == GIT_STATUS_IGNORED);
633
634 /* don't ignore the file */
635
636 cl_git_rewritefile("with_space/.gitignore", ".gitignore\n");
637
638 memset(&result, 0, sizeof(result));
639 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
640 cl_assert_equal_i(2, result.count);
641 cl_assert(result.status == GIT_STATUS_WT_NEW);
642
643 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
644 cl_assert(status_flags == GIT_STATUS_WT_NEW);
645
646 /* add the file to the index */
647
648 cl_git_pass(git_repository_index(&index, repo));
649 cl_git_pass(git_index_add(index, FILE_WITH_SPACE, 0));
650 cl_git_pass(git_index_write(index));
651
652 memset(&result, 0, sizeof(result));
653 cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
654 cl_assert_equal_i(2, result.count);
655 cl_assert(result.status == GIT_STATUS_INDEX_NEW);
656
657 cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
658 cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
659
660 git_index_free(index);
661 git_repository_free(repo);
662 }
663
664 static const char *filemode_paths[] = {
665 "exec_off",
666 "exec_off2on_staged",
667 "exec_off2on_workdir",
668 "exec_off_untracked",
669 "exec_on",
670 "exec_on2off_staged",
671 "exec_on2off_workdir",
672 "exec_on_untracked",
673 };
674
675 static unsigned int filemode_statuses[] = {
676 GIT_STATUS_CURRENT,
677 GIT_STATUS_INDEX_MODIFIED,
678 GIT_STATUS_WT_MODIFIED,
679 GIT_STATUS_WT_NEW,
680 GIT_STATUS_CURRENT,
681 GIT_STATUS_INDEX_MODIFIED,
682 GIT_STATUS_WT_MODIFIED,
683 GIT_STATUS_WT_NEW
684 };
685
686 static const int filemode_count = 8;
687
688 void test_status_worktree__filemode_changes(void)
689 {
690 git_repository *repo = cl_git_sandbox_init("filemodes");
691 status_entry_counts counts;
692 git_status_options opts;
693 git_config *cfg;
694
695 /* overwrite stored filemode with platform appropriate value */
696 cl_git_pass(git_repository_config(&cfg, repo));
697 if (cl_is_chmod_supported())
698 cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
699 else {
700 int i;
701 cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
702
703 /* won't trust filesystem mode diffs, so these will appear unchanged */
704 for (i = 0; i < filemode_count; ++i)
705 if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
706 filemode_statuses[i] = GIT_STATUS_CURRENT;
707 }
708
709 memset(&opts, 0, sizeof(opts));
710 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
711 GIT_STATUS_OPT_INCLUDE_IGNORED |
712 GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
713
714 memset(&counts, 0, sizeof(counts));
715 counts.expected_entry_count = filemode_count;
716 counts.expected_paths = filemode_paths;
717 counts.expected_statuses = filemode_statuses;
718
719 cl_git_pass(
720 git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
721 );
722
723 cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
724 cl_assert_equal_i(0, counts.wrong_status_flags_count);
725 cl_assert_equal_i(0, counts.wrong_sorted_path);
726
727 git_config_free(cfg);
728 }
729
730 static int cb_status__expected_path(const char *p, unsigned int s, void *payload)
731 {
732 const char *expected_path = (const char *)payload;
733
734 GIT_UNUSED(s);
735
736 if (payload == NULL)
737 cl_fail("Unexpected path");
738
739 cl_assert_equal_s(expected_path, p);
740
741 return 0;
742 }
743
744 void test_status_worktree__disable_pathspec_match(void)
745 {
746 git_repository *repo;
747 git_status_options opts;
748 char *file_with_bracket = "LICENSE[1].md",
749 *imaginary_file_with_bracket = "LICENSE[1-2].md";
750
751 cl_git_pass(git_repository_init(&repo, "pathspec", 0));
752 cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n");
753 cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n");
754
755 memset(&opts, 0, sizeof(opts));
756 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
757 GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
758 opts.pathspec.count = 1;
759 opts.pathspec.strings = &file_with_bracket;
760
761 cl_git_pass(
762 git_status_foreach_ext(repo, &opts, cb_status__expected_path,
763 file_with_bracket)
764 );
765
766 /* Test passing a pathspec matching files in the workdir. */
767 /* Must not match because pathspecs are disabled. */
768 opts.pathspec.strings = &imaginary_file_with_bracket;
769 cl_git_pass(
770 git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL)
771 );
772
773 git_repository_free(repo);
774 }
775
776
777 static int cb_status__interrupt(const char *p, unsigned int s, void *payload)
778 {
779 volatile int *count = (int *)payload;
780
781 GIT_UNUSED(p);
782 GIT_UNUSED(s);
783
784 (*count)++;
785
786 return (*count == 8);
787 }
788
789 void test_status_worktree__interruptable_foreach(void)
790 {
791 int count = 0;
792 git_repository *repo = cl_git_sandbox_init("status");
793
794 cl_assert_equal_i(
795 GIT_EUSER, git_status_foreach(repo, cb_status__interrupt, &count)
796 );
797
798 cl_assert_equal_i(8, count);
799 }
800
801 void test_status_worktree__new_staged_file_must_handle_crlf(void)
802 {
803 git_repository *repo;
804 git_index *index;
805 git_config *config;
806 unsigned int status;
807
808 cl_git_pass(git_repository_init(&repo, "getting_started", 0));
809
810 // Ensure that repo has core.autocrlf=true
811 cl_git_pass(git_repository_config(&config, repo));
812 cl_git_pass(git_config_set_bool(config, "core.autocrlf", true));
813
814 cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF
815
816 cl_git_pass(git_repository_index(&index, repo));
817 cl_git_pass(git_index_add(index, "testfile.txt", 0));
818 cl_git_pass(git_index_write(index));
819
820 cl_git_pass(git_status_file(&status, repo, "testfile.txt"));
821 cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status);
822
823 git_config_free(config);
824 git_index_free(index);
825 git_repository_free(repo);
826 }