Commit ef206124 by Edward Thomson Committed by Edward Thomson

Move filelist into the iterator handling itself.

parent ed1c6446
......@@ -163,6 +163,13 @@ typedef enum {
/** Include unreadable files in the diff */
GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17),
/** Use literal path matching in the iterators. This is more broad
* than the DISABLE_PATHSPEC_MATCH flag. The caller must provide an
* array of paths (no patterns or prefixes). Only values included in
* that list will be returned.
*/
GIT_DIFF_ENABLE_FILELIST_MATCH = (1u << 18),
/*
* Options controlling how output will be generated
*/
......
......@@ -74,6 +74,24 @@ static int diff_insert_delta(
return error;
}
static bool diff_pathspec_match(
const char **matched_pathspec, git_diff *diff, const char *path)
{
/* The iterator has filtered out paths for us, so the fact that we're
* seeing this patch means that it must match the given path list.
*/
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_ENABLE_FILELIST_MATCH)) {
*matched_pathspec = path;
return true;
}
return git_pathspec__match(
&diff->pathspec, path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
matched_pathspec, NULL);
}
static int diff_delta__from_one(
git_diff *diff,
git_delta_t status,
......@@ -110,11 +128,7 @@ static int diff_delta__from_one(
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
return 0;
if (!git_pathspec__match(
&diff->pathspec, entry->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
&matched_pathspec, NULL))
if (!diff_pathspec_match(&matched_pathspec, diff, entry->path))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
......@@ -755,11 +769,7 @@ static int maybe_modified(
const char *matched_pathspec;
int error = 0;
if (!git_pathspec__match(
&diff->pathspec, oitem->path,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
&matched_pathspec, NULL))
if (!diff_pathspec_match(&matched_pathspec, diff, oitem->path))
return 0;
memset(&noid, 0, sizeof(noid));
......@@ -1266,7 +1276,9 @@ cleanup:
#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
git_iterator *a = NULL, *b = NULL; \
char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
git_vector pathlist = GIT_VECTOR_INIT; \
char *pfx = (opts && !(opts->flags & GIT_DIFF_ENABLE_FILELIST_MATCH)) ? \
git_pathspec_prefix(&opts->pathspec) : NULL; \
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
b_opts = GIT_ITERATOR_OPTIONS_INIT; \
a_opts.flags = FLAGS_FIRST; \
......@@ -1276,9 +1288,19 @@ cleanup:
b_opts.start = pfx; \
b_opts.end = pfx; \
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
if (opts && (opts->flags & GIT_DIFF_ENABLE_FILELIST_MATCH) && opts->pathspec.count) { \
size_t __i; \
error = git_vector_init(&pathlist, opts->pathspec.count, NULL); \
for (__i = 0; !error && __i < opts->pathspec.count; __i++) { \
error = git_vector_insert(&pathlist, opts->pathspec.strings[__i]); \
} \
a_opts.pathlist = &pathlist; \
b_opts.pathlist = &pathlist; \
} \
if (!error && !(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); \
git_vector_free(&pathlist); \
} while (0)
int git_diff_tree_to_tree(
......
......@@ -42,6 +42,8 @@
(P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \
if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
(P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
if (options && options->pathlist) \
(P)->base.pathlist = options->pathlist; \
} while (0)
#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
......@@ -106,6 +108,12 @@ static int iterator__update_ignore_case(
iter->prefixcomp = iterator__ignore_case(iter) ?
git__prefixcmp_icase : git__prefixcmp;
if (iter->pathlist) {
git_vector_set_cmp(iter->pathlist, iterator__ignore_case(iter) ?
git__strcasecmp : git__strcmp);
git_vector_sort(iter->pathlist);
}
return error;
}
......@@ -616,6 +624,9 @@ int git_iterator_for_tree(
if (tree == NULL)
return git_iterator_for_nothing(iter, options);
/* not yet supported */
assert (!options || !options->pathlist);
if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
return error;
......@@ -668,15 +679,47 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
return ie;
}
static const git_index_entry *index_iterator__advance_over_conflicts(index_iterator *ii)
static const git_index_entry *index_iterator__advance_over_unwanted(index_iterator *ii)
{
const git_index_entry *ie = index_iterator__index_entry(ii);
const char *p;
int cmp;
if (!iterator__include_conflicts(ii)) {
while (ie && git_index_entry_is_conflict(ie)) {
while (ie) {
if (!iterator__include_conflicts(ii) &&
git_index_entry_is_conflict(ie)) {
ii->current++;
ie = index_iterator__index_entry(ii);
continue;
}
/* if we have a pathlist, this entry's path must be in it to be
* returned. otherwise, advance the pathlist entry or the iterator
* until we find the next path that we want to return.
*/
if (ii->base.pathlist) {
if (ii->base.pathlist_idx >= ii->base.pathlist->length) {
ii->current = SIZE_MAX;
ie = NULL;
break;
}
p = ii->base.pathlist->contents[ii->base.pathlist_idx];
cmp = ii->base.pathlist->_cmp(p, ie->path);
if (cmp < 0) {
ii->base.pathlist_idx++;
continue;
}
if (cmp > 0) {
ii->current++;
ie = index_iterator__index_entry(ii);
continue;
}
}
break;
}
return ie;
......@@ -705,7 +748,7 @@ static void index_iterator__next_prefix_tree(index_iterator *ii)
static int index_iterator__first_prefix_tree(index_iterator *ii)
{
const git_index_entry *ie = index_iterator__advance_over_conflicts(ii);
const git_index_entry *ie = index_iterator__advance_over_unwanted(ii);
const char *scan, *prior, *slash;
if (!ie || !iterator__include_trees(ii))
......@@ -818,17 +861,22 @@ static int index_iterator__reset(
{
index_iterator *ii = (index_iterator *)self;
const git_index_entry *ie;
size_t pathlist_idx = 0;
if (iterator__reset_range(self, start, end) < 0)
return -1;
ii->current = 0;
ii->base.pathlist_idx = 0;
/* if we're given a start prefix, find it; if we're given a pathlist, find
* the first of those. start at the later of the two.
*/
if (ii->base.start)
git_index_snapshot_find(
&ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0);
if ((ie = index_iterator__advance_over_conflicts(ii)) == NULL)
if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL)
return 0;
if (git_buf_sets(&ii->partial, ie->path) < 0)
......@@ -1004,25 +1052,60 @@ static void fs_iterator__seek_frame_start(
ff->index = 0;
}
typedef enum {
DIRLOAD_PATHLIST_NONE = 0,
DIRLOAD_PATHLIST_EXACT = 1,
DIRLOAD_PATHLIST_DIRECTORY = 2,
} dirload_pathlist_match_t;
static dirload_pathlist_match_t dirload_pathlist_match(
git_vector *pathlist,
const char *path,
size_t path_len,
int (*prefixcomp)(const char *a, const char *b))
{
const char *matched;
size_t idx;
if (git_vector_bsearch2(
&idx, pathlist, pathlist->_cmp, path) != GIT_ENOTFOUND)
return DIRLOAD_PATHLIST_EXACT;
/* the explicit path we searched for was not found, but this may be
* a directory and the pathlist contains a file in it. check.
*/
if ((matched = git_vector_get(pathlist, idx)) != NULL &&
prefixcomp(matched, path) == 0 &&
matched[path_len] == '/')
return DIRLOAD_PATHLIST_DIRECTORY;
return DIRLOAD_PATHLIST_NONE;
}
static int dirload_with_stat(
git_vector *contents,
const char *dirpath,
size_t prefix_len,
unsigned int flags,
const char *start_stat,
const char *end_stat,
git_vector *contents)
git_vector *pathlist)
{
git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
const char *path;
int (*strncomp)(const char *a, const char *b, size_t sz);
int (*prefixcomp)(const char *a, const char *b);
size_t start_len = start_stat ? strlen(start_stat) : 0;
size_t end_len = end_stat ? strlen(end_stat) : 0;
fs_iterator_path_with_stat *ps;
size_t path_len, cmp_len, ps_size;
dirload_pathlist_match_t pathlist_match = DIRLOAD_PATHLIST_EXACT;
int error;
strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
git__strncasecmp : git__strncmp;
prefixcomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
git__prefixcmp_icase : git__prefixcmp;
/* Any error here is equivalent to the dir not existing, skip over it */
if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0) {
......@@ -1044,10 +1127,20 @@ static int dirload_with_stat(
cmp_len = min(start_len, path_len);
if (cmp_len && strncomp(path, start_stat, cmp_len) < 0)
continue;
/* skip if after end_stat */
cmp_len = min(end_len, path_len);
if (cmp_len && strncomp(path, end_stat, cmp_len) > 0)
continue;
/* skip if we have a pathlist and this isn't in it. note that we
* haven't stat'd yet to know if it's a file or a directory, so this
* match for files like `foo` when we're looking for `foo/bar`
*/
if (pathlist &&
!(pathlist_match = dirload_pathlist_match(
pathlist, path, path_len, prefixcomp)))
continue;
/* Make sure to append two bytes, one for the path's null
* termination, one for a possible trailing '/' for folders.
*/
......@@ -1068,6 +1161,12 @@ static int dirload_with_stat(
continue;
}
if (pathlist_match == DIRLOAD_PATHLIST_DIRECTORY) {
/* were looking for a directory, but this is a file */
git__free(ps);
continue;
}
/* Treat the file as unreadable if we get any other error */
memset(&ps->st, 0, sizeof(ps->st));
ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
......@@ -1113,9 +1212,9 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
ff = fs_iterator__alloc_frame(fi);
GITERR_CHECK_ALLOC(ff);
error = dirload_with_stat(
error = dirload_with_stat(&ff->entries,
fi->path.ptr, fi->root_len, fi->dirload_flags,
fi->base.start, fi->base.end, &ff->entries);
fi->base.start, fi->base.end, fi->base.pathlist);
if (error < 0) {
git_error_state last_error = { 0 };
......
......@@ -43,6 +43,11 @@ typedef struct {
const char *start;
const char *end;
/* paths to include in the iterator (literal). any paths not listed
* will be excluded. note that this vector may be resorted!
*/
git_vector *pathlist;
/* flags, from above */
unsigned int flags;
} git_iterator_options;
......@@ -65,6 +70,8 @@ struct git_iterator {
git_repository *repo;
char *start;
char *end;
git_vector *pathlist;
size_t pathlist_idx;
int (*prefixcomp)(const char *str, const char *prefix);
size_t stat_calls;
unsigned int flags;
......
......@@ -2356,10 +2356,8 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index
goto done;
}
opts.pathspec.count = staged_paths.length;
opts.pathspec.strings = (char **)staged_paths.contents;
iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
iter_opts.pathlist = &staged_paths;
if ((error = git_iterator_for_index(&iter_repo, index_repo, &iter_opts)) < 0 ||
(error = git_iterator_for_index(&iter_new, index_new, &iter_opts)) < 0 ||
......@@ -2406,6 +2404,7 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde
* will be applied by the merge (including conflicts). Ensure that there
* are no changes in the workdir to these paths.
*/
opts.flags |= GIT_DIFF_ENABLE_FILELIST_MATCH;
opts.pathspec.count = merged_paths->length;
opts.pathspec.strings = (char **)merged_paths->contents;
......
......@@ -1073,3 +1073,251 @@ void test_repo_iterator__skips_fifos_and_such(void)
git_iterator_free(i);
#endif
}
void test_repo_iterator__indexfilelist(void)
{
git_iterator *i;
git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
git_index *index;
git_vector filelist;
int default_icase;
int expect;
cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
cl_git_pass(git_vector_insert(&filelist, "a"));
cl_git_pass(git_vector_insert(&filelist, "B"));
cl_git_pass(git_vector_insert(&filelist, "c"));
cl_git_pass(git_vector_insert(&filelist, "D"));
cl_git_pass(git_vector_insert(&filelist, "e"));
cl_git_pass(git_vector_insert(&filelist, "k/1"));
cl_git_pass(git_vector_insert(&filelist, "k/a"));
cl_git_pass(git_vector_insert(&filelist, "L/1"));
g_repo = cl_git_sandbox_init("icase");
cl_git_pass(git_repository_index(&index, g_repo));
/* In this test we DO NOT force a case setting on the index. */
default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
i_opts.pathlist = &filelist;
/* All indexfilelist iterator tests are "autoexpand with no tree entries" */
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 8, NULL, 8, NULL);
git_iterator_free(i);
i_opts.start = "c";
i_opts.end = NULL;
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
/* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
expect = ((default_icase) ? 6 : 4);
expect_iterator_items(i, expect, NULL, expect, NULL);
git_iterator_free(i);
i_opts.start = NULL;
i_opts.end = "e";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
/* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
expect = ((default_icase) ? 5 : 6);
expect_iterator_items(i, expect, NULL, expect, NULL);
git_iterator_free(i);
git_index_free(index);
git_vector_free(&filelist);
}
void test_repo_iterator__indexfilelist_2(void)
{
git_iterator *i;
git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
git_index *index;
git_vector filelist = GIT_VECTOR_INIT;
g_repo = cl_git_sandbox_init("icase");
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
cl_git_pass(git_vector_insert(&filelist, "0"));
cl_git_pass(git_vector_insert(&filelist, "c"));
cl_git_pass(git_vector_insert(&filelist, "D"));
cl_git_pass(git_vector_insert(&filelist, "e"));
cl_git_pass(git_vector_insert(&filelist, "k/a"));
i_opts.pathlist = &filelist;
i_opts.start = "b";
i_opts.end = "k/D";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 4, NULL, 4, NULL);
git_iterator_free(i);
git_index_free(index);
git_vector_free(&filelist);
}
void test_repo_iterator__indexfilelist_icase(void)
{
git_iterator *i;
git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
git_index *index;
int caps;
git_vector filelist;
cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
cl_git_pass(git_vector_insert(&filelist, "a"));
cl_git_pass(git_vector_insert(&filelist, "B"));
cl_git_pass(git_vector_insert(&filelist, "c"));
cl_git_pass(git_vector_insert(&filelist, "D"));
cl_git_pass(git_vector_insert(&filelist, "e"));
cl_git_pass(git_vector_insert(&filelist, "k/1"));
cl_git_pass(git_vector_insert(&filelist, "k/a"));
cl_git_pass(git_vector_insert(&filelist, "L/1"));
g_repo = cl_git_sandbox_init("icase");
cl_git_pass(git_repository_index(&index, g_repo));
caps = git_index_caps(index);
/* force case sensitivity */
cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
/* All indexfilelist iterator tests are "autoexpand with no tree entries" */
i_opts.pathlist = &filelist;
i_opts.start = "c";
i_opts.end = "k/D";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 3, NULL, 3, NULL);
git_iterator_free(i);
i_opts.start = "k";
i_opts.end = "k/Z";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 1, NULL, 1, NULL);
git_iterator_free(i);
/* force case insensitivity */
cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
i_opts.start = "c";
i_opts.end = "k/D";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 5, NULL, 5, NULL);
git_iterator_free(i);
i_opts.start = "k";
i_opts.end = "k/Z";
cl_git_pass(git_iterator_for_index(&i, index, &i_opts));
expect_iterator_items(i, 2, NULL, 2, NULL);
git_iterator_free(i);
cl_git_pass(git_index_set_caps(index, caps));
git_index_free(index);
git_vector_free(&filelist);
}
void test_repo_iterator__workdirfilelist(void)
{
git_iterator *i;
git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
git_vector filelist;
bool default_icase;
int expect;
cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
cl_git_pass(git_vector_insert(&filelist, "a"));
cl_git_pass(git_vector_insert(&filelist, "B"));
cl_git_pass(git_vector_insert(&filelist, "c"));
cl_git_pass(git_vector_insert(&filelist, "D"));
cl_git_pass(git_vector_insert(&filelist, "e"));
cl_git_pass(git_vector_insert(&filelist, "k/1"));
cl_git_pass(git_vector_insert(&filelist, "k/a"));
cl_git_pass(git_vector_insert(&filelist, "L/1"));
g_repo = cl_git_sandbox_init("icase");
/* All indexfilelist iterator tests are "autoexpand with no tree entries" */
/* In this test we DO NOT force a case on the iteratords and verify default behavior. */
i_opts.pathlist = &filelist;
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
expect_iterator_items(i, 8, NULL, 8, NULL);
git_iterator_free(i);
i_opts.start = "c";
i_opts.end = NULL;
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
default_icase = git_iterator_ignore_case(i);
/* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
expect = ((default_icase) ? 6 : 4);
expect_iterator_items(i, expect, NULL, expect, NULL);
git_iterator_free(i);
i_opts.start = NULL;
i_opts.end = "e";
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
default_icase = git_iterator_ignore_case(i);
/* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
expect = ((default_icase) ? 5 : 6);
expect_iterator_items(i, expect, NULL, expect, NULL);
git_iterator_free(i);
git_vector_free(&filelist);
}
void test_repo_iterator__workdirfilelist_icase(void)
{
git_iterator *i;
git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
git_vector filelist;
cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
cl_git_pass(git_vector_insert(&filelist, "a"));
cl_git_pass(git_vector_insert(&filelist, "B"));
cl_git_pass(git_vector_insert(&filelist, "c"));
cl_git_pass(git_vector_insert(&filelist, "D"));
cl_git_pass(git_vector_insert(&filelist, "e"));
cl_git_pass(git_vector_insert(&filelist, "k/1"));
cl_git_pass(git_vector_insert(&filelist, "k/a"));
cl_git_pass(git_vector_insert(&filelist, "L/1"));
g_repo = cl_git_sandbox_init("icase");
i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
i_opts.pathlist = &filelist;
i_opts.start = "c";
i_opts.end = "k/D";
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
expect_iterator_items(i, 3, NULL, 3, NULL);
git_iterator_free(i);
i_opts.start = "k";
i_opts.end = "k/Z";
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
expect_iterator_items(i, 1, NULL, 1, NULL);
git_iterator_free(i);
i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
i_opts.start = "c";
i_opts.end = "k/D";
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
expect_iterator_items(i, 5, NULL, 5, NULL);
git_iterator_free(i);
i_opts.start = "k";
i_opts.end = "k/Z";
cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
expect_iterator_items(i, 2, NULL, 2, NULL);
git_iterator_free(i);
git_vector_free(&filelist);
}
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