diff --git a/include/git2/tree.h b/include/git2/tree.h index 2d3534fab..b3c22e71d 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -80,6 +80,14 @@ GIT_INLINE(void) git_tree_free(git_tree *tree) */ GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree); +/** + * Get the repository that contains the tree. + * + * @param tree A previously loaded tree. + * @return Repository that contains this tree. + */ +GIT_EXTERN(git_repository *) git_tree_owner(const git_tree *tree); + /** * Get the number of entries listed in a tree * diff --git a/src/checkout.c b/src/checkout.c index 33de7adf3..d9f0f8fad 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -448,8 +448,7 @@ static int checkout_get_actions( !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) return -1; - if ((error = git_iterator_for_tree_range( - &hiter, data->repo, head, pfx, pfx)) < 0) + if ((error = git_iterator_for_tree_range(&hiter, head, pfx, pfx)) < 0) goto fail; if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && diff --git a/src/diff.c b/src/diff.c index c4bfc3687..822ae5b09 100644 --- a/src/diff.c +++ b/src/diff.c @@ -570,7 +570,7 @@ static int diff_list_init_from_iterators( return 0; } -static int diff_from_iterators( +int git_diff__from_iterators( git_diff_list **diff_ptr, git_repository *repo, git_iterator *old_iter, @@ -610,9 +610,10 @@ static int diff_from_iterators( /* run iterators building diffs */ while (oitem || nitem) { + int cmp = oitem ? (nitem ? diff->entrycomp(oitem, nitem) : -1) : 1; /* create DELETED records for old items not matched in new */ - if (oitem && (!nitem || diff->entrycomp(oitem, nitem) < 0)) { + if (cmp < 0) { if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0) goto fail; @@ -637,7 +638,7 @@ 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 || diff->entrycomp(oitem, nitem) > 0)) { + else if (cmp > 0) { git_delta_t delta_type = GIT_DELTA_UNTRACKED; /* check if contained in ignored parent directory */ @@ -733,7 +734,7 @@ static int diff_from_iterators( * (or ADDED and DELETED pair if type changed) */ else { - assert(oitem && nitem && diff->entrycomp(oitem, nitem) == 0); + assert(oitem && nitem && cmp == 0); if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || git_iterator_advance(old_iter, &oitem) < 0 || @@ -759,8 +760,8 @@ fail: git_iterator *a = NULL, *b = NULL; \ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ - error = diff_from_iterators(diff, repo, a, b, opts); \ + if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + error = git_diff__from_iterators(diff, repo, a, b, opts); \ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ } while (0) @@ -776,8 +777,8 @@ int git_diff_tree_to_tree( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), - git_iterator_for_tree_range(&b, repo, new_tree, pfx, pfx) + git_iterator_for_tree_range(&a, old_tree, pfx, pfx), + git_iterator_for_tree_range(&b, new_tree, pfx, pfx) ); return error; @@ -798,7 +799,7 @@ int git_diff_index_to_tree( return error; DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), + git_iterator_for_tree_range(&a, old_tree, pfx, pfx), git_iterator_for_index_range(&b, index, pfx, pfx) ); @@ -838,7 +839,7 @@ int git_diff_workdir_to_tree( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), + git_iterator_for_tree_range(&a, old_tree, pfx, pfx), git_iterator_for_workdir_range(&b, repo, pfx, pfx) ); diff --git a/src/diff.h b/src/diff.h index f93bab18d..8f5ea3485 100644 --- a/src/diff.h +++ b/src/diff.h @@ -61,6 +61,12 @@ extern bool git_diff_delta__should_skip( extern int git_diff__oid_for_file( git_repository *, const char *, uint16_t, git_off_t, git_oid *); +extern int git_diff__from_iterators( + git_diff_list **diff_ptr, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts); #endif diff --git a/src/index.c b/src/index.c index f3ced9e39..1e5b28002 100644 --- a/src/index.c +++ b/src/index.c @@ -1641,8 +1641,7 @@ int git_index_read_tree_match( pfx = git_pathspec_prefix(strspec); - if ((error = git_iterator_for_tree_range( - &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 || + if ((error = git_iterator_for_tree_range(&iter, tree, pfx, pfx)) < 0 || (error = git_iterator_current(iter, &entry)) < 0) goto cleanup; diff --git a/src/iterator.c b/src/iterator.c index 0fdf0c69d..706106703 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -18,7 +18,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.ignore_case = false; \ (P)->base.current = NAME_LC ## _iterator__current; \ (P)->base.at_end = NAME_LC ## _iterator__at_end; \ (P)->base.advance = NAME_LC ## _iterator__advance; \ @@ -29,6 +29,25 @@ return -1; \ } while (0) +static int iterator__reset_range( + git_iterator *iter, const char *start, const char *end) +{ + if (start) { + if (iter->start) + git__free(iter->start); + iter->start = git__strdup(start); + GITERR_CHECK_ALLOC(iter->start); + } + + if (end) { + if (iter->end) + git__free(iter->end); + iter->end = git__strdup(end); + GITERR_CHECK_ALLOC(iter->end); + } + + return 0; +} static int empty_iterator__no_item( git_iterator *iter, const git_index_entry **entry) @@ -44,16 +63,16 @@ static int empty_iterator__at_end(git_iterator *iter) return 1; } -static int empty_iterator__noop(git_iterator *iter) +static int empty_iterator__reset( + git_iterator *iter, const char *start, const char *end) { - GIT_UNUSED(iter); + GIT_UNUSED(iter); GIT_UNUSED(start); GIT_UNUSED(end); return 0; } static int empty_iterator__seek(git_iterator *iter, const char *prefix) { - GIT_UNUSED(iter); - GIT_UNUSED(prefix); + GIT_UNUSED(iter); GIT_UNUSED(prefix); return -1; } @@ -72,7 +91,7 @@ int git_iterator_for_nothing(git_iterator **iter) i->at_end = empty_iterator__at_end; i->advance = empty_iterator__no_item; i->seek = empty_iterator__seek; - i->reset = empty_iterator__noop; + i->reset = empty_iterator__reset; i->free = empty_iterator__free; *iter = i; @@ -91,17 +110,15 @@ struct tree_iterator_frame { typedef struct { git_iterator base; - git_repository *repo; tree_iterator_frame *stack, *tail; git_index_entry entry; git_buf path; bool path_has_filename; } tree_iterator; -static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) +GIT_INLINE(const git_tree_entry *)tree_iterator__tree_entry(tree_iterator *ti) { - return (ti->stack == NULL) ? NULL : - git_tree_entry_byindex(ti->stack->tree, ti->stack->index); + return git_tree_entry_byindex(ti->stack->tree, ti->stack->index); } static char *tree_iterator__current_filename( @@ -116,25 +133,34 @@ static char *tree_iterator__current_filename( return ti->path.ptr; } -static void tree_iterator__pop_frame(tree_iterator *ti) +static void tree_iterator__free_frame(tree_iterator_frame *tf) +{ + if (!tf) + return; + git_tree_free(tf->tree); + git__free(tf); +} + +static bool tree_iterator__pop_frame(tree_iterator *ti) { tree_iterator_frame *tf = ti->stack; + + /* don't free the initial tree/frame */ + if (!tf->next) + return false; + ti->stack = tf->next; - if (ti->stack != NULL) { - git_tree_free(tf->tree); /* don't free the initial tree */ - ti->stack->prev = NULL; /* disconnect prev */ - } - git__free(tf); + ti->stack->prev = NULL; + + tree_iterator__free_frame(tf); + + return true; } static int tree_iterator__to_end(tree_iterator *ti) { - while (ti->stack && ti->stack->next) - tree_iterator__pop_frame(ti); - - if (ti->stack) - ti->stack->index = git_tree_entrycount(ti->stack->tree); - + while (tree_iterator__pop_frame(ti)) /* pop all */; + ti->stack->index = git_tree_entrycount(ti->stack->tree); return 0; } @@ -205,7 +231,7 @@ static int tree_iterator__expand_tree(tree_iterator *ti) git__prefixcmp(ti->path.ptr, ti->base.end) > 0) return tree_iterator__to_end(ti); - if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0) + if ((error = git_tree_lookup(&subtree, ti->base.repo, &te->oid)) < 0) return error; relpath = NULL; @@ -247,12 +273,13 @@ static int tree_iterator__advance( ti->path_has_filename = false; } - while (ti->stack != NULL) { + while (1) { te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); if (te != NULL) break; - tree_iterator__pop_frame(ti); + if (!tree_iterator__pop_frame(ti)) + break; /* no frames left to pop */ git_buf_rtruncate_at_char(&ti->path, '/'); } @@ -279,30 +306,36 @@ static int tree_iterator__seek(git_iterator *self, const char *prefix) static void tree_iterator__free(git_iterator *self) { tree_iterator *ti = (tree_iterator *)self; - while (ti->stack != NULL) - tree_iterator__pop_frame(ti); + + while (tree_iterator__pop_frame(ti)) /* pop all */; + + tree_iterator__free_frame(ti->stack); + ti->stack = ti->tail = NULL; + git_buf_free(&ti->path); } -static int tree_iterator__reset(git_iterator *self) +static int tree_iterator__reset( + git_iterator *self, const char *start, const char *end) { tree_iterator *ti = (tree_iterator *)self; - while (ti->stack && ti->stack->next) - tree_iterator__pop_frame(ti); + while (tree_iterator__pop_frame(ti)) /* pop all */; - if (ti->stack) - ti->stack->index = - git_tree__prefix_position(ti->stack->tree, ti->base.start); + if (iterator__reset_range(self, start, end) < 0) + return -1; + + ti->stack->index = + git_tree__prefix_position(ti->stack->tree, ti->base.start); git_buf_clear(&ti->path); + ti->path_has_filename = false; return tree_iterator__expand_tree(ti); } int git_iterator_for_tree_range( git_iterator **iter, - git_repository *repo, git_tree *tree, const char *start, const char *end) @@ -313,9 +346,12 @@ int git_iterator_for_tree_range( if (tree == NULL) return git_iterator_for_nothing(iter); + if ((error = git_tree__dup(&tree, tree)) < 0) + return error; + ITERATOR_BASE_INIT(ti, tree, TREE); - ti->repo = repo; + ti->base.repo = git_tree_owner(tree); ti->stack = ti->tail = tree_iterator__alloc_frame(tree, ti->base.start); if ((error = tree_iterator__expand_tree(ti)) < 0) @@ -396,9 +432,12 @@ static int index_iterator__seek(git_iterator *self, const char *prefix) return -1; } -static int index_iterator__reset(git_iterator *self) +static int index_iterator__reset( + git_iterator *self, const char *start, const char *end) { index_iterator *ii = (index_iterator *)self; + if (iterator__reset_range(self, start, end) < 0) + return -1; ii->current = ii->base.start ? git_index__prefix_position(ii->index, ii->base.start) : 0; index_iterator__skip_conflicts(ii); @@ -424,9 +463,10 @@ int git_iterator_for_index_range( ITERATOR_BASE_INIT(ii, index, INDEX); ii->index = index; + ii->base.repo = git_index_owner(index); ii->base.ignore_case = ii->index->ignore_case; - index_iterator__reset((git_iterator *)ii); + index_iterator__reset((git_iterator *)ii, NULL, NULL); *iter = (git_iterator *)ii; @@ -456,36 +496,19 @@ struct workdir_iterator_frame { workdir_iterator_frame *next; git_vector entries; size_t index; - char *start; }; typedef struct { git_iterator base; - git_repository *repo; - size_t root_len; workdir_iterator_frame *stack; + int (*entrycmp)(const void *pfx, const void *item); git_ignores ignores; git_index_entry entry; git_buf path; + size_t root_len; int is_ignored; } workdir_iterator; -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); -} - GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps) { if (!ps) @@ -494,26 +517,34 @@ GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps) const char *path = ps->path; size_t len = ps->path_len; - return len >= 4 && - tolower(path[len - 1]) == 't' && - tolower(path[len - 2]) == 'i' && - tolower(path[len - 3]) == 'g' && - path[len - 4] == '.' && - (len == 4 || path[len - 5] == '/'); + if (len < 4) + return false; + if (path[len - 1] == '/') + len--; + if (tolower(path[len - 1]) != 't' || + tolower(path[len - 2]) != 'i' || + tolower(path[len - 3]) != 'g' || + tolower(path[len - 4]) != '.') + return false; + return (len == 4 || path[len - 5] == '/'); } } -static workdir_iterator_frame *workdir_iterator__alloc_frame(workdir_iterator *wi) +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); + git_vector_cmp entry_compare = CASESELECT(wi->base.ignore_case, + git_path_with_stat_cmp_icase, git_path_with_stat_cmp); if (wf == NULL) return NULL; + if (git_vector_init(&wf->entries, 0, entry_compare) != 0) { git__free(wf); return NULL; } + return wf; } @@ -530,16 +561,32 @@ 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_case(const void *prefix, const void *item) +static int workdir_iterator__entry_cmp_case(const void *pfx, const void *item) { const git_path_with_stat *ps = item; - return git__prefixcmp((const char *)prefix, ps->path); + return git__prefixcmp((const char *)pfx, ps->path); } -static int workdir_iterator__entry_cmp_icase(const void *prefix, const void *item) +static int workdir_iterator__entry_cmp_icase(const void *pfx, const void *item) { const git_path_with_stat *ps = item; - return git__prefixcmp_icase((const char *)prefix, ps->path); + return git__prefixcmp_icase((const char *)pfx, ps->path); +} + +static void workdir_iterator__seek_frame_start( + workdir_iterator *wi, workdir_iterator_frame *wf) +{ + if (!wf) + return; + + if (wi->base.start) + git_vector_bsearch3( + &wf->index, &wf->entries, wi->entrycmp, wi->base.start); + else + wf->index = 0; + + if (path_is_dotgit(git_vector_get(&wf->entries, wf->index))) + wf->index++; } static int workdir_iterator__expand_dir(workdir_iterator *wi) @@ -548,39 +595,26 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) 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); + error = git_path_dirload_with_stat( + wi->path.ptr, wi->root_len, wi->base.ignore_case, + wi->base.start, wi->base.end, &wf->entries); + if (error < 0 || wf->entries.length == 0) { workdir_iterator__free_frame(wf); return GIT_ENOTFOUND; } - git_vector_sort(&wf->entries); - - if (!wi->stack) - wf->start = wi->base.start; - else if (wi->stack->start && - 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, - CASESELECT(wi->base.ignore_case, workdir_iterator__entry_cmp_icase, workdir_iterator__entry_cmp_case), - wf->start); - - if (path_is_dotgit(git_vector_get(&wf->entries, wf->index))) - wf->index++; - - wf->next = wi->stack; - wi->stack = wf; + workdir_iterator__seek_frame_start(wi, wf); /* only push new ignores if this is not top level directory */ - if (wi->stack->next != NULL) { + if (wi->stack != NULL) { ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/'); (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); } + wf->next = wi->stack; + wi->stack = wf; + return workdir_iterator__update_entry(wi); } @@ -611,8 +645,10 @@ static int workdir_iterator__advance( if (wi->entry.path == NULL) return 0; - while ((wf = wi->stack) != NULL) { + while (1) { + wf = wi->stack; next = git_vector_get(&wf->entries, ++wf->index); + if (next != NULL) { /* match git's behavior of ignoring anything named ".git" */ if (path_is_dotgit(next)) @@ -621,15 +657,15 @@ static int workdir_iterator__advance( break; } - /* pop workdir directory stack */ - wi->stack = wf->next; - workdir_iterator__free_frame(wf); - git_ignore__pop_dir(&wi->ignores); - - if (wi->stack == NULL) { + /* pop stack if anything is left to pop */ + if (!wf->next) { memset(&wi->entry, 0, sizeof(wi->entry)); return 0; } + + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); } error = workdir_iterator__update_entry(wi); @@ -650,18 +686,24 @@ static int workdir_iterator__seek(git_iterator *self, const char *prefix) return 0; } -static int workdir_iterator__reset(git_iterator *self) +static int workdir_iterator__reset( + git_iterator *self, const char *start, const char *end) { workdir_iterator *wi = (workdir_iterator *)self; + while (wi->stack != NULL && wi->stack->next != NULL) { workdir_iterator_frame *wf = wi->stack; wi->stack = wf->next; workdir_iterator__free_frame(wf); git_ignore__pop_dir(&wi->ignores); } - if (wi->stack) - wi->stack->index = 0; - return 0; + + if (iterator__reset_range(self, start, end) < 0) + return -1; + + workdir_iterator__seek_frame_start(wi, wi->stack); + + return workdir_iterator__update_entry(wi); } static void workdir_iterator__free(git_iterator *self) @@ -680,7 +722,8 @@ static void workdir_iterator__free(git_iterator *self) static int workdir_iterator__update_entry(workdir_iterator *wi) { - git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); + git_path_with_stat *ps = + git_vector_get(&wi->stack->entries, wi->stack->index); git_buf_truncate(&wi->path, wi->root_len); memset(&wi->entry, 0, sizeof(wi->entry)); @@ -691,8 +734,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 && ITERATOR_PREFIXCMP( + wi->base, wi->path.ptr + wi->root_len, wi->base.end) > 0) return 0; wi->entry.path = ps->path; @@ -716,7 +759,7 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) /* detect submodules */ if (S_ISDIR(wi->entry.mode)) { - int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path); + int res = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path); bool is_submodule = (res == 0); if (res == GIT_ENOTFOUND) giterr_clear(); @@ -750,10 +793,10 @@ int git_iterator_for_workdir_range( return error; ITERATOR_BASE_INIT(wi, workdir, WORKDIR); - wi->repo = repo; + wi->base.repo = repo; if ((error = git_repository_index__weakptr(&index, repo)) < 0) { - git__free(wi); + git_iterator_free((git_iterator *)wi); return error; } @@ -769,6 +812,8 @@ int git_iterator_for_workdir_range( } wi->root_len = wi->path.size; + wi->entrycmp = wi->base.ignore_case ? + workdir_iterator__entry_cmp_icase : workdir_iterator__entry_cmp_case; if ((error = workdir_iterator__expand_dir(wi)) < 0) { if (error == GIT_ENOTFOUND) @@ -837,10 +882,13 @@ static int spoolandsort_iterator__seek(git_iterator *self, const char *prefix) return -1; } -static int spoolandsort_iterator__reset(git_iterator *self) +static int spoolandsort_iterator__reset( + git_iterator *self, const char *start, const char *end) { spoolandsort_iterator *si = (spoolandsort_iterator *)self; + GIT_UNUSED(start); GIT_UNUSED(end); + si->position = 0; return 0; diff --git a/src/iterator.h b/src/iterator.h index 77ead76cc..9fe684412 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -28,32 +28,33 @@ typedef enum { struct git_iterator { git_iterator_type_t type; + git_repository *repo; char *start; char *end; + bool ignore_case; + int (*current)(git_iterator *, const git_index_entry **); int (*at_end)(git_iterator *); int (*advance)(git_iterator *, const git_index_entry **); int (*seek)(git_iterator *, const char *prefix); - int (*reset)(git_iterator *); + int (*reset)(git_iterator *, const char *start, const char *end); void (*free)(git_iterator *); - unsigned int ignore_case:1; }; extern int git_iterator_for_nothing(git_iterator **iter); extern int git_iterator_for_tree_range( - git_iterator **iter, git_repository *repo, git_tree *tree, + git_iterator **iter, git_tree *tree, const char *start, const char *end); GIT_INLINE(int) git_iterator_for_tree( - git_iterator **iter, git_repository *repo, git_tree *tree) + git_iterator **iter, git_tree *tree) { - return git_iterator_for_tree_range(iter, repo, tree, NULL, NULL); + return git_iterator_for_tree_range(iter, tree, NULL, NULL); } extern int git_iterator_for_index_range( - git_iterator **iter, git_index *index, - const char *start, const char *end); + git_iterator **iter, git_index *index, const char *start, const char *end); GIT_INLINE(int) git_iterator_for_index( git_iterator **iter, git_index *index) @@ -90,7 +91,8 @@ 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); + return git_iterator_spoolandsort_range( + iter, towrap, comparer, ignore_case, NULL, NULL); } /* Entry is not guaranteed to be fully populated. For a tree iterator, @@ -124,9 +126,10 @@ GIT_INLINE(int) git_iterator_seek( return iter->seek(iter, prefix); } -GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +GIT_INLINE(int) git_iterator_reset( + git_iterator *iter, const char *start, const char *end) { - return iter->reset(iter); + return iter->reset(iter, start, end); } GIT_INLINE(void) git_iterator_free(git_iterator *iter) @@ -149,6 +152,11 @@ GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) return iter->type; } +GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) +{ + return iter->repo; +} + extern int git_iterator_current_tree_entry( git_iterator *iter, const git_tree_entry **tree_entry); diff --git a/src/notes.c b/src/notes.c index f96b5b139..8a27bdbf5 100644 --- a/src/notes.c +++ b/src/notes.c @@ -593,7 +593,7 @@ int git_note_foreach( if (!(error = retrieve_note_tree_and_commit( &tree, &commit, repo, ¬es_ref)) && - !(error = git_iterator_for_tree(&iter, repo, tree))) + !(error = git_iterator_for_tree(&iter, tree))) error = git_iterator_current(iter, &item); while (!error && item) { diff --git a/src/path.c b/src/path.c index 87eded3c4..569101c40 100644 --- a/src/path.c +++ b/src/path.c @@ -770,18 +770,30 @@ int git_path_dirload( int git_path_with_stat_cmp(const void *a, const void *b) { const git_path_with_stat *psa = a, *psb = b; - return git__strcmp_cb(psa->path, psb->path); + return strcmp(psa->path, psb->path); +} + +int git_path_with_stat_cmp_icase(const void *a, const void *b) +{ + const git_path_with_stat *psa = a, *psb = b; + return strcasecmp(psa->path, psb->path); } int git_path_dirload_with_stat( const char *path, size_t prefix_len, + bool ignore_case, + const char *start_stat, + const char *end_stat, git_vector *contents) { int error; unsigned int i; git_path_with_stat *ps; git_buf full = GIT_BUF_INIT; + int (*strncomp)(const char *a, const char *b, size_t sz); + size_t start_len = start_stat ? strlen(start_stat) : 0; + size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len; if (git_buf_set(&full, path, prefix_len) < 0) return -1; @@ -793,11 +805,23 @@ int git_path_dirload_with_stat( return error; } + strncomp = ignore_case ? git__strncasecmp : git__strncmp; + + /* stat struct at start of git_path_with_stat, so shift path text */ git_vector_foreach(contents, i, ps) { size_t path_len = strlen((char *)ps); - memmove(ps->path, ps, path_len + 1); ps->path_len = path_len; + } + + git_vector_foreach(contents, i, ps) { + /* skip if before start_stat or after end_stat */ + cmp_len = min(start_len, ps->path_len); + if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0) + continue; + cmp_len = min(end_len, ps->path_len); + if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0) + continue; if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || (error = git_path_lstat(full.ptr, &ps->st)) < 0) @@ -806,11 +830,14 @@ int git_path_dirload_with_stat( git_buf_truncate(&full, prefix_len); if (S_ISDIR(ps->st.st_mode)) { - ps->path[path_len] = '/'; - ps->path[path_len + 1] = '\0'; + ps->path[ps->path_len++] = '/'; + ps->path[ps->path_len] = '\0'; } } + /* sort now that directory suffix is added */ + git_vector_sort(contents); + git_buf_free(&full); return error; diff --git a/src/path.h b/src/path.h index b6292277f..db260d8e3 100644 --- a/src/path.h +++ b/src/path.h @@ -321,18 +321,33 @@ typedef struct { } git_path_with_stat; extern int git_path_with_stat_cmp(const void *a, const void *b); +extern int git_path_with_stat_cmp_icase(const void *a, const void *b); /** * Load all directory entries along with stat info into a vector. * - * This is just like git_path_dirload except that each entry in the - * vector is a git_path_with_stat structure that contains both the - * path and the stat info, plus directories will have a / suffixed - * to their path name. + * This adds four things on top of plain `git_path_dirload`: + * + * 1. Each entry in the vector is a `git_path_with_stat` struct that + * contains both the path and the stat info + * 2. The entries will be sorted alphabetically + * 3. Entries that are directories will be suffixed with a '/' + * 4. Optionally, you can be a start and end prefix and only elements + * after the start and before the end (inclusively) will be stat'ed. + * + * @param path The directory to read from + * @param prefix_len The trailing part of path to prefix to entry paths + * @param ignore_case How to sort and compare paths with start/end limits + * @param start_stat As optimization, only stat values after this prefix + * @param end_stat As optimization, only stat values before this prefix + * @param contents Vector to fill with git_path_with_stat structures */ extern int git_path_dirload_with_stat( const char *path, size_t prefix_len, + bool ignore_case, + const char *start_stat, + const char *end_stat, git_vector *contents); #endif diff --git a/src/submodule.c b/src/submodule.c index 21a1875c2..3d9950d58 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1156,7 +1156,7 @@ static int load_submodule_config_from_head( if ((error = git_repository_head_tree(&head, repo)) < 0) return error; - if ((error = git_iterator_for_tree(&i, repo, head)) < 0) { + if ((error = git_iterator_for_tree(&i, head)) < 0) { git_tree_free(head); return error; } diff --git a/src/tree.c b/src/tree.c index efb991df1..7f1b9feb1 100644 --- a/src/tree.c +++ b/src/tree.c @@ -207,9 +207,14 @@ void git_tree__free(git_tree *tree) git__free(tree); } -const git_oid *git_tree_id(const git_tree *c) +const git_oid *git_tree_id(const git_tree *t) { - return git_object_id((const git_object *)c); + return git_object_id((const git_object *)t); +} + +git_repository *git_tree_owner(const git_tree *t) +{ + return git_object_owner((const git_object *)t); } git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) @@ -296,6 +301,9 @@ int git_tree__prefix_position(git_tree *tree, const char *path) struct tree_key_search ksearch; size_t at_pos; + if (!path) + return 0; + ksearch.filename = path; ksearch.filename_len = strlen(path); diff --git a/src/tree.h b/src/tree.h index e0bcd6acf..c28523d6f 100644 --- a/src/tree.h +++ b/src/tree.h @@ -29,6 +29,10 @@ struct git_treebuilder { git_vector entries; }; +GIT_INLINE(int) git_tree__dup(git_tree **dest, git_tree *source) +{ + return git_object__dup((git_object **)dest, (git_object *)source); +} GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) { diff --git a/src/util.c b/src/util.c index 9813eb694..831b07385 100644 --- a/src/util.c +++ b/src/util.c @@ -199,9 +199,17 @@ int git__strncmp(const char *a, const char *b, size_t sz) int git__strncasecmp(const char *a, const char *b, size_t sz) { - while (sz && *a && *b && tolower(*a) == tolower(*b)) + int al, bl; + + while (sz && *a && *b) { + al = (unsigned char)tolower(*a); + bl = (unsigned char)tolower(*b); + if (al != bl) + break; --sz, ++a, ++b; - return !sz ? 0 : (tolower(*a) - tolower(*b)); + } + + return !sz ? 0 : al - bl; } void git__strntolower(char *str, size_t len) diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index 1d8396099..b5790632d 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -31,25 +31,35 @@ static void tree_iterator_test( git_tree *t; git_iterator *i; const git_index_entry *entry; - int count = 0; + int count = 0, count_post_reset = 0; 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, repo, t, start, end)); - cl_git_pass(git_iterator_current(i, &entry)); + cl_git_pass(git_iterator_for_tree_range(&i, t, start, end)); + /* test loop */ + cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { if (expected_values != NULL) cl_assert_equal_s(expected_values[count], entry->path); - count++; + cl_git_pass(git_iterator_advance(i, &entry)); + } + /* test reset */ + cl_git_pass(git_iterator_reset(i, NULL, NULL)); + cl_git_pass(git_iterator_current(i, &entry)); + while (entry != NULL) { + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count_post_reset], entry->path); + count_post_reset++; cl_git_pass(git_iterator_advance(i, &entry)); } git_iterator_free(i); - cl_assert(expected_count == count); + cl_assert_equal_i(expected_count, count); + cl_assert_equal_i(count, count_post_reset); git_tree_free(t); } @@ -294,7 +304,7 @@ void test_diff_iterator__tree_special_functions(void) repo, "24fa9a9fc4e202313e24b648087495441dab432b"); cl_assert(t != NULL); - cl_git_pass(git_iterator_for_tree_range(&i, repo, t, NULL, NULL)); + cl_git_pass(git_iterator_for_tree_range(&i, t, NULL, NULL)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { @@ -520,7 +530,7 @@ static void workdir_iterator_test( { git_iterator *i; const git_index_entry *entry; - int count = 0, count_all = 0; + 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)); @@ -547,10 +557,26 @@ static void workdir_iterator_test( cl_git_pass(git_iterator_advance(i, &entry)); } + cl_git_pass(git_iterator_reset(i, NULL, NULL)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into_directory(i, &entry)); + continue; + } + if (expected_names != NULL) + cl_assert_equal_s( + expected_names[count_all_post_reset], entry->path); + count_all_post_reset++; + cl_git_pass(git_iterator_advance(i, &entry)); + } + git_iterator_free(i); - cl_assert_equal_i(expected_count,count); + cl_assert_equal_i(expected_count, count); cl_assert_equal_i(expected_count + expected_ignores, count_all); + cl_assert_equal_i(count_all, count_all_post_reset); } void test_diff_iterator__workdir_0(void)