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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
diff --git a/examples/status.c b/examples/status.c
index 2378c78..3c82640 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 282b606..63aea2f 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 d059cfe..d880398 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 3846a5e..26e1174 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 d556852..1d46779 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 375100a..e520c10 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 af488b7..89eba2a 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 e2f729b..1bda9c9 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 f073c24..902b65c 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 ae1469e..f1f009e 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 7c27ee5..920671e 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);
+}