diff options
-rw-r--r-- | include/git2/diff.h | 1 | ||||
-rw-r--r-- | src/attr.c | 7 | ||||
-rw-r--r-- | src/attr.h | 3 | ||||
-rw-r--r-- | src/attr_file.c | 11 | ||||
-rw-r--r-- | src/attr_file.h | 3 | ||||
-rw-r--r-- | src/diff.c | 64 | ||||
-rw-r--r-- | src/ignore.c | 53 | ||||
-rw-r--r-- | src/ignore.h | 1 | ||||
-rw-r--r-- | src/index.c | 64 | ||||
-rw-r--r-- | src/index.h | 2 | ||||
-rw-r--r-- | src/iterator.c | 189 | ||||
-rw-r--r-- | src/iterator.h | 20 | ||||
-rw-r--r-- | src/status.c | 18 | ||||
-rw-r--r-- | src/util.c | 5 | ||||
-rw-r--r-- | src/util.h | 7 | ||||
-rw-r--r-- | src/vector.c | 16 | ||||
-rw-r--r-- | src/vector.h | 1 | ||||
-rw-r--r-- | tests-clar/attr/lookup.c | 2 | ||||
-rw-r--r-- | tests-clar/status/status_data.h | 50 | ||||
-rw-r--r-- | tests-clar/status/worktree.c | 10 |
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 | |