Commit ec40b7f9 by Philip Kelley

Support for core.ignorecase

parent e8776d30
......@@ -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)
};
/**
......
......@@ -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;
}
......
......@@ -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(
......
......@@ -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;
}
......
......@@ -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,
......
......@@ -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);
......
#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;
}
......
......@@ -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);
......
......@@ -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)
......
......@@ -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);
......
......@@ -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);
}
......@@ -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
......
......@@ -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))
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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",
......
......@@ -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 |
......
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