diff --git a/include/git2/tree.h b/include/git2/tree.h index 7726a6599..3861102d9 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -208,6 +208,15 @@ GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry); */ GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); +/** + * Compare two tree entries + * + * @param e1 first tree entry + * @param e2 second tree entry + * @return <0 if e1 is before e2, 0 if e1 == e2, >0 if e1 is after e2 + */ +GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2); + /** * Convert a tree entry to the git_object it points too. * diff --git a/src/checkout.c b/src/checkout.c index d5a471d0c..411bf3be7 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -224,7 +224,7 @@ static int checkout_action_wd_only( if (!git_pathspec_match_path( pathspec, wd->path, (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - workdir->ignore_case)) + git_iterator_ignore_case(workdir))) return 0; /* check if item is tracked in the index but not in the checkout diff */ @@ -1130,7 +1130,7 @@ static int checkout_data_init( if ((error = git_config_refresh(cfg)) < 0) goto cleanup; - if (git_iterator_inner_type(target) == GIT_ITERATOR_INDEX) { + if (git_iterator_inner_type(target) == GIT_ITERATOR_TYPE_INDEX) { /* if we are iterating over the index, don't reload */ data->index = git_iterator_index_get_index(target); GIT_REFCOUNT_INC(data->index); @@ -1208,6 +1208,7 @@ int git_checkout_iterator( git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; uint32_t *actions = NULL; size_t *counts = NULL; + git_iterator_flag_t iterflags = 0; /* initialize structures and options */ error = checkout_data_init(&data, target, opts); @@ -1228,18 +1229,21 @@ int git_checkout_iterator( diff_opts.pathspec = data.opts.paths; /* set up iterators */ + + iterflags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_workdir_range( - &workdir, data.repo, data.pfx, data.pfx)) < 0 || + &workdir, data.repo, iterflags, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_tree_range( - &baseline, data.opts.baseline, data.pfx, data.pfx)) < 0) + &baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0) goto cleanup; /* Handle case insensitivity for baseline if necessary */ - if (workdir->ignore_case && !baseline->ignore_case) { + if (git_iterator_ignore_case(workdir) != git_iterator_ignore_case(baseline)) if ((error = git_iterator_spoolandsort_push(baseline, true)) < 0) goto cleanup; - } /* Generate baseline-to-target diff which will include an entry for * every possible update that might need to be made. diff --git a/src/diff.c b/src/diff.c index 5e34b9221..4b60935f0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -418,7 +418,7 @@ static int maybe_modified( git_delta_t status = GIT_DELTA_MODIFIED; unsigned int omode = oitem->mode; unsigned int nmode = nitem->mode; - bool new_is_workdir = (new_iter->type == GIT_ITERATOR_WORKDIR); + bool new_is_workdir = (new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); GIT_UNUSED(old_iter); @@ -556,7 +556,9 @@ static int diff_list_init_from_iterators( /* Use case-insensitive compare if either iterator has * the ignore_case bit set */ - if (!old_iter->ignore_case && !new_iter->ignore_case) { + if (!git_iterator_ignore_case(old_iter) && + !git_iterator_ignore_case(new_iter)) + { diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; diff->strcomp = git__strcmp; @@ -714,7 +716,7 @@ int git_diff__from_iterators( else if (git_iterator_current_is_ignored(new_iter)) delta_type = GIT_DELTA_IGNORED; - else if (new_iter->type != GIT_ITERATOR_WORKDIR) + else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; if (diff_delta__from_one(diff, delta_type, nitem) < 0) @@ -786,8 +788,8 @@ int git_diff_tree_to_tree( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, pfx, pfx), - git_iterator_for_tree_range(&b, new_tree, pfx, pfx) + git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), + git_iterator_for_tree_range(&b, new_tree, 0, pfx, pfx) ); return error; @@ -808,8 +810,8 @@ int git_diff_tree_to_index( return error; DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, pfx, pfx), - git_iterator_for_index_range(&b, index, pfx, pfx) + git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), + git_iterator_for_index_range(&b, index, 0, pfx, pfx) ); return error; @@ -829,8 +831,8 @@ int git_diff_index_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_index_range(&a, index, pfx, pfx), - git_iterator_for_workdir_range(&b, repo, pfx, pfx) + git_iterator_for_index_range(&a, index, 0, pfx, pfx), + git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx) ); return error; @@ -848,8 +850,8 @@ int git_diff_tree_to_workdir( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, pfx, pfx), - git_iterator_for_workdir_range(&b, repo, pfx, pfx) + git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), + git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx) ); return error; diff --git a/src/diff_output.c b/src/diff_output.c index e79bf30d2..8a7a7a2a1 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -495,7 +495,7 @@ static void diff_patch_init( patch->old_src = patch->diff->old_src; patch->new_src = patch->diff->new_src; } else { - patch->old_src = patch->new_src = GIT_ITERATOR_TREE; + patch->old_src = patch->new_src = GIT_ITERATOR_TYPE_TREE; } } @@ -578,7 +578,7 @@ static int diff_patch_load( */ if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && - patch->old_src == GIT_ITERATOR_WORKDIR) { + patch->old_src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = get_workdir_content( ctxt, delta, &delta->old_file, &patch->old_data)) < 0) goto cleanup; @@ -587,7 +587,7 @@ static int diff_patch_load( } if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && - patch->new_src == GIT_ITERATOR_WORKDIR) { + patch->new_src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = get_workdir_content( ctxt, delta, &delta->new_file, &patch->new_data)) < 0) goto cleanup; @@ -596,7 +596,7 @@ static int diff_patch_load( } if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && - patch->old_src != GIT_ITERATOR_WORKDIR) { + patch->old_src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = get_blob_content( ctxt, delta, &delta->old_file, &patch->old_data, &patch->old_blob)) < 0) @@ -606,7 +606,7 @@ static int diff_patch_load( } if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && - patch->new_src != GIT_ITERATOR_WORKDIR) { + patch->new_src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = get_blob_content( ctxt, delta, &delta->new_file, &patch->new_data, &patch->new_blob)) < 0) @@ -1666,32 +1666,28 @@ int git_diff__paired_foreach( int cmp; git_diff_delta *i2h, *w2i; size_t i, j, i_max, j_max; - bool icase = false; + int (*strcomp)(const char *, const char *); 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... */ - icase = true; + /* Get appropriate strcmp function */ + strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL; - /* 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)); - } + /* Assert both iterators use matching ignore-case. If this function ever + * supports merging diffs that are not sorted by the same function, then + * it will need to spool and sort on one of the results before merging + */ + if (idx2head && wd2idx) { + assert(idx2head->strcomp == wd2idx->strcomp); + } for (i = 0, j = 0; 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_CASESELECT(icase, i2h->old_file.path, w2i->old_file.path); + strcomp(i2h->old_file.path, w2i->old_file.path); if (cmp < 0) { if (cb(i2h, NULL, payload)) diff --git a/src/iterator.c b/src/iterator.c index 08e2e79e4..56b262975 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -24,14 +24,14 @@ #define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \ (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \ GITERR_CHECK_ALLOC(P); \ - (P)->base.type = GIT_ITERATOR_ ## NAME_UC; \ + (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \ (P)->base.cb = &(P)->cb; \ ITERATOR_SET_CB(P,NAME_LC); \ (P)->base.start = start ? git__strdup(start) : NULL; \ (P)->base.end = end ? git__strdup(end) : NULL; \ - (P)->base.ignore_case = false; \ if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \ git__free(P); return -1; } \ + (P)->base.prefixcomp = git__prefixcmp; \ } while (0) static int iterator__reset_range( @@ -54,6 +54,34 @@ static int iterator__reset_range( return 0; } +static int iterator_update_ignore_case( + git_iterator *iter, + git_iterator_flag_t flags) +{ + int error = 0, ignore_case = -1; + + if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + ignore_case = true; + else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) + ignore_case = false; + else { + git_index *index; + + if (!(error = git_repository_index__weakptr(&index, iter->repo))) + ignore_case = (index->ignore_case != false); + } + + if (ignore_case > 0) + iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE); + else if (ignore_case == 0) + iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); + + iter->prefixcomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ? + git__prefixcmp_icase : git__prefixcmp; + + return error; +} + static int empty_iterator__no_item( git_iterator *iter, const git_index_entry **entry) { @@ -91,13 +119,14 @@ typedef struct { git_iterator_callbacks cb; } empty_iterator; -int git_iterator_for_nothing(git_iterator **iter) +int git_iterator_for_nothing(git_iterator **iter, git_iterator_flag_t flags) { empty_iterator *i = git__calloc(1, sizeof(empty_iterator)); GITERR_CHECK_ALLOC(i); - i->base.type = GIT_ITERATOR_EMPTY; + i->base.type = GIT_ITERATOR_TYPE_EMPTY; i->base.cb = &i->cb; + i->base.flags = flags; i->cb.current = empty_iterator__no_item; i->cb.at_end = empty_iterator__at_end; i->cb.advance = empty_iterator__no_item; @@ -116,7 +145,10 @@ struct tree_iterator_frame { tree_iterator_frame *next, *prev; git_tree *tree; char *start; + size_t startlen; size_t index; + void **icase_map; + void *icase_data[GIT_FLEX_ARRAY]; }; typedef struct { @@ -130,7 +162,13 @@ typedef struct { GIT_INLINE(const git_tree_entry *)tree_iterator__tree_entry(tree_iterator *ti) { - return git_tree_entry_byindex(ti->stack->tree, ti->stack->index); + tree_iterator_frame *tf = ti->stack; + + if (tf->index >= git_tree_entrycount(tf->tree)) + return NULL; + + return git_tree_entry_byindex( + tf->tree, tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index); } static char *tree_iterator__current_filename( @@ -149,7 +187,10 @@ static void tree_iterator__free_frame(tree_iterator_frame *tf) { if (!tf) return; + git_tree_free(tf->tree); + tf->tree = NULL; + git__free(tf); } @@ -195,7 +236,7 @@ static int tree_iterator__current( if (ti->entry.path == NULL) return -1; - if (ti->base.end && git__prefixcmp(ti->entry.path, ti->base.end) > 0) + if (ti->base.end && ti->base.prefixcomp(ti->entry.path, ti->base.end) > 0) return tree_iterator__to_end(ti); if (entry) @@ -209,10 +250,50 @@ static int tree_iterator__at_end(git_iterator *self) return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); } -static tree_iterator_frame *tree_iterator__alloc_frame( - git_tree *tree, char *start) +static int tree_iterator__icase_map_cmp(const void *a, const void *b, void *data) { - tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame)); + git_tree *tree = data; + const git_tree_entry *te1 = git_tree_entry_byindex(tree, (size_t)a); + const git_tree_entry *te2 = git_tree_entry_byindex(tree, (size_t)b); + return te1 ? (te2 ? git_tree_entry_icmp(te1, te2) : 1) : -1; +} + +static int tree_iterator__frame_start_icmp(const void *key, const void *element) +{ + const tree_iterator_frame *tf = (const tree_iterator_frame *)key; + const git_tree_entry *te = git_tree_entry_byindex(tf->tree, (size_t)element); + + return memcmp(tf->start, te->filename, min(tf->startlen, te->filename_len)); +} + +static void tree_iterator__frame_seek_start(tree_iterator_frame *tf) +{ + if (!tf->start) + tf->index = 0; + else if (!tf->icase_map) + tf->index = git_tree__prefix_position(tf->tree, tf->start); + else { + if (!git__bsearch( + tf->icase_map, git_tree_entrycount(tf->tree), + tf, tree_iterator__frame_start_icmp, &tf->index)) + { + while (tf->index > 0) { + /* move back while previous entry is still prefixed */ + if (tree_iterator__frame_start_icmp( + tf, (const void *)(tf->index - 1))) + break; + tf->index--; + } + } + } +} + +static tree_iterator_frame *tree_iterator__alloc_frame( + tree_iterator *ti, git_tree *tree, char *start) +{ + size_t i, max_i = git_tree_entrycount(tree); + tree_iterator_frame *tf = + git__calloc(1, sizeof(tree_iterator_frame) + max_i * sizeof(void *)); if (!tf) return NULL; @@ -220,9 +301,24 @@ static tree_iterator_frame *tree_iterator__alloc_frame( if (start && *start) { tf->start = start; - tf->index = git_tree__prefix_position(tree, start); + tf->startlen = strlen(start); } + if (!max_i) + return tf; + + if ((ti->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0) { + tf->icase_map = tf->icase_data; + + for (i = 0; i < max_i; ++i) + tf->icase_map[i] = (void *)i; + + git__tsort_r( + tf->icase_map, max_i, tree_iterator__icase_map_cmp, tf->tree); + } + + tree_iterator__frame_seek_start(tf); + return tf; } @@ -240,7 +336,7 @@ static int tree_iterator__expand_tree(tree_iterator *ti) /* check that we have not passed the range end */ if (ti->base.end != NULL && - git__prefixcmp(ti->path.ptr, ti->base.end) > 0) + ti->base.prefixcomp(ti->path.ptr, ti->base.end) > 0) return tree_iterator__to_end(ti); if ((error = git_tree_lookup(&subtree, ti->base.repo, &te->oid)) < 0) @@ -250,14 +346,13 @@ static int tree_iterator__expand_tree(tree_iterator *ti) /* apply range start to new frame if relevant */ if (ti->stack->start && - git__prefixcmp(ti->stack->start, te->filename) == 0) + ti->base.prefixcomp(ti->stack->start, te->filename) == 0) { - size_t namelen = strlen(te->filename); - if (ti->stack->start[namelen] == '/') - relpath = ti->stack->start + namelen + 1; + if (ti->stack->start[te->filename_len] == '/') + relpath = ti->stack->start + te->filename_len + 1; } - if ((tf = tree_iterator__alloc_frame(subtree, relpath)) == NULL) + if ((tf = tree_iterator__alloc_frame(ti, subtree, relpath)) == NULL) return -1; tf->next = ti->stack; @@ -286,8 +381,9 @@ static int tree_iterator__advance( } while (1) { - te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); - if (te != NULL) + ++ti->stack->index; + + if ((te = tree_iterator__tree_entry(ti)) != NULL) break; if (!tree_iterator__pop_frame(ti)) @@ -337,8 +433,8 @@ static int tree_iterator__reset( if (iterator__reset_range(self, start, end) < 0) return -1; - ti->stack->index = - git_tree__prefix_position(ti->stack->tree, ti->base.start); + /* reset start position */ + tree_iterator__frame_seek_start(ti->stack); git_buf_clear(&ti->path); ti->path_has_filename = false; @@ -349,6 +445,7 @@ static int tree_iterator__reset( int git_iterator_for_tree_range( git_iterator **iter, git_tree *tree, + git_iterator_flag_t flags, const char *start, const char *end) { @@ -356,7 +453,7 @@ int git_iterator_for_tree_range( tree_iterator *ti; if (tree == NULL) - return git_iterator_for_nothing(iter); + return git_iterator_for_nothing(iter, flags); if ((error = git_tree__dup(&tree, tree)) < 0) return error; @@ -364,13 +461,20 @@ int git_iterator_for_tree_range( ITERATOR_BASE_INIT(ti, tree, TREE); ti->base.repo = git_tree_owner(tree); - ti->stack = ti->tail = tree_iterator__alloc_frame(tree, ti->base.start); + + if ((error = iterator_update_ignore_case((git_iterator *)ti, flags)) < 0) + goto fail; + + ti->stack = ti->tail = tree_iterator__alloc_frame(ti, tree, ti->base.start); if ((error = tree_iterator__expand_tree(ti)) < 0) - git_iterator_free((git_iterator *)ti); - else - *iter = (git_iterator *)ti; + goto fail; + *iter = (git_iterator *)ti; + return 0; + +fail: + git_iterator_free((git_iterator *)ti); return error; } @@ -411,7 +515,7 @@ static void index_iterator__skip_conflicts( if (ie == NULL || (ii->base.end != NULL && - ITERATOR_PREFIXCMP(ii->base, ie->path, ii->base.end) > 0)) { + ii->base.prefixcomp(ie->path, ii->base.end) > 0)) { ii->current = entrycount; break; } @@ -466,15 +570,21 @@ static void index_iterator__free(git_iterator *self) int git_iterator_for_index_range( git_iterator **iter, git_index *index, + git_iterator_flag_t flags, const char *start, const char *end) { index_iterator *ii; + GIT_UNUSED(flags); + ITERATOR_BASE_INIT(ii, index, INDEX); ii->base.repo = git_index_owner(index); - ii->base.ignore_case = index->ignore_case; + if (index->ignore_case) { + ii->base.flags |= GIT_ITERATOR_IGNORE_CASE; + ii->base.prefixcomp = git__prefixcmp_icase; + } ii->index = index; GIT_REFCOUNT_INC(index); @@ -485,20 +595,6 @@ int git_iterator_for_index_range( return 0; } -int git_iterator_for_repo_index_range( - git_iterator **iter, - git_repository *repo, - const char *start, - const char *end) -{ - int error; - git_index *index; - - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - return error; - - return git_iterator_for_index_range(iter, index, start, end); -} typedef struct workdir_iterator_frame workdir_iterator_frame; struct workdir_iterator_frame { @@ -544,7 +640,8 @@ 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_vector_cmp entry_compare = CASESELECT( + (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0, git_path_with_stat_cmp_icase, git_path_with_stat_cmp); if (wf == NULL) @@ -606,7 +703,8 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) GITERR_CHECK_ALLOC(wf); error = git_path_dirload_with_stat( - wi->path.ptr, wi->root_len, wi->base.ignore_case, + wi->path.ptr, wi->root_len, + (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0, wi->base.start, wi->base.end, &wf->entries); if (error < 0 || wf->entries.length == 0) { @@ -744,8 +842,8 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0) return -1; - if (wi->base.end && ITERATOR_PREFIXCMP( - wi->base, wi->path.ptr + wi->root_len, wi->base.end) > 0) + if (wi->base.end && + wi->base.prefixcomp(wi->path.ptr + wi->root_len, wi->base.end) > 0) return 0; wi->entry.path = ps->path; @@ -789,12 +887,12 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) int git_iterator_for_workdir_range( git_iterator **iter, git_repository *repo, + git_iterator_flag_t flags, const char *start, const char *end) { int error; workdir_iterator *wi; - git_index *index; assert(iter && repo); @@ -805,13 +903,8 @@ int git_iterator_for_workdir_range( ITERATOR_BASE_INIT(wi, workdir, WORKDIR); wi->base.repo = repo; - if ((error = git_repository_index__weakptr(&index, repo)) < 0) { - git_iterator_free((git_iterator *)wi); - return error; - } - - /* Match ignore_case flag for iterator to that of the index */ - wi->base.ignore_case = index->ignore_case; + if ((error = iterator_update_ignore_case((git_iterator *)wi, flags)) < 0) + goto fail; if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 || git_path_to_dir(&wi->path) < 0 || @@ -822,23 +915,24 @@ int git_iterator_for_workdir_range( } wi->root_len = wi->path.size; - wi->entrycmp = wi->base.ignore_case ? + wi->entrycmp = (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? workdir_iterator__entry_cmp_icase : workdir_iterator__entry_cmp_case; if ((error = workdir_iterator__expand_dir(wi)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - else { - git_iterator_free((git_iterator *)wi); - wi = NULL; - } + if (error != GIT_ENOTFOUND) + goto fail; + giterr_clear(); } *iter = (git_iterator *)wi; + return 0; +fail: + git_iterator_free((git_iterator *)wi); return error; } + typedef struct { /* replacement callbacks */ git_iterator_callbacks cb; @@ -913,12 +1007,12 @@ void git_iterator_spoolandsort_pop(git_iterator *self) { spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - if (self->type != GIT_ITERATOR_SPOOLANDSORT) + if (self->type != GIT_ITERATOR_TYPE_SPOOLANDSORT) return; self->cb = scb->orig; self->type = scb->orig_type; - self->ignore_case = !self->ignore_case; + self->flags ^= GIT_ITERATOR_IGNORE_CASE; spoolandsort_iterator__free_callbacks(scb); } @@ -935,9 +1029,14 @@ int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case) spoolandsort_callbacks *scb; int (*entrycomp)(const void *a, const void *b); - if (iter->ignore_case == ignore_case) + if (((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) == (ignore_case != 0)) return 0; + if (iter->type == GIT_ITERATOR_TYPE_EMPTY) { + iter->flags = (iter->flags ^ GIT_ITERATOR_IGNORE_CASE); + return 0; + } + scb = git__calloc(1, sizeof(spoolandsort_callbacks)); GITERR_CHECK_ALLOC(scb); @@ -978,8 +1077,8 @@ int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case) git_vector_sort(&scb->entries); iter->cb = (git_iterator_callbacks *)scb; - iter->type = GIT_ITERATOR_SPOOLANDSORT; - iter->ignore_case = !iter->ignore_case; + iter->type = GIT_ITERATOR_TYPE_SPOOLANDSORT; + iter->flags ^= GIT_ITERATOR_IGNORE_CASE; return 0; @@ -988,13 +1087,29 @@ fail: return -1; } + +void git_iterator_free(git_iterator *iter) +{ + if (iter == NULL) + return; + + iter->cb->free(iter); + + git__free(iter->start); + git__free(iter->end); + + memset(iter, 0, sizeof(*iter)); + + git__free(iter); +} + git_index *git_iterator_index_get_index(git_iterator *iter) { - if (iter->type == GIT_ITERATOR_INDEX) + if (iter->type == GIT_ITERATOR_TYPE_INDEX) return ((index_iterator *)iter)->index; - if (iter->type == GIT_ITERATOR_SPOOLANDSORT && - ((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_INDEX) + if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT && + ((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_TYPE_INDEX) return ((index_iterator *)iter)->index; return NULL; @@ -1002,7 +1117,7 @@ git_index *git_iterator_index_get_index(git_iterator *iter) git_iterator_type_t git_iterator_inner_type(git_iterator *iter) { - if (iter->type == GIT_ITERATOR_SPOOLANDSORT) + if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT) return ((spoolandsort_callbacks *)iter->cb)->orig_type; return iter->type; @@ -1011,7 +1126,7 @@ git_iterator_type_t git_iterator_inner_type(git_iterator *iter) int git_iterator_current_tree_entry( git_iterator *iter, const git_tree_entry **tree_entry) { - *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL : + *tree_entry = (iter->type != GIT_ITERATOR_TYPE_TREE) ? NULL : tree_iterator__tree_entry((tree_iterator *)iter); return 0; } @@ -1024,10 +1139,14 @@ int git_iterator_current_parent_tree( tree_iterator *ti = (tree_iterator *)iter; tree_iterator_frame *tf; const char *scan = parent_path; + int (*strncomp)(const char *a, const char *b, size_t sz); - if (iter->type != GIT_ITERATOR_TREE || ti->stack == NULL) + if (iter->type != GIT_ITERATOR_TYPE_TREE || ti->stack == NULL) goto notfound; + strncomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ? + git__strncasecmp : git__strncmp; + for (tf = ti->tail; tf != NULL; tf = tf->prev) { const git_tree_entry *te; @@ -1036,9 +1155,10 @@ int git_iterator_current_parent_tree( return 0; } - te = git_tree_entry_byindex(tf->tree, tf->index); + te = git_tree_entry_byindex(tf->tree, + tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index); - if (strncmp(scan, te->filename, te->filename_len) != 0) + if (strncomp(scan, te->filename, te->filename_len) != 0) goto notfound; scan += te->filename_len; @@ -1059,7 +1179,7 @@ int git_iterator_current_is_ignored(git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; - if (iter->type != GIT_ITERATOR_WORKDIR) + if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) return 0; if (wi->is_ignored != -1) @@ -1076,7 +1196,7 @@ int git_iterator_advance_into_directory( { workdir_iterator *wi = (workdir_iterator *)iter; - if (iter->type == GIT_ITERATOR_WORKDIR && + if (iter->type == GIT_ITERATOR_TYPE_WORKDIR && wi->entry.path && (wi->entry.mode == GIT_FILEMODE_TREE || wi->entry.mode == GIT_FILEMODE_COMMIT)) @@ -1089,8 +1209,7 @@ int git_iterator_advance_into_directory( return entry ? git_iterator_current(iter, entry) : 0; } -int git_iterator_cmp( - git_iterator *iter, const char *path_prefix) +int git_iterator_cmp(git_iterator *iter, const char *path_prefix) { const git_index_entry *entry; @@ -1103,14 +1222,14 @@ int git_iterator_cmp( if (!path_prefix) return -1; - return ITERATOR_PREFIXCMP(*iter, entry->path, path_prefix); + return iter->prefixcomp(entry->path, path_prefix); } int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path) { workdir_iterator *wi = (workdir_iterator *)iter; - if (iter->type != GIT_ITERATOR_WORKDIR || !wi->entry.path) + if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->entry.path) *path = NULL; else *path = &wi->path; diff --git a/src/iterator.h b/src/iterator.h index 8a4356e3e..a9bccfca8 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -12,20 +12,26 @@ #include "vector.h" #include "buffer.h" -#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX) (((ITER).ignore_case) ? \ +#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX) \ + (((ITER).flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? \ git__prefixcmp_icase((STR), (PREFIX)) : \ git__prefixcmp((STR), (PREFIX))) typedef struct git_iterator git_iterator; typedef enum { - GIT_ITERATOR_EMPTY = 0, - GIT_ITERATOR_TREE = 1, - GIT_ITERATOR_INDEX = 2, - GIT_ITERATOR_WORKDIR = 3, - GIT_ITERATOR_SPOOLANDSORT = 4 + GIT_ITERATOR_TYPE_EMPTY = 0, + GIT_ITERATOR_TYPE_TREE = 1, + GIT_ITERATOR_TYPE_INDEX = 2, + GIT_ITERATOR_TYPE_WORKDIR = 3, + GIT_ITERATOR_TYPE_SPOOLANDSORT = 4 } git_iterator_type_t; +typedef enum { + GIT_ITERATOR_IGNORE_CASE = (1 << 0), /* ignore_case */ + GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), /* force ignore_case off */ +} git_iterator_flag_t; + typedef struct { int (*current)(git_iterator *, const git_index_entry **); int (*at_end)(git_iterator *); @@ -41,50 +47,60 @@ struct git_iterator { git_repository *repo; char *start; char *end; - bool ignore_case; + int (*prefixcomp)(const char *str, const char *prefix); + unsigned int flags; }; -extern int git_iterator_for_nothing(git_iterator **iter); +extern int git_iterator_for_nothing( + git_iterator **out, git_iterator_flag_t flags); +/* tree iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ extern int git_iterator_for_tree_range( - git_iterator **iter, git_tree *tree, - const char *start, const char *end); + git_iterator **out, + git_tree *tree, + git_iterator_flag_t flags, + const char *start, + const char *end); -GIT_INLINE(int) git_iterator_for_tree( - git_iterator **iter, git_tree *tree) +GIT_INLINE(int) git_iterator_for_tree(git_iterator **out, git_tree *tree) { - return git_iterator_for_tree_range(iter, tree, NULL, NULL); + return git_iterator_for_tree_range(out, tree, 0, NULL, NULL); } +/* index iterators will take the ignore_case value from the index; the + * ignore_case flags are not used + */ extern int git_iterator_for_index_range( - git_iterator **iter, git_index *index, const char *start, const char *end); + git_iterator **out, + git_index *index, + git_iterator_flag_t flags, + const char *start, + const char *end); -GIT_INLINE(int) git_iterator_for_index( - git_iterator **iter, git_index *index) +GIT_INLINE(int) git_iterator_for_index(git_iterator **out, git_index *index) { - return git_iterator_for_index_range(iter, index, NULL, NULL); -} - -extern int git_iterator_for_repo_index_range( - git_iterator **iter, git_repository *repo, - const char *start, const char *end); - -GIT_INLINE(int) git_iterator_for_repo_index( - git_iterator **iter, git_repository *repo) -{ - return git_iterator_for_repo_index_range(iter, repo, NULL, NULL); + return git_iterator_for_index_range(out, index, 0, NULL, NULL); } +/* workdir iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ extern int git_iterator_for_workdir_range( - git_iterator **iter, git_repository *repo, - const char *start, const char *end); + git_iterator **out, + git_repository *repo, + git_iterator_flag_t flags, + const char *start, + const char *end); -GIT_INLINE(int) git_iterator_for_workdir( - git_iterator **iter, git_repository *repo) +GIT_INLINE(int) git_iterator_for_workdir(git_iterator **out, git_repository *repo) { - return git_iterator_for_workdir_range(iter, repo, NULL, NULL); + return git_iterator_for_workdir_range(out, repo, 0, NULL, NULL); } +extern void git_iterator_free(git_iterator *iter); + /* Spool all iterator values, resort with alternative ignore_case value * and replace callbacks with spoolandsort alternates. */ @@ -130,21 +146,6 @@ GIT_INLINE(int) git_iterator_reset( return iter->cb->reset(iter, start, end); } -GIT_INLINE(void) git_iterator_free(git_iterator *iter) -{ - if (iter == NULL) - return; - - iter->cb->free(iter); - - git__free(iter->start); - git__free(iter->end); - - memset(iter, 0, sizeof(*iter)); - - git__free(iter); -} - GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) { return iter->type; @@ -155,6 +156,16 @@ GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) return iter->repo; } +GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) +{ + return iter->flags; +} + +GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) +{ + return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); +} + extern int git_iterator_current_tree_entry( git_iterator *iter, const git_tree_entry **tree_entry); diff --git a/src/path.c b/src/path.c index 0fd367eaf..5de58cce7 100644 --- a/src/path.c +++ b/src/path.c @@ -701,6 +701,30 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +int git_path_icmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2) +{ + unsigned char c1, c2; + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = strncasecmp(name1, name2, len); + if (cmp) + return cmp; + + c1 = name1[len]; + c2 = name2[len]; + + if (c1 == '\0' && isdir1) + c1 = '/'; + + if (c2 == '\0' && isdir2) + c2 = '/'; + + return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; +} + int git_path_direach( git_buf *path, int (*fn)(void *, git_buf *), diff --git a/src/path.h b/src/path.h index de0a40b53..feefd65d1 100644 --- a/src/path.h +++ b/src/path.h @@ -261,12 +261,17 @@ extern int git_path_direach( void *state); /** - * Sort function to order two paths. + * Sort function to order two paths */ extern int git_path_cmp( const char *name1, size_t len1, int isdir1, const char *name2, size_t len2, int isdir2); +/** Path sort function that is case insensitive */ +extern int git_path_icmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2); + /** * Invoke callback up path directory by directory until the ceiling is * reached (inclusive of a final call at the root_path). diff --git a/src/status.c b/src/status.c index 777d8502b..282cb396b 100644 --- a/src/status.c +++ b/src/status.c @@ -196,21 +196,24 @@ struct status_file_info { char *expected; unsigned int count; unsigned int status; + int fnm_flags; int ambiguous; }; static int get_one_status(const char *path, unsigned int status, void *data) { struct status_file_info *sfi = data; + int (*strcomp)(const char *a, const char *b); sfi->count++; sfi->status = status; + strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp; + if (sfi->count > 1 || - (strcmp(sfi->expected, path) != 0 && - p_fnmatch(sfi->expected, path, 0) != 0)) { - giterr_set(GITERR_INVALID, - "Ambiguous path '%s' given to git_status_file", sfi->expected); + (strcomp(sfi->expected, path) != 0 && + p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0)) + { sfi->ambiguous = true; return GIT_EAMBIGUOUS; } @@ -226,11 +229,17 @@ int git_status_file( int error; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_file_info sfi = {0}; + git_index *index; assert(status_flags && repo && path); + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + return error; + if ((sfi.expected = git__strdup(path)) == NULL) return -1; + if (index->ignore_case) + sfi.fnm_flags = FNM_CASEFOLD; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | @@ -242,8 +251,11 @@ int git_status_file( error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); - if (error < 0 && sfi.ambiguous) + if (error < 0 && sfi.ambiguous) { + giterr_set(GITERR_INVALID, + "Ambiguous path '%s' given to git_status_file", sfi.expected); error = GIT_EAMBIGUOUS; + } if (!error && !sfi.count) { git_buf full = GIT_BUF_INIT; diff --git a/src/submodule.c b/src/submodule.c index a72326602..5283322f2 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1130,10 +1130,12 @@ static int load_submodule_config_from_index( git_repository *repo, git_oid *gitmodules_oid) { int error; + git_index *index; git_iterator *i; const git_index_entry *entry; - if ((error = git_iterator_for_repo_index(&i, repo)) < 0) + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_iterator_for_index(&i, index)) < 0) return error; error = git_iterator_current(i, &entry); diff --git a/src/tree.c b/src/tree.c index cd1cd6076..c34e9b940 100644 --- a/src/tree.c +++ b/src/tree.c @@ -55,14 +55,23 @@ static int valid_entry_name(const char *filename) strcmp(filename, DOT_GIT) != 0)); } +int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) +{ + return git_path_cmp( + e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2)); +} + +int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2) +{ + return git_path_icmp( + e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2)); +} + static int entry_sort_cmp(const void *a, const void *b) { - const git_tree_entry *entry_a = (const git_tree_entry *)(a); - const git_tree_entry *entry_b = (const git_tree_entry *)(b); - - return git_path_cmp( - entry_a->filename, entry_a->filename_len, git_tree_entry__is_tree(entry_a), - entry_b->filename, entry_b->filename_len, git_tree_entry__is_tree(entry_b)); + return git_tree_entry_cmp((const git_tree_entry *)a, (const git_tree_entry *)b); } static git_tree_entry *alloc_entry(const char *filename) diff --git a/src/tree.h b/src/tree.h index 6f05f5a7a..27afd4fd4 100644 --- a/src/tree.h +++ b/src/tree.h @@ -39,6 +39,8 @@ GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); } +extern int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2); + void git_tree__free(git_tree *tree); int git_tree__parse(git_tree *tree, git_odb_object *obj); diff --git a/src/tsort.c b/src/tsort.c index 634fe2672..97473be91 100644 --- a/src/tsort.c +++ b/src/tsort.c @@ -23,9 +23,8 @@ # define MIN(x,y) (((x) < (y) ? (x) : (y))) #endif -typedef int (*cmp_ptr_t)(const void *, const void *); - -static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) +static int binsearch( + void **dst, const void *x, size_t size, git__tsort_r_cmp cmp, void *payload) { int l, c, r; void *lx, *cx; @@ -38,12 +37,12 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) lx = dst[l]; /* check for beginning conditions */ - if (cmp(x, lx) < 0) + if (cmp(x, lx, payload) < 0) return 0; - else if (cmp(x, lx) == 0) { + else if (cmp(x, lx, payload) == 0) { int i = 1; - while (cmp(x, dst[i]) == 0) + while (cmp(x, dst[i], payload) == 0) i++; return i; } @@ -51,7 +50,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) /* guaranteed not to be >= rx */ cx = dst[c]; while (1) { - const int val = cmp(x, cx); + const int val = cmp(x, cx, payload); if (val < 0) { if (c - l <= 1) return c; r = c; @@ -62,7 +61,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) } else { do { cx = dst[++c]; - } while (cmp(x, cx) == 0); + } while (cmp(x, cx, payload) == 0); return c; } c = l + ((r - l) >> 1); @@ -71,7 +70,8 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) } /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ -static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp) +static void bisort( + void **dst, size_t start, size_t size, git__tsort_r_cmp cmp, void *payload) { size_t i; void *x; @@ -80,12 +80,12 @@ static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp) for (i = start; i < size; i++) { int j; /* If this entry is already correct, just move along */ - if (cmp(dst[i - 1], dst[i]) <= 0) + if (cmp(dst[i - 1], dst[i], payload) <= 0) continue; /* Else we need to find the right place, shift everything over, and squeeze in */ x = dst[i]; - location = binsearch(dst, x, i, cmp); + location = binsearch(dst, x, i, cmp, payload); for (j = (int)i - 1; j >= location; j--) { dst[j + 1] = dst[j]; } @@ -102,7 +102,8 @@ struct tsort_run { struct tsort_store { size_t alloc; - cmp_ptr_t cmp; + git__tsort_r_cmp cmp; + void *payload; void **storage; }; @@ -118,7 +119,8 @@ static void reverse_elements(void **dst, ssize_t start, ssize_t end) } } -static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +static ssize_t count_run( + void **dst, ssize_t start, ssize_t size, struct tsort_store *store) { ssize_t curr = start + 2; @@ -126,7 +128,7 @@ static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_s return 1; if (start >= size - 2) { - if (store->cmp(dst[size - 2], dst[size - 1]) > 0) { + if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { void *tmp = dst[size - 1]; dst[size - 1] = dst[size - 2]; dst[size - 2] = tmp; @@ -135,13 +137,15 @@ static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_s return 2; } - if (store->cmp(dst[start], dst[start + 1]) <= 0) { - while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0) + if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) curr++; return curr - start; } else { - while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0) + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) curr++; /* reverse in-place */ @@ -219,7 +223,7 @@ static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, for (k = curr; k < curr + A + B; k++) { if ((i < A) && (j < curr + A + B)) { - if (store->cmp(storage[i], dst[j]) <= 0) + if (store->cmp(storage[i], dst[j], store->payload) <= 0) dst[k] = storage[i++]; else dst[k] = dst[j++]; @@ -235,7 +239,7 @@ static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, for (k = curr + A + B - 1; k >= curr; k--) { if ((i >= 0) && (j >= curr)) { - if (store->cmp(dst[j], storage[i]) > 0) + if (store->cmp(dst[j], storage[i], store->payload) > 0) dst[k] = dst[j--]; else dst[k] = storage[i--]; @@ -307,7 +311,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, if (run < minrun) run = minrun;\ if (run > (ssize_t)size - curr) run = size - curr;\ if (run > len) {\ - bisort(&dst[curr], len, run, cmp);\ + bisort(&dst[curr], len, run, cmp, payload);\ len = run;\ }\ run_stack[stack_curr].start = curr;\ @@ -329,7 +333,8 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, }\ while (0) -void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) +void git__tsort_r( + void **dst, size_t size, git__tsort_r_cmp cmp, void *payload) { struct tsort_store _store, *store = &_store; struct tsort_run run_stack[128]; @@ -340,7 +345,7 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) ssize_t minrun; if (size < 64) { - bisort(dst, 1, size, cmp); + bisort(dst, 1, size, cmp, payload); return; } @@ -351,6 +356,7 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) store->alloc = 0; store->storage = NULL; store->cmp = cmp; + store->payload = payload; PUSH_NEXT(); PUSH_NEXT(); @@ -365,3 +371,13 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) PUSH_NEXT(); } } + +static int tsort_r_cmp(const void *a, const void *b, void *payload) +{ + return ((git__tsort_cmp)payload)(a, b); +} + +void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) +{ + git__tsort_r(dst, size, tsort_r_cmp, cmp); +} diff --git a/src/util.c b/src/util.c index 51173fa70..30c4dc6ce 100644 --- a/src/util.c +++ b/src/util.c @@ -462,7 +462,7 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. - */ + */ int git__bsearch( void **array, size_t array_len, @@ -493,6 +493,37 @@ int git__bsearch( return (cmp == 0) ? 0 : -1; } +int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *, const void *, void *), + void *payload, + size_t *position) +{ + unsigned int lim; + int cmp = -1; + void **part, **base = array; + + for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare_r)(key, *part, payload); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : -1; +} + /** * A strcmp wrapper * diff --git a/src/util.h b/src/util.h index ee0d0e3ed..9bcd3203e 100644 --- a/src/util.h +++ b/src/util.h @@ -119,7 +119,15 @@ GIT_INLINE(const char *) git__next_line(const char *s) return s; } -extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *)); +typedef int (*git__tsort_cmp)(const void *a, const void *b); + +extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); + +typedef int (*git__tsort_r_cmp)(const void *a, const void *b, void *payload); + +extern void git__tsort_r( + void **dst, size_t size, git__tsort_r_cmp cmp, void *payload); + /** * @param position If non-NULL, this will be set to the position where the @@ -130,7 +138,15 @@ extern int git__bsearch( void **array, size_t array_len, const void *key, - int (*compare)(const void *, const void *), + int (*compare)(const void *key, const void *element), + size_t *position); + +extern int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *key, const void *element, void *payload), + void *payload, size_t *position); extern int git__strcmp_cb(const void *a, const void *b); diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index b5790632d..566503b6e 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -35,7 +35,8 @@ static void tree_iterator_test( git_repository *repo = cl_git_sandbox_init(sandbox); cl_assert(t = resolve_commit_oid_to_tree(repo, treeish)); - cl_git_pass(git_iterator_for_tree_range(&i, t, start, end)); + cl_git_pass(git_iterator_for_tree_range( + &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, start, end)); /* test loop */ cl_git_pass(git_iterator_current(i, &entry)); @@ -304,7 +305,8 @@ void test_diff_iterator__tree_special_functions(void) repo, "24fa9a9fc4e202313e24b648087495441dab432b"); cl_assert(t != NULL); - cl_git_pass(git_iterator_for_tree_range(&i, t, NULL, NULL)); + cl_git_pass(git_iterator_for_tree_range( + &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -355,12 +357,14 @@ static void index_iterator_test( const char **expected_names, const char **expected_oids) { + git_index *index; git_iterator *i; const git_index_entry *entry; int count = 0; git_repository *repo = cl_git_sandbox_init(sandbox); - cl_git_pass(git_iterator_for_repo_index_range(&i, repo, start, end)); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_iterator_for_index_range(&i, index, 0, start, end)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -378,6 +382,7 @@ static void index_iterator_test( } git_iterator_free(i); + git_index_free(index); cl_assert_equal_i(expected_count, count); } @@ -533,7 +538,7 @@ static void workdir_iterator_test( int count = 0, count_all = 0, count_all_post_reset = 0; git_repository *repo = cl_git_sandbox_init(sandbox); - cl_git_pass(git_iterator_for_workdir_range(&i, repo, start, end)); + cl_git_pass(git_iterator_for_workdir_range(&i, repo, 0, start, end)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -731,7 +736,7 @@ void test_diff_iterator__workdir_builtin_ignores(void) cl_git_mkfile("attr/sub/.git", "whatever"); cl_git_pass( - git_iterator_for_workdir_range(&i, repo, "dir", "sub/sub/file")); + git_iterator_for_workdir_range(&i, repo, 0, "dir", "sub/sub/file")); cl_git_pass(git_iterator_current(i, &entry)); for (idx = 0; entry != NULL; ++idx) { @@ -750,3 +755,155 @@ void test_diff_iterator__workdir_builtin_ignores(void) git_iterator_free(i); } + +static void check_wd_first_through_third_range( + git_repository *repo, const char *start, const char *end) +{ + git_iterator *i; + const git_index_entry *entry; + int idx; + static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; + + cl_git_pass(git_iterator_for_workdir_range( + &i, repo, GIT_ITERATOR_IGNORE_CASE, start, end)); + cl_git_pass(git_iterator_current(i, &entry)); + + for (idx = 0; entry != NULL; ++idx) { + cl_assert_equal_s(expected[idx], entry->path); + + if (S_ISDIR(entry->mode)) + cl_git_pass(git_iterator_advance_into_directory(i, &entry)); + else + cl_git_pass(git_iterator_advance(i, &entry)); + } + + cl_assert(expected[idx] == NULL); + + git_iterator_free(i); +} + +void test_diff_iterator__workdir_handles_icase_range(void) +{ + git_repository *repo; + + repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); + + cl_git_mkfile("empty_standard_repo/before", "whatever\n"); + cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n"); + cl_git_mkfile("empty_standard_repo/second", "whatever\n"); + cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n"); + cl_git_mkfile("empty_standard_repo/zafter", "whatever\n"); + cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n"); + + check_wd_first_through_third_range(repo, "first", "third"); + check_wd_first_through_third_range(repo, "FIRST", "THIRD"); + check_wd_first_through_third_range(repo, "first", "THIRD"); + check_wd_first_through_third_range(repo, "FIRST", "third"); + check_wd_first_through_third_range(repo, "FirSt", "tHiRd"); +} + +static void check_tree_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_tree *head; + git_iterator *i; + const git_index_entry *entry; + int count; + + cl_git_pass(git_repository_head_tree(&head, repo)); + + cl_git_pass(git_iterator_for_tree_range( + &i, head, + ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE, + start, end)); + + cl_git_pass(git_iterator_current(i, &entry)); + + for (count = 0; entry != NULL; ) { + ++count; + cl_git_pass(git_iterator_advance(i, &entry)); + } + + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_tree_free(head); +} + +void test_diff_iterator__tree_handles_icase_range(void) +{ + git_repository *repo; + + repo = cl_git_sandbox_init("testrepo"); + + check_tree_range(repo, "B", "C", false, 0); + check_tree_range(repo, "B", "C", true, 1); + check_tree_range(repo, "a", "z", false, 3); + check_tree_range(repo, "a", "z", true, 4); +} + +static void check_index_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_index *index; + git_iterator *i; + const git_index_entry *entry; + int count, caps; + bool is_ignoring_case; + + cl_git_pass(git_repository_index(&index, repo)); + + caps = git_index_caps(index); + is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0); + + if (ignore_case != is_ignoring_case) + cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE)); + + cl_git_pass(git_iterator_for_index_range(&i, index, 0, start, end)); + + cl_assert(git_iterator_ignore_case(i) == ignore_case); + + cl_git_pass(git_iterator_current(i, &entry)); + + for (count = 0; entry != NULL; ) { + ++count; + cl_git_pass(git_iterator_advance(i, &entry)); + } + + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_index_free(index); +} + +void test_diff_iterator__index_handles_icase_range(void) +{ + git_repository *repo; + git_index *index; + git_tree *head; + + repo = cl_git_sandbox_init("testrepo"); + + /* reset index to match HEAD */ + cl_git_pass(git_repository_head_tree(&head, repo)); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read_tree(index, head)); + cl_git_pass(git_index_write(index)); + git_tree_free(head); + git_index_free(index); + + /* do some ranged iterator checks toggling case sensitivity */ + check_index_range(repo, "B", "C", false, 0); + check_index_range(repo, "B", "C", true, 1); + check_index_range(repo, "a", "z", false, 3); + check_index_range(repo, "a", "z", true, 4); +} diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 6786b91ff..ead1bc734 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -580,3 +580,90 @@ void test_status_worktree__conflicted_item(void) git_index_free(index); } +static void stage_and_commit(git_repository *repo, const char *path) +{ + git_oid tree_oid, commit_oid; + git_tree *tree; + git_signature *signature; + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, path)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_write_tree(&tree_oid, index)); + git_index_free(index); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); + + cl_git_pass(git_commit_create_v( + &commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + "Initial commit\n\0", + tree, + 0)); + + git_tree_free(tree); + git_signature_free(signature); +} + +static void assert_ignore_case( + bool should_ignore_case, + int expected_lower_cased_file_status, + int expected_camel_cased_file_status) +{ + git_config *config; + unsigned int status; + git_buf lower_case_path = GIT_BUF_INIT, camel_case_path = GIT_BUF_INIT; + git_repository *repo, *repo2; + + repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.ignorecase", should_ignore_case)); + git_config_free(config); + + cl_git_pass(git_buf_joinpath(&lower_case_path, + git_repository_workdir(repo), "plop")); + + cl_git_mkfile(git_buf_cstr(&lower_case_path), ""); + + stage_and_commit(repo, "plop"); + + cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo")); + + cl_git_pass(git_status_file(&status, repo2, "plop")); + cl_assert_equal_i(GIT_STATUS_CURRENT, status); + + cl_git_pass(git_buf_joinpath(&camel_case_path, + git_repository_workdir(repo), "Plop")); + + cl_git_pass(p_rename(git_buf_cstr(&lower_case_path), git_buf_cstr(&camel_case_path))); + + cl_git_pass(git_status_file(&status, repo2, "plop")); + cl_assert_equal_i(expected_lower_cased_file_status, status); + + cl_git_pass(git_status_file(&status, repo2, "Plop")); + cl_assert_equal_i(expected_camel_cased_file_status, status); + + git_repository_free(repo2); + git_buf_free(&lower_case_path); + git_buf_free(&camel_case_path); +} + +void test_status_worktree__file_status_honors_core_ignorecase_true(void) +{ + assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT); +} + +void test_status_worktree__file_status_honors_core_ignorecase_false(void) +{ + assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW); +}