summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/diff.h1
-rw-r--r--src/attr.c7
-rw-r--r--src/attr.h3
-rw-r--r--src/attr_file.c11
-rw-r--r--src/attr_file.h3
-rw-r--r--src/diff.c64
-rw-r--r--src/ignore.c53
-rw-r--r--src/ignore.h1
-rw-r--r--src/index.c64
-rw-r--r--src/index.h2
-rw-r--r--src/iterator.c189
-rw-r--r--src/iterator.h20
-rw-r--r--src/status.c18
-rw-r--r--src/util.c5
-rw-r--r--src/util.h7
-rw-r--r--src/vector.c16
-rw-r--r--src/vector.h1
-rw-r--r--tests-clar/attr/lookup.c2
-rw-r--r--tests-clar/status/status_data.h50
-rw-r--r--tests-clar/status/worktree.c10
20 files changed, 475 insertions, 52 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h
index 05825c50d..61b5bdbd0 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -47,6 +47,7 @@ enum {
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
+ GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12)
};
/**
diff --git a/src/attr.c b/src/attr.c
index 68f8d7de6..025ad3c87 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -376,6 +376,7 @@ int git_attr_cache__push_file(
const char *filename,
git_attr_file_source source,
git_attr_file_parser parse,
+ void* parsedata,
git_vector *stack)
{
int error = 0;
@@ -436,7 +437,7 @@ int git_attr_cache__push_file(
goto finish;
}
- if (parse && (error = parse(repo, content, file)) < 0)
+ if (parse && (error = parse(repo, parsedata, content, file)) < 0)
goto finish;
git_strmap_insert(cache->files, file->key, file, error); //-V595
@@ -468,7 +469,7 @@ finish:
}
#define push_attr_file(R,S,B,F) \
- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S))
+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
typedef struct {
git_repository *repo;
@@ -517,7 +518,7 @@ static int push_one_attr(void *ref, git_buf *path)
for (i = 0; !error && i < n_src; ++i)
error = git_attr_cache__push_file(
info->repo, path->ptr, GIT_ATTR_FILE, src[i],
- git_attr_file__parse_buffer, info->files);
+ git_attr_file__parse_buffer, NULL, info->files);
return error;
}
diff --git a/src/attr.h b/src/attr.h
index 7589bb10a..c3a19a190 100644
--- a/src/attr.h
+++ b/src/attr.h
@@ -25,7 +25,7 @@ typedef struct {
} git_attr_cache;
typedef int (*git_attr_file_parser)(
- git_repository *, const char *, git_attr_file *);
+ git_repository *, void *, const char *, git_attr_file *);
extern int git_attr_cache__init(git_repository *repo);
@@ -41,6 +41,7 @@ extern int git_attr_cache__push_file(
const char *filename,
git_attr_file_source source,
git_attr_file_parser parse,
+ void *parsedata, /* passed through to parse function */
git_vector *stack);
extern int git_attr_cache__internal_file(
diff --git a/src/attr_file.c b/src/attr_file.c
index b2f312e3e..ca8bdd89d 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -53,7 +53,7 @@ fail:
}
int git_attr_file__parse_buffer(
- git_repository *repo, const char *buffer, git_attr_file *attrs)
+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
{
int error = 0;
const char *scan = NULL;
@@ -123,7 +123,7 @@ int git_attr_file__new_and_load(
if (!(error = git_futils_readbuffer(&content, path)))
error = git_attr_file__parse_buffer(
- NULL, git_buf_cstr(&content), *attrs_ptr);
+ NULL, NULL, git_buf_cstr(&content), *attrs_ptr);
git_buf_free(&content);
@@ -207,16 +207,17 @@ bool git_attr_fnmatch__match(
const git_attr_path *path)
{
int fnm;
+ int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0;
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
return false;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
- fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME);
+ fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags);
else if (path->is_dir)
- fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR);
+ fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags);
else
- fnm = p_fnmatch(match->pattern, path->basename, 0);
+ fnm = p_fnmatch(match->pattern, path->basename, icase_flags);
return (fnm == FNM_NOMATCH) ? false : true;
}
diff --git a/src/attr_file.h b/src/attr_file.h
index 9d6730d90..b28d8a02b 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -23,6 +23,7 @@
#define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
+#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
extern const char *git_attr__true;
extern const char *git_attr__false;
@@ -96,7 +97,7 @@ extern void git_attr_file__free(git_attr_file *file);
extern void git_attr_file__clear_rules(git_attr_file *file);
extern int git_attr_file__parse_buffer(
- git_repository *repo, const char *buf, git_attr_file *file);
+ git_repository *repo, void *parsedata, const char *buf, git_attr_file *file);
extern int git_attr_file__lookup_one(
git_attr_file *file,
diff --git a/src/diff.c b/src/diff.c
index 499b95b44..1bda305ad 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -574,6 +574,22 @@ static int maybe_modified(
diff, status, oitem, omode, nitem, nmode, use_noid);
}
+static int git_index_entry_cmp_case(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcmp(entry_a->path, entry_b->path);
+}
+
+static int git_index_entry_cmp_icase(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcasecmp(entry_a->path, entry_b->path);
+}
+
static int diff_from_iterators(
git_repository *repo,
const git_diff_options *opts, /**< can be NULL for defaults */
@@ -584,12 +600,36 @@ static int diff_from_iterators(
const git_index_entry *oitem, *nitem;
git_buf ignore_prefix = GIT_BUF_INIT;
git_diff_list *diff = git_diff_list_alloc(repo, opts);
+ git_vector_cmp entry_compare;
+
if (!diff)
goto fail;
diff->old_src = old_iter->type;
diff->new_src = new_iter->type;
+ /* Use case-insensitive compare if either iterator has
+ * the ignore_case bit set */
+ if (!old_iter->ignore_case && !new_iter->ignore_case) {
+ entry_compare = git_index_entry_cmp_case;
+ diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
+ } else {
+ entry_compare = git_index_entry_cmp_icase;
+ diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+
+ /* If one of the iterators doesn't have ignore_case set,
+ * then that's unfortunate because we'll have to spool
+ * its data, sort it icase, and then use that for our
+ * merge join to the other iterator that is icase sorted */
+ if (!old_iter->ignore_case) {
+ if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0)
+ goto fail;
+ } else if (!new_iter->ignore_case) {
+ if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0)
+ goto fail;
+ }
+ }
+
if (git_iterator_current(old_iter, &oitem) < 0 ||
git_iterator_current(new_iter, &nitem) < 0)
goto fail;
@@ -598,7 +638,7 @@ static int diff_from_iterators(
while (oitem || nitem) {
/* create DELETED records for old items not matched in new */
- if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) {
+ if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
git_iterator_advance(old_iter, &oitem) < 0)
goto fail;
@@ -607,12 +647,12 @@ static int diff_from_iterators(
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
- else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) {
+ else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) {
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
/* check if contained in ignored parent directory */
if (git_buf_len(&ignore_prefix) &&
- git__prefixcmp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
+ ITERATOR_PREFIXCMP(*old_iter, nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
delta_type = GIT_DELTA_IGNORED;
if (S_ISDIR(nitem->mode)) {
@@ -620,7 +660,7 @@ static int diff_from_iterators(
* it or if the user requested the contents of untracked
* directories and it is not under an ignored directory.
*/
- if ((oitem && git__prefixcmp(oitem->path, nitem->path) == 0) ||
+ if ((oitem && ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path) == 0) ||
(delta_type == GIT_DELTA_UNTRACKED &&
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0))
{
@@ -674,7 +714,7 @@ static int diff_from_iterators(
* (or ADDED and DELETED pair if type changed)
*/
else {
- assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0);
+ assert(oitem && nitem && entry_compare(oitem->path, nitem->path) == 0);
if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
git_iterator_advance(old_iter, &oitem) < 0 ||
@@ -805,6 +845,7 @@ int git_diff_merge(
git_pool onto_pool;
git_vector onto_new;
git_diff_delta *delta;
+ bool ignore_case = false;
unsigned int i, j;
assert(onto && from);
@@ -816,10 +857,21 @@ int git_diff_merge(
git_pool_init(&onto_pool, 1, 0) < 0)
return -1;
+ if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
+ {
+ ignore_case = true;
+
+ /* This function currently only supports merging diff lists that
+ * are sorted identically. */
+ assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);
+ }
+
for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
- int cmp = !f ? -1 : !o ? 1 : strcmp(o->old_file.path, f->old_file.path);
+ int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
if (cmp < 0) {
delta = diff_delta__dup(o, &onto_pool);
diff --git a/src/ignore.c b/src/ignore.c
index 3c2f19ab9..c562f4e43 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -1,20 +1,37 @@
#include "git2/ignore.h"
#include "ignore.h"
#include "path.h"
+#include "config.h"
#define GIT_IGNORE_INTERNAL "[internal]exclude"
#define GIT_IGNORE_FILE_INREPO "info/exclude"
#define GIT_IGNORE_FILE ".gitignore"
static int parse_ignore_file(
- git_repository *repo, const char *buffer, git_attr_file *ignores)
+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores)
{
int error = 0;
git_attr_fnmatch *match = NULL;
const char *scan = NULL;
char *context = NULL;
-
- GIT_UNUSED(repo);
+ bool ignore_case = false;
+ git_config *cfg = NULL;
+ int val;
+
+ /* Prefer to have the caller pass in a git_ignores as the parsedata object.
+ * If they did not, then we can (much more slowly) find the value of
+ * ignore_case by using the repository object. */
+ if (parsedata != NULL) {
+ ignore_case = ((git_ignores *)parsedata)->ignore_case;
+ } else {
+ if ((error = git_repository_config(&cfg, repo)) < 0)
+ return error;
+
+ if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
+ ignore_case = (val != 0);
+
+ git_config_free(cfg);
+ }
if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) {
context = ignores->key + 2;
@@ -31,6 +48,9 @@ static int parse_ignore_file(
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+ if (ignore_case)
+ match->flags |= GIT_ATTR_FNMATCH_ICASE;
+
if (!(error = git_attr_fnmatch__parse(
match, ignores->pool, context, &scan)))
{
@@ -58,13 +78,13 @@ static int parse_ignore_file(
return error;
}
-#define push_ignore_file(R,S,B,F) \
- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(S))
+#define push_ignore_file(R,IGN,S,B,F) \
+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S))
static int push_one_ignore(void *ref, git_buf *path)
{
git_ignores *ign = (git_ignores *)ref;
- return push_ignore_file(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+ return push_ignore_file(ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
}
int git_ignore__for_path(
@@ -74,6 +94,8 @@ int git_ignore__for_path(
{
int error = 0;
const char *workdir = git_repository_workdir(repo);
+ git_config *cfg = NULL;
+ int val;
assert(ignores);
@@ -81,6 +103,17 @@ int git_ignore__for_path(
git_buf_init(&ignores->dir, 0);
ignores->ign_internal = NULL;
+ /* Set the ignore_case flag appropriately */
+ if ((error = git_repository_config(&cfg, repo)) < 0)
+ goto cleanup;
+
+ if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
+ ignores->ignore_case = (val != 0);
+ else
+ ignores->ignore_case = 0;
+
+ git_config_free(cfg);
+
if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 ||
(error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 ||
(error = git_attr_cache__init(repo)) < 0)
@@ -109,14 +142,14 @@ int git_ignore__for_path(
}
/* load .git/info/exclude */
- error = push_ignore_file(repo, &ignores->ign_global,
+ error = push_ignore_file(repo, ignores, &ignores->ign_global,
git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
if (error < 0)
goto cleanup;
/* load core.excludesfile */
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
- error = push_ignore_file(repo, &ignores->ign_global, NULL,
+ error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL,
git_repository_attr_cache(repo)->cfg_excl_file);
cleanup:
@@ -132,7 +165,7 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir)
return -1;
else
return push_ignore_file(
- ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
+ ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
}
int git_ignore__pop_dir(git_ignores *ign)
@@ -223,7 +256,7 @@ int git_ignore_add_rule(
git_attr_file *ign_internal;
if (!(error = get_internal_ignores(&ign_internal, repo)))
- error = parse_ignore_file(repo, rules, ign_internal);
+ error = parse_ignore_file(repo, NULL, rules, ign_internal);
return error;
}
diff --git a/src/ignore.h b/src/ignore.h
index 809d2edbd..1d472cc47 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -23,6 +23,7 @@ typedef struct {
git_attr_file *ign_internal;
git_vector ign_path;
git_vector ign_global;
+ unsigned int ignore_case:1;
} git_ignores;
extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign);
diff --git a/src/index.c b/src/index.c
index 3a92c360b..f9f3b14cc 100644
--- a/src/index.c
+++ b/src/index.c
@@ -99,6 +99,13 @@ static int index_srch(const void *key, const void *array_member)
return strcmp(key, entry->path);
}
+static int index_isrch(const void *key, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcasecmp(key, entry->path);
+}
+
static int index_cmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
@@ -107,6 +114,14 @@ static int index_cmp(const void *a, const void *b)
return strcmp(entry_a->path, entry_b->path);
}
+static int index_icmp(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcasecmp(entry_a->path, entry_b->path);
+}
+
static int unmerged_srch(const void *key, const void *array_member)
{
const git_index_entry_unmerged *entry = array_member;
@@ -147,6 +162,14 @@ static unsigned int index_merge_mode(
return index_create_mode(mode);
}
+static void index_set_ignore_case(git_index *index, bool ignore_case)
+{
+ index->entries._cmp = ignore_case ? index_icmp : index_cmp;
+ index->entries_search = ignore_case ? index_isrch : index_srch;
+ index->entries.sorted = 0;
+ git_vector_sort(&index->entries);
+}
+
int git_index_open(git_index **index_out, const char *index_path)
{
git_index *index;
@@ -162,6 +185,8 @@ int git_index_open(git_index **index_out, const char *index_path)
if (git_vector_init(&index->entries, 32, index_cmp) < 0)
return -1;
+ index->entries_search = index_srch;
+
/* Check if index file is stored on disk already */
if (git_path_exists(index->index_file_path) == true)
index->on_disk = 1;
@@ -228,8 +253,12 @@ void git_index_clear(git_index *index)
int git_index_set_caps(git_index *index, unsigned int caps)
{
+ int old_ignore_case;
+
assert(index);
+ old_ignore_case = index->ignore_case;
+
if (caps == GIT_INDEXCAP_FROM_OWNER) {
git_config *cfg;
int val;
@@ -255,6 +284,11 @@ int git_index_set_caps(git_index *index, unsigned int caps)
index->no_symlinks = ((caps & GIT_INDEXCAP_NO_SYMLINKS) != 0);
}
+ if (old_ignore_case != index->ignore_case)
+ {
+ index_set_ignore_case(index, index->ignore_case);
+ }
+
return 0;
}
@@ -552,14 +586,14 @@ int git_index_remove(git_index *index, int position)
int git_index_find(git_index *index, const char *path)
{
- return git_vector_bsearch2(&index->entries, index_srch, path);
+ return git_vector_bsearch2(&index->entries, index->entries_search, path);
}
unsigned int git_index__prefix_position(git_index *index, const char *path)
{
unsigned int pos;
- git_vector_bsearch3(&pos, &index->entries, index_srch, path);
+ git_vector_bsearch3(&pos, &index->entries, index->entries_search, path);
return pos;
}
@@ -938,16 +972,28 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
static int write_entries(git_index *index, git_filebuf *file)
{
+ int error = 0;
unsigned int i;
-
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *entry;
- entry = git_vector_get(&index->entries, i);
- if (write_disk_entry(file, entry) < 0)
- return -1;
+ git_vector case_sorted;
+ git_index_entry *entry;
+ git_vector *out = &index->entries;
+
+ /* If index->entries is sorted case-insensitively, then we need
+ * to re-sort it case-sensitively before writing */
+ if (index->ignore_case) {
+ git_vector_dup(&case_sorted, &index->entries, index_cmp);
+ git_vector_sort(&case_sorted);
+ out = &case_sorted;
}
- return 0;
+ git_vector_foreach(out, i, entry)
+ if ((error = write_disk_entry(file, entry)) < 0)
+ break;
+
+ if (index->ignore_case)
+ git_vector_free(&case_sorted);
+
+ return error;
}
static int write_index(git_index *index, git_filebuf *file)
diff --git a/src/index.h b/src/index.h
index a57da5386..7dd23ee60 100644
--- a/src/index.h
+++ b/src/index.h
@@ -34,6 +34,8 @@ struct git_index {
git_tree_cache *tree;
git_vector unmerged;
+
+ git_vector_cmp entries_search;
};
extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry);
diff --git a/src/iterator.c b/src/iterator.c
index e30e11220..e52554d4f 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -17,6 +17,7 @@
(P)->base.type = GIT_ITERATOR_ ## NAME_UC; \
(P)->base.start = start ? git__strdup(start) : NULL; \
(P)->base.end = end ? git__strdup(end) : NULL; \
+ (P)->base.ignore_case = 0; \
(P)->base.current = NAME_LC ## _iterator__current; \
(P)->base.at_end = NAME_LC ## _iterator__at_end; \
(P)->base.advance = NAME_LC ## _iterator__advance; \
@@ -336,7 +337,7 @@ static int index_iterator__current(
if (ie != NULL &&
ii->base.end != NULL &&
- git__prefixcmp(ie->path, ii->base.end) > 0)
+ ITERATOR_PREFIXCMP(ii->base, ie->path, ii->base.end) > 0)
{
ii->current = git_index_entrycount(ii->index);
ie = NULL;
@@ -401,6 +402,7 @@ int git_iterator_for_index_range(
if ((error = git_repository_index(&ii->index, repo)) < 0)
git__free(ii);
else {
+ ii->base.ignore_case = ii->index->ignore_case;
ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
*iter = (git_iterator *)ii;
}
@@ -428,12 +430,30 @@ typedef struct {
int is_ignored;
} workdir_iterator;
-static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
+static int git_path_with_stat_cmp_case(const void *a, const void *b)
+{
+ const git_path_with_stat *path_with_stat_a = a;
+ const git_path_with_stat *path_with_stat_b = b;
+
+ return strcmp(path_with_stat_a->path, path_with_stat_b->path);
+}
+
+static int git_path_with_stat_cmp_icase(const void *a, const void *b)
+{
+ const git_path_with_stat *path_with_stat_a = a;
+ const git_path_with_stat *path_with_stat_b = b;
+
+ return strcasecmp(path_with_stat_a->path, path_with_stat_b->path);
+}
+
+static workdir_iterator_frame *workdir_iterator__alloc_frame(workdir_iterator *wi)
{
workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
+ git_vector_cmp entry_compare = CASESELECT(wi->base.ignore_case, git_path_with_stat_cmp_icase, git_path_with_stat_cmp_case);
+
if (wf == NULL)
return NULL;
- if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) {
+ if (git_vector_init(&wf->entries, 0, entry_compare) != 0) {
git__free(wf);
return NULL;
}
@@ -453,16 +473,22 @@ static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
static int workdir_iterator__update_entry(workdir_iterator *wi);
-static int workdir_iterator__entry_cmp(const void *prefix, const void *item)
+static int workdir_iterator__entry_cmp_case(const void *prefix, const void *item)
{
const git_path_with_stat *ps = item;
return git__prefixcmp((const char *)prefix, ps->path);
}
+static int workdir_iterator__entry_cmp_icase(const void *prefix, const void *item)
+{
+ const git_path_with_stat *ps = item;
+ return git__prefixcmp_icase((const char *)prefix, ps->path);
+}
+
static int workdir_iterator__expand_dir(workdir_iterator *wi)
{
int error;
- workdir_iterator_frame *wf = workdir_iterator__alloc_frame();
+ workdir_iterator_frame *wf = workdir_iterator__alloc_frame(wi);
GITERR_CHECK_ALLOC(wf);
error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries);
@@ -476,12 +502,15 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi)
if (!wi->stack)
wf->start = wi->base.start;
else if (wi->stack->start &&
- git__prefixcmp(wi->stack->start, wi->path.ptr + wi->root_len) == 0)
+ ITERATOR_PREFIXCMP(wi->base, wi->stack->start, wi->path.ptr + wi->root_len) == 0)
wf->start = wi->stack->start;
if (wf->start)
git_vector_bsearch3(
- &wf->index, &wf->entries, workdir_iterator__entry_cmp, wf->start);
+ &wf->index,
+ &wf->entries,
+ CASESELECT(wi->base.ignore_case, workdir_iterator__entry_cmp_icase, workdir_iterator__entry_cmp_case),
+ wf->start);
wf->next = wi->stack;
wi->stack = wf;
@@ -526,8 +555,8 @@ static int workdir_iterator__advance(
next = git_vector_get(&wf->entries, ++wf->index);
if (next != NULL) {
/* match git's behavior of ignoring anything named ".git" */
- if (strcmp(next->path, DOT_GIT "/") == 0 ||
- strcmp(next->path, DOT_GIT) == 0)
+ if (STRCMP_CASESELECT(wi->base.ignore_case, next->path, DOT_GIT "/") == 0 ||
+ STRCMP_CASESELECT(wi->base.ignore_case, next->path, DOT_GIT) == 0)
continue;
/* else found a good entry */
break;
@@ -604,13 +633,14 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
return -1;
if (wi->base.end &&
- git__prefixcmp(wi->path.ptr + wi->root_len, wi->base.end) > 0)
+ ITERATOR_PREFIXCMP(wi->base, wi->path.ptr + wi->root_len, wi->base.end) > 0)
return 0;
wi->entry.path = ps->path;
/* skip over .git entry */
- if (strcmp(ps->path, DOT_GIT "/") == 0 || strcmp(ps->path, DOT_GIT) == 0)
+ if (STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT "/") == 0 ||
+ STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT) == 0)
return workdir_iterator__advance((git_iterator *)wi, NULL);
/* if there is an error processing the entry, treat as ignored */
@@ -656,6 +686,7 @@ int git_iterator_for_workdir_range(
{
int error;
workdir_iterator *wi;
+ git_index *index;
assert(iter && repo);
@@ -666,6 +697,17 @@ int git_iterator_for_workdir_range(
wi->repo = repo;
+ if ((error = git_repository_index(&index, repo)) < 0) {
+ git__free(wi);
+ return error;
+ }
+
+ /* Set the ignore_case flag for the workdir iterator to match
+ * that of the index. */
+ wi->base.ignore_case = index->ignore_case;
+
+ git_index_free(index);
+
if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
git_path_to_dir(&wi->path) < 0 ||
git_ignore__for_path(repo, "", &wi->ignores) < 0)
@@ -690,6 +732,129 @@ int git_iterator_for_workdir_range(
return error;
}
+typedef struct {
+ git_iterator base;
+ git_iterator *wrapped;
+ git_vector entries;
+ git_vector_cmp comparer;
+ git_pool entry_pool;
+ git_pool string_pool;
+ unsigned int position;
+} spoolandsort_iterator;
+
+static int spoolandsort_iterator__current(
+ git_iterator *self, const git_index_entry **entry)
+{
+ spoolandsort_iterator *si = (spoolandsort_iterator *)self;
+
+ if (si->position < si->entries.length)
+ *entry = (const git_index_entry *)git_vector_get_const(&si->entries, si->position);
+ else
+ *entry = NULL;
+
+ return 0;
+}
+
+static int spoolandsort_iterator__at_end(git_iterator *self)
+{
+ spoolandsort_iterator *si = (spoolandsort_iterator *)self;
+
+ return 0 == si->entries.length || si->entries.length - 1 <= si->position;
+}
+
+static int spoolandsort_iterator__advance(
+ git_iterator *self, const git_index_entry **entry)
+{
+ spoolandsort_iterator *si = (spoolandsort_iterator *)self;
+
+ if (si->position < si->entries.length)
+ *entry = (const git_index_entry *)git_vector_get_const(&si->entries, ++si->position);
+ else
+ *entry = NULL;
+
+ return 0;
+}
+
+static int spoolandsort_iterator__seek(git_iterator *self, const char *prefix)
+{
+ GIT_UNUSED(self);
+ GIT_UNUSED(prefix);
+
+ return -1;
+}
+
+static int spoolandsort_iterator__reset(git_iterator *self)
+{
+ spoolandsort_iterator *si = (spoolandsort_iterator *)self;
+
+ si->position = 0;
+
+ return 0;
+}
+
+static void spoolandsort_iterator__free(git_iterator *self)
+{
+ spoolandsort_iterator *si = (spoolandsort_iterator *)self;
+
+ git_pool_clear(&si->string_pool);
+ git_pool_clear(&si->entry_pool);
+ git_vector_free(&si->entries);
+ git_iterator_free(si->wrapped);
+}
+
+int git_iterator_spoolandsort_range(
+ git_iterator **iter,
+ git_iterator *towrap,
+ git_vector_cmp comparer,
+ bool ignore_case,
+ const char *start,
+ const char *end)
+{
+ spoolandsort_iterator *si;
+ const git_index_entry *item;
+
+ assert(iter && towrap && comparer);
+
+ ITERATOR_BASE_INIT(si, spoolandsort, SPOOLANDSORT);
+ si->base.ignore_case = ignore_case;
+ si->wrapped = towrap;
+ si->comparer = comparer;
+ si->position = 0;
+
+ if (git_vector_init(&si->entries, 16, si->comparer) < 0 ||
+ git_iterator_current(towrap, &item) < 0 ||
+ git_pool_init(&si->entry_pool, sizeof(git_index_entry), 0) ||
+ git_pool_init(&si->string_pool, 1, 0))
+ {
+ git__free(si);
+ return -1;
+ }
+
+ while (item)
+ {
+ git_index_entry *clone = git_pool_malloc(&si->entry_pool, 1);
+ memcpy(clone, item, sizeof(git_index_entry));
+
+ if (item->path)
+ {
+ clone->path = git_pool_strdup(&si->string_pool, item->path);
+ }
+
+ git_vector_insert(&si->entries, clone);
+
+ if (git_iterator_advance(towrap, &item) < 0)
+ {
+ git__free(si);
+ return -1;
+ }
+ }
+
+ git_vector_sort(&si->entries);
+
+ *iter = (git_iterator *)si;
+
+ return 0;
+}
int git_iterator_current_tree_entry(
git_iterator *iter, const git_tree_entry **tree_entry)
@@ -737,6 +902,6 @@ int git_iterator_cmp(
if (!path_prefix)
return -1;
- return git__prefixcmp(entry->path, path_prefix);
+ return ITERATOR_PREFIXCMP(*iter, entry->path, path_prefix);
}
diff --git a/src/iterator.h b/src/iterator.h
index b916a9080..552d5ca0e 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -9,6 +9,11 @@
#include "common.h"
#include "git2/index.h"
+#include "vector.h"
+
+#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX) (((ITER) ## .ignore_case) ? \
+ git__prefixcmp_icase((STR), (PREFIX)) : \
+ git__prefixcmp((STR), (PREFIX)))
typedef struct git_iterator git_iterator;
@@ -16,7 +21,8 @@ typedef enum {
GIT_ITERATOR_EMPTY = 0,
GIT_ITERATOR_TREE = 1,
GIT_ITERATOR_INDEX = 2,
- GIT_ITERATOR_WORKDIR = 3
+ GIT_ITERATOR_WORKDIR = 3,
+ GIT_ITERATOR_SPOOLANDSORT = 4
} git_iterator_type_t;
struct git_iterator {
@@ -29,6 +35,7 @@ struct git_iterator {
int (*seek)(git_iterator *, const char *prefix);
int (*reset)(git_iterator *);
void (*free)(git_iterator *);
+ unsigned int ignore_case:1;
};
extern int git_iterator_for_nothing(git_iterator **iter);
@@ -63,6 +70,17 @@ GIT_INLINE(int) git_iterator_for_workdir(
return git_iterator_for_workdir_range(iter, repo, NULL, NULL);
}
+extern int git_iterator_spoolandsort_range(
+ git_iterator **iter, git_iterator *towrap,
+ git_vector_cmp comparer, bool ignore_case,
+ const char *start, const char *end);
+
+GIT_INLINE(int) git_iterator_spoolandsort(
+ git_iterator **iter, git_iterator *towrap,
+ git_vector_cmp comparer, bool ignore_case)
+{
+ return git_iterator_spoolandsort_range(iter, towrap, comparer, ignore_case, NULL, NULL);
+}
/* Entry is not guaranteed to be fully populated. For a tree iterator,
* we will only populate the mode, oid and path, for example. For a workdir
diff --git a/src/status.c b/src/status.c
index 0a5fbdcbf..3a0ed075f 100644
--- a/src/status.c
+++ b/src/status.c
@@ -82,6 +82,7 @@ int git_status_foreach_ext(
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
git_diff_delta *i2h, *w2i;
size_t i, j, i_max, j_max;
+ bool ignore_case = false;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
@@ -124,11 +125,26 @@ int git_status_foreach_ext(
i_max = idx2head ? idx2head->deltas.length : 0;
j_max = wd2idx ? wd2idx->deltas.length : 0;
+ if (idx2head && wd2idx &&
+ (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
+ {
+ /* Then use the ignore-case sorter... */
+ ignore_case = true;
+
+ /* and assert that both are ignore-case sorted. If this function
+ * ever needs to support merge joining result sets that are not sorted
+ * by the same function, then it will need to be extended to do a spool
+ * and sort on one of the results before merge joining */
+ assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
+ }
+
for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) {
i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
- cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path);
+ cmp = !w2i ? -1 : !i2h ? 1 : STRCMP_CASESELECT(ignore_case, i2h->old_file.path, w2i->old_file.path);
if (cmp < 0) {
if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata))
diff --git a/src/util.c b/src/util.c
index 719714105..0a82ccea6 100644
--- a/src/util.c
+++ b/src/util.c
@@ -199,6 +199,11 @@ int git__prefixcmp(const char *str, const char *prefix)
}
}
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+ return strncasecmp(str, prefix, strlen(prefix));
+}
+
int git__suffixcmp(const char *str, const char *suffix)
{
size_t a = strlen(str);
diff --git a/src/util.h b/src/util.h
index 905fc927f..4f83d3bc1 100644
--- a/src/util.h
+++ b/src/util.h
@@ -70,7 +70,14 @@ GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
#define git__free(ptr) free(ptr)
+#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \
+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))
+
+#define CASESELECT(IGNORE_CASE, ICASE, CASE) \
+ ((IGNORE_CASE) ? (ICASE) : (CASE))
+
extern int git__prefixcmp(const char *str, const char *prefix);
+extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
diff --git a/src/vector.c b/src/vector.c
index 0308ce26e..c6a644cc3 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -24,6 +24,22 @@ static int resize_vector(git_vector *v)
return 0;
}
+int git_vector_dup(git_vector *v, git_vector *src, git_vector_cmp cmp)
+{
+ assert(v && src);
+
+ v->_alloc_size = src->length;
+ v->_cmp = cmp;
+ v->length = src->length;
+ v->sorted = src->sorted && cmp == src->_cmp;
+ v->contents = git__malloc(src->length * sizeof(void *));
+ GITERR_CHECK_ALLOC(v->contents);
+
+ memcpy(v->contents, src->contents, src->length * sizeof(void *));
+
+ return 0;
+}
+
void git_vector_free(git_vector *v)
{
assert(v);
diff --git a/src/vector.h b/src/vector.h
index f75e634ba..49ba754f0 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -24,6 +24,7 @@ typedef struct git_vector {
int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
void git_vector_clear(git_vector *v);
+int git_vector_dup(git_vector *v, git_vector *src, git_vector_cmp cmp);
void git_vector_swap(git_vector *a, git_vector *b);
void git_vector_sort(git_vector *v);
diff --git a/tests-clar/attr/lookup.c b/tests-clar/attr/lookup.c
index 40aac0b6e..200bdd2c7 100644
--- a/tests-clar/attr/lookup.c
+++ b/tests-clar/attr/lookup.c
@@ -252,7 +252,7 @@ void test_attr_lookup__from_buffer(void)
cl_git_pass(git_attr_file__new(&file, 0, NULL, NULL));
- cl_git_pass(git_attr_file__parse_buffer(NULL, "a* foo\nabc bar\n* baz", file));
+ cl_git_pass(git_attr_file__parse_buffer(NULL, NULL, "a* foo\nabc bar\n* baz", file));
cl_assert(file->rules.length == 3);
diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h
index 85a7cd6b5..a41bde7c2 100644
--- a/tests-clar/status/status_data.h
+++ b/tests-clar/status/status_data.h
@@ -90,6 +90,56 @@ static const int entry_count2 = 15;
/* entries for a copy of tests/resources/status with some mods */
+static const char *entry_paths3_icase[] = {
+ ".HEADER",
+ "42-is-not-prime.sigh",
+ "current_file",
+ "current_file/",
+ "file_deleted",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "README.md",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses3_icase[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+};
+
static const char *entry_paths3[] = {
".HEADER",
"42-is-not-prime.sigh",
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index 05e396e1f..0ceba7f16 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -110,7 +110,13 @@ void test_status_worktree__swap_subdir_and_file(void)
{
status_entry_counts counts;
git_repository *repo = cl_git_sandbox_init("status");
+ git_index *index;
git_status_options opts;
+ bool ignore_case;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ ignore_case = index->ignore_case;
+ git_index_free(index);
/* first alter the contents of the worktree */
cl_git_pass(p_rename("status/current_file", "status/swap"));
@@ -124,8 +130,8 @@ void test_status_worktree__swap_subdir_and_file(void)
/* now get status */
memset(&counts, 0x0, sizeof(status_entry_counts));
counts.expected_entry_count = entry_count3;
- counts.expected_paths = entry_paths3;
- counts.expected_statuses = entry_statuses3;
+ counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3;
+ counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3;
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |