Commit 22b6b82f by Russell Belfer

Add status flags to force output sort order

Files in status will, be default, be sorted according to the case
insensitivity of the filesystem that we're running on.  However,
in some cases, this is not desirable.  Even on case insensitive
file systems, 'git status' at the command line will generally use
a case sensitive sort (like 'ls').  Some GUIs prefer to display a
list of file case insensitively even on case-sensitive platforms.

This adds two new flags: GIT_STATUS_OPT_SORT_CASE_SENSITIVELY
and GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY that will override the
default sort order of the status output and give the user control.
This includes tests for exercising these new options and makes
the examples/status.c program emulate core Git and always use a
case sensitive sort.
parent cf300bb9
...@@ -203,7 +203,8 @@ int main(int argc, char *argv[]) ...@@ -203,7 +203,8 @@ int main(int argc, char *argv[])
opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | 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) { for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') { if (argv[i][0] != '-') {
......
...@@ -111,6 +111,12 @@ typedef enum { ...@@ -111,6 +111,12 @@ typedef enum {
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename
* detection should be run between the index and the working directory * detection should be run between the index and the working directory
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * 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 * Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
...@@ -127,6 +133,8 @@ typedef enum { ...@@ -127,6 +133,8 @@ typedef enum {
GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), 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; } git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \ #define GIT_STATUS_OPT_DEFAULTS \
......
...@@ -498,7 +498,7 @@ int git_attr_assignment__parse( ...@@ -498,7 +498,7 @@ int git_attr_assignment__parse(
assert(assigns && !assigns->length); 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') { while (*scan && *scan != '\n') {
const char *name_start, *value_start; const char *name_start, *value_start;
......
...@@ -365,7 +365,7 @@ static git_diff_list *diff_list_alloc( ...@@ -365,7 +365,7 @@ static git_diff_list *diff_list_alloc(
diff->pfxcomp = git__prefixcmp_icase; diff->pfxcomp = git__prefixcmp_icase;
diff->entrycomp = git_index_entry__cmp_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; return diff;
...@@ -1165,7 +1165,7 @@ int git_diff_tree_to_index( ...@@ -1165,7 +1165,7 @@ int git_diff_tree_to_index(
d->pfxcomp = git__prefixcmp_icase; d->pfxcomp = git__prefixcmp_icase;
d->entrycomp = git_index_entry__cmp_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); git_vector_sort(&d->deltas);
} }
} }
...@@ -1266,10 +1266,10 @@ int git_diff__paired_foreach( ...@@ -1266,10 +1266,10 @@ int git_diff__paired_foreach(
/* force case-sensitive delta sort */ /* force case-sensitive delta sort */
if (icase_mismatch) { if (icase_mismatch) {
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { 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); git_vector_sort(&head2idx->deltas);
} else { } else {
idx2wd->deltas._cmp = git_diff_delta__cmp; git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas); git_vector_sort(&idx2wd->deltas);
} }
} }
...@@ -1301,10 +1301,10 @@ int git_diff__paired_foreach( ...@@ -1301,10 +1301,10 @@ int git_diff__paired_foreach(
/* restore case-insensitive delta sort */ /* restore case-insensitive delta sort */
if (icase_mismatch) { if (icase_mismatch) {
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { 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); git_vector_sort(&head2idx->deltas);
} else { } else {
idx2wd->deltas._cmp = git_diff_delta__casecmp; git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp);
git_vector_sort(&idx2wd->deltas); git_vector_sort(&idx2wd->deltas);
} }
} }
......
...@@ -290,16 +290,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) ...@@ -290,16 +290,16 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case)
{ {
index->ignore_case = 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_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
index->entries_search = ignore_case ? index_isrch : index_srch; index->entries_search = ignore_case ? index_isrch : index_srch;
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; 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); 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_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); git_vector_sort(&index->reuc);
} }
...@@ -2024,7 +2024,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) ...@@ -2024,7 +2024,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
git_vector_sort(&index->entries); 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_vector_swap(&entries, &index->entries);
git_index_clear(index); git_index_clear(index);
......
...@@ -335,8 +335,16 @@ int git_status_list_new( ...@@ -335,8 +335,16 @@ int git_status_list_new(
status->head2idx, status->idx2wd, status_collect, status)) < 0) status->head2idx, status->idx2wd, status_collect, status)) < 0)
goto done; goto done;
if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
(flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) 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); git_vector_sort(&status->paired);
done: done:
......
...@@ -151,7 +151,7 @@ int git_submodule_foreach( ...@@ -151,7 +151,7 @@ int git_submodule_foreach(
int error; int error;
git_submodule *sm; git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT; git_vector seen = GIT_VECTOR_INIT;
seen._cmp = submodule_cmp; git_vector_set_cmp(&seen, submodule_cmp);
assert(repo && callback); assert(repo && callback);
......
...@@ -78,4 +78,13 @@ void git_vector_remove_matching( ...@@ -78,4 +78,13 @@ void git_vector_remove_matching(
int git_vector_resize_to(git_vector *v, size_t new_length); 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); 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 #endif
...@@ -6,6 +6,9 @@ int cb_status__normal( ...@@ -6,6 +6,9 @@ int cb_status__normal(
{ {
status_entry_counts *counts = payload; status_entry_counts *counts = payload;
if (counts->debug)
cb_status__print(path, status_flags, NULL);
if (counts->entry_count >= counts->expected_entry_count) { if (counts->entry_count >= counts->expected_entry_count) {
counts->wrong_status_flags_count++; counts->wrong_status_flags_count++;
goto exit; goto exit;
......
...@@ -8,6 +8,7 @@ typedef struct { ...@@ -8,6 +8,7 @@ typedef struct {
const unsigned int* expected_statuses; const unsigned int* expected_statuses;
const char** expected_paths; const char** expected_paths;
int expected_entry_count; int expected_entry_count;
bool debug;
} status_entry_counts; } status_entry_counts;
/* cb_status__normal takes payload of "status_entry_counts *" */ /* cb_status__normal takes payload of "status_entry_counts *" */
......
...@@ -743,3 +743,83 @@ void test_status_worktree__simple_delete_indexed(void) ...@@ -743,3 +743,83 @@ void test_status_worktree__simple_delete_indexed(void)
GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
git_status_list_free(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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment