diff --git a/examples/status.c b/examples/status.c index 2378c78b6..3c82640b2 100644 --- a/examples/status.c +++ b/examples/status.c @@ -203,7 +203,8 @@ int main(int argc, char *argv[]) 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_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; for (i = 1; i < argc; ++i) { if (argv[i][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 3846a5e1b..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; @@ -1165,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); } } @@ -1266,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); } } @@ -1301,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/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); +}