diff --git a/examples/Makefile b/examples/Makefile index c5d555566..140cc4da9 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file +APPS = general showindex diff rev-list cat-file status all: $(APPS) diff --git a/examples/status.c b/examples/status.c new file mode 100644 index 000000000..689098415 --- /dev/null +++ b/examples/status.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include +#include +#include +#include + +enum { + FORMAT_DEFAULT = 0, + FORMAT_LONG = 1, + FORMAT_SHORT = 2, + FORMAT_PORCELAIN = 3, +}; +#define MAX_PATHSPEC 8 + +/* + * This example demonstrates the use of the libgit2 status APIs, + * particularly the `git_status_list` object, to roughly simulate the + * output of running `git status`. It serves as a simple example of + * using those APIs to get basic status information. + * + * This does not have: + * - Robust error handling + * - Colorized or paginated output formatting + * + * This does have: + * - Examples of translating command line arguments to the status + * options settings to mimic `git status` results. + * - A sample status formatter that matches the default "long" format + * from `git status` + * - A sample status formatter that matches the "short" format + */ + +static void check(int error, const char *message, const char *extra) +{ + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) + return; + + if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + exit(1); +} + +static void fail(const char *message) +{ + check(-1, message, NULL); +} + +static void show_branch(git_repository *repo, int format) +{ + int error = 0; + const char *branch = NULL; + git_reference *head = NULL; + + error = git_repository_head(&head, repo); + + if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND) + branch = NULL; + else if (!error) { + branch = git_reference_name(head); + if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) + branch += strlen("refs/heads/"); + } else + check(error, "failed to get current branch", NULL); + + if (format == FORMAT_LONG) + printf("# On branch %s\n", + branch ? branch : "Not currently on any branch."); + else + printf("## %s\n", branch ? branch : "HEAD (no branch)"); + + git_reference_free(head); +} + +static void print_long(git_repository *repo, git_status_list *status) +{ + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + int header = 0, changes_in_index = 0; + int changed_in_workdir = 0, rm_in_workdir = 0; + const char *old_path, *new_path; + + (void)repo; + + /* print index changes */ + + for (i = 0; i < maxi; ++i) { + char *istatus = NULL; + + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + if (s->status & GIT_STATUS_WT_DELETED) + rm_in_workdir = 1; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = "new file: "; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = "modified: "; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = "deleted: "; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = "renamed: "; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = "typechange:"; + + if (istatus == NULL) + continue; + + if (!header) { + printf("# Changes to be committed:\n"); + printf("# (use \"git reset HEAD ...\" to unstage)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->head_to_index->old_file.path; + new_path = s->head_to_index->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", istatus, old_path, new_path); + else + printf("#\t%s %s\n", istatus, old_path ? old_path : new_path); + } + + if (header) { + changes_in_index = 1; + printf("#\n"); + } + header = 0; + + /* print workdir changes to tracked files */ + + for (i = 0; i < maxi; ++i) { + char *wstatus = NULL; + + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL) + continue; + + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = "modified: "; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = "deleted: "; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = "renamed: "; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = "typechange:"; + + if (wstatus == NULL) + continue; + + if (!header) { + printf("# Changes not staged for commit:\n"); + printf("# (use \"git add%s ...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : ""); + printf("# (use \"git checkout -- ...\" to discard changes in working directory)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->index_to_workdir->old_file.path; + new_path = s->index_to_workdir->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", wstatus, old_path, new_path); + else + printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path); + } + + if (header) { + changed_in_workdir = 1; + printf("#\n"); + } + header = 0; + + /* print untracked files */ + + header = 0; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) { + + if (!header) { + printf("# Untracked files:\n"); + printf("# (use \"git add ...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + header = 0; + + /* print ignored files */ + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_IGNORED) { + + if (!header) { + printf("# Ignored files:\n"); + printf("# (use \"git add -f ...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + if (!changes_in_index && changed_in_workdir) + printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); +} + +static void print_short(git_repository *repo, git_status_list *status) +{ + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + char istatus, wstatus; + const char *extra, *a, *b, *c; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + a = b = c = NULL; + istatus = wstatus = ' '; + extra = ""; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = 'A'; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = 'M'; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = 'D'; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = 'R'; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = 'T'; + + if (s->status & GIT_STATUS_WT_NEW) { + if (istatus == ' ') + istatus = '?'; + wstatus = '?'; + } + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = 'M'; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = 'D'; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = 'R'; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = 'T'; + + if (s->status & GIT_STATUS_IGNORED) { + istatus = '!'; + wstatus = '!'; + } + + if (istatus == '?' && wstatus == '?') + continue; + + if (s->index_to_workdir && + s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) + { + git_submodule *sm = NULL; + unsigned int smstatus = 0; + + if (!git_submodule_lookup( + &sm, repo, s->index_to_workdir->new_file.path) && + !git_submodule_status(&smstatus, sm)) + { + if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) + extra = " (new commits)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED) + extra = " (untracked content)"; + } + } + + if (s->head_to_index) { + a = s->head_to_index->old_file.path; + b = s->head_to_index->new_file.path; + } + if (s->index_to_workdir) { + if (!a) + a = s->index_to_workdir->old_file.path; + if (!b) + b = s->index_to_workdir->old_file.path; + c = s->index_to_workdir->new_file.path; + } + + if (istatus == 'R') { + if (wstatus == 'R') + printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra); + else + printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra); + } else { + if (wstatus == 'R') + printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra); + else + printf("%c%c %s%s\n", istatus, wstatus, a, extra); + } + } + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) + printf("?? %s\n", s->index_to_workdir->old_file.path); + } +} + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0; + git_status_options opt = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + char *repodir = ".", *pathspec[MAX_PATHSPEC]; + + opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + if (npaths < MAX_PATHSPEC) + pathspec[npaths++] = argv[i]; + else + fail("Example only supports a limited pathspec"); + } + else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) + format = FORMAT_SHORT; + else if (!strcmp(argv[i], "--long")) + format = FORMAT_LONG; + else if (!strcmp(argv[i], "--porcelain")) + format = FORMAT_PORCELAIN; + else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) + showbranch = 1; + else if (!strcmp(argv[i], "-z")) { + zterm = 1; + if (format == FORMAT_DEFAULT) + format = FORMAT_PORCELAIN; + } + else if (!strcmp(argv[i], "--ignored")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; + else if (!strcmp(argv[i], "-uno") || + !strcmp(argv[i], "--untracked-files=no")) + opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(argv[i], "-unormal") || + !strcmp(argv[i], "--untracked-files=normal")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(argv[i], "-uall") || + !strcmp(argv[i], "--untracked-files=all")) + opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + else if (!strcmp(argv[i], "--ignore-submodules=all")) + opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) + repodir = argv[i] + strlen("--git-dir="); + else + check(-1, "Unsupported option", argv[i]); + } + + if (format == FORMAT_DEFAULT) + format = FORMAT_LONG; + if (format == FORMAT_LONG) + showbranch = 1; + if (npaths > 0) { + opt.pathspec.strings = pathspec; + opt.pathspec.count = npaths; + } + + /* + * Try to open the repository at the given path (or at the current + * directory if none was given). + */ + check(git_repository_open_ext(&repo, repodir, 0, NULL), + "Could not open repository", repodir); + + if (git_repository_is_bare(repo)) + fail("Cannot report status on bare repository"); + + /* + * Run status on the repository + * + * Because we want to simluate a full "git status" run and want to + * support some command line options, we use `git_status_foreach_ext()` + * instead of just the plain status call. This allows (a) iterating + * over the index and then the workdir and (b) extra flags that control + * which files are included. If you just want simple status (e.g. to + * enumerate files that are modified) then you probably don't need the + * extended API. + */ + check(git_status_list_new(&status, repo, &opt), + "Could not get status", NULL); + + if (showbranch) + show_branch(repo, format); + + if (format == FORMAT_LONG) + print_long(repo, status); + else + print_short(repo, status); + + git_status_list_free(status); + git_repository_free(repo); + + return 0; +} + diff --git a/include/git2/status.h b/include/git2/status.h index 282b606cb..63aea2f3b 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -111,6 +111,12 @@ typedef enum { * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename * detection should be run between the index and the working directory * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. + * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case + * sensitivity for the file system and forces the output to be in + * case-sensitive order + * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case + * sensitivity for the file system and forces the output to be in + * case-insensitive order * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -127,6 +133,8 @@ typedef enum { GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/src/attr_file.c b/src/attr_file.c index d059cfec7..d880398e8 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -498,7 +498,7 @@ int git_attr_assignment__parse( assert(assigns && !assigns->length); - assigns->_cmp = sort_by_hash_and_name; + git_vector_set_cmp(assigns, sort_by_hash_and_name); while (*scan && *scan != '\n') { const char *name_start, *value_start; diff --git a/src/diff.c b/src/diff.c index 633601699..26e117402 100644 --- a/src/diff.c +++ b/src/diff.c @@ -365,7 +365,7 @@ static git_diff_list *diff_list_alloc( diff->pfxcomp = git__prefixcmp_icase; diff->entrycomp = git_index_entry__cmp_icase; - diff->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); } return diff; @@ -786,10 +786,15 @@ static int diff_scan_inside_untracked_dir( /* need to recurse into non-ignored directories */ if (!is_ignored && S_ISDIR(info->nitem->mode)) { - if ((error = git_iterator_advance_into( - &info->nitem, info->new_iter)) < 0) - break; - continue; + error = git_iterator_advance_into(&info->nitem, info->new_iter); + + if (!error) + continue; + else if (error == GIT_ENOTFOUND) { + error = 0; + is_ignored = true; /* treat empty as ignored */ + } else + break; /* real error, must stop */ } /* found a non-ignored item - treat parent dir as untracked */ @@ -1160,7 +1165,7 @@ int git_diff_tree_to_index( d->pfxcomp = git__prefixcmp_icase; d->entrycomp = git_index_entry__cmp_icase; - d->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&d->deltas, git_diff_delta__casecmp); git_vector_sort(&d->deltas); } } @@ -1261,10 +1266,10 @@ int git_diff__paired_foreach( /* force case-sensitive delta sort */ if (icase_mismatch) { if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - head2idx->deltas._cmp = git_diff_delta__cmp; + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); git_vector_sort(&head2idx->deltas); } else { - idx2wd->deltas._cmp = git_diff_delta__cmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp); git_vector_sort(&idx2wd->deltas); } } @@ -1296,10 +1301,10 @@ int git_diff__paired_foreach( /* restore case-insensitive delta sort */ if (icase_mismatch) { if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - head2idx->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); git_vector_sort(&head2idx->deltas); } else { - idx2wd->deltas._cmp = git_diff_delta__casecmp; + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp); git_vector_sort(&idx2wd->deltas); } } diff --git a/src/index.c b/src/index.c index d5568528b..1d46779bf 100644 --- a/src/index.c +++ b/src/index.c @@ -290,16 +290,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) { index->ignore_case = ignore_case; - index->entries._cmp = ignore_case ? index_icmp : index_cmp; index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path; index->entries_search = ignore_case ? index_isrch : index_srch; index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; - index->entries.sorted = 0; + + git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp); git_vector_sort(&index->entries); - index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp; index->reuc_search = ignore_case ? reuc_isrch : reuc_srch; - index->reuc.sorted = 0; + + git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); git_vector_sort(&index->reuc); } @@ -2024,7 +2024,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) git_vector_sort(&index->entries); - entries._cmp = index->entries._cmp; + git_vector_set_cmp(&entries, index->entries._cmp); git_vector_swap(&entries, &index->entries); git_index_clear(index); diff --git a/src/status.c b/src/status.c index 375100a89..e520c1017 100644 --- a/src/status.c +++ b/src/status.c @@ -335,8 +335,16 @@ int git_status_list_new( status->head2idx, status->idx2wd, status_collect, status)) < 0) goto done; - if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || - (flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_cmp); + if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_icmp); + + if ((flags & + (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) git_vector_sort(&status->paired); done: diff --git a/src/submodule.c b/src/submodule.c index af488b7f3..89eba2aa4 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -151,7 +151,7 @@ int git_submodule_foreach( int error; git_submodule *sm; git_vector seen = GIT_VECTOR_INIT; - seen._cmp = submodule_cmp; + git_vector_set_cmp(&seen, submodule_cmp); assert(repo && callback); diff --git a/src/vector.h b/src/vector.h index e2f729b83..1bda9c93d 100644 --- a/src/vector.h +++ b/src/vector.h @@ -78,4 +78,13 @@ void git_vector_remove_matching( int git_vector_resize_to(git_vector *v, size_t new_length); int git_vector_set(void **old, git_vector *v, size_t position, void *value); +/** Set the comparison function used for sorting the vector */ +GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) +{ + if (cmp != v->_cmp) { + v->_cmp = cmp; + v->sorted = 0; + } +} + #endif diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 18182ea96..6a2504d09 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -1109,6 +1109,26 @@ void test_diff_workdir__untracked_directory_scenarios(void) git_diff_list_free(diff); + /* empty directory in empty directory */ + + cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777)); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_list_free(diff); + /* directory with only ignored files */ cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777)); diff --git a/tests-clar/status/status_helpers.c b/tests-clar/status/status_helpers.c index f073c2491..902b65c4f 100644 --- a/tests-clar/status/status_helpers.c +++ b/tests-clar/status/status_helpers.c @@ -6,6 +6,9 @@ int cb_status__normal( { status_entry_counts *counts = payload; + if (counts->debug) + cb_status__print(path, status_flags, NULL); + if (counts->entry_count >= counts->expected_entry_count) { counts->wrong_status_flags_count++; goto exit; diff --git a/tests-clar/status/status_helpers.h b/tests-clar/status/status_helpers.h index ae1469e79..f1f009e02 100644 --- a/tests-clar/status/status_helpers.h +++ b/tests-clar/status/status_helpers.h @@ -8,6 +8,7 @@ typedef struct { const unsigned int* expected_statuses; const char** expected_paths; int expected_entry_count; + bool debug; } status_entry_counts; /* cb_status__normal takes payload of "status_entry_counts *" */ diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 7c27ee588..920671e13 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -743,3 +743,83 @@ void test_status_worktree__simple_delete_indexed(void) GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); git_status_list_free(status); } + +static const char *icase_paths[] = { "B", "c", "g", "H" }; +static unsigned int icase_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, +}; + +static const char *case_paths[] = { "B", "H", "c", "g" }; +static unsigned int case_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED, +}; + +void test_status_worktree__sorting_by_case(void) +{ + git_repository *repo = cl_git_sandbox_init("icase"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + bool native_ignore_case; + status_entry_counts counts; + + cl_git_pass(git_repository_index(&index, repo)); + native_ignore_case = + (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0; + git_index_free(index); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 0; + counts.expected_paths = NULL; + counts.expected_statuses = NULL; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + cl_git_rewritefile("icase/B", "new stuff"); + cl_must_pass(p_unlink("icase/c")); + cl_git_rewritefile("icase/g", "new stuff"); + cl_must_pass(p_unlink("icase/H")); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + if (native_ignore_case) { + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + } else { + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + } + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +}