Commit 7b0f8ba9 by Vicent Marti

Merge pull request #2279 from libgit2/rb/moar-eegnöre-fîxés

Fix several ignore and attribute file behavior bugs
parents 386777fd ac16bd0a
......@@ -217,6 +217,74 @@ cleanup:
return error;
}
static int preload_attr_file(
git_repository *repo,
git_attr_file_source source,
const char *base,
const char *file)
{
int error;
git_attr_file *preload = NULL;
if (!file)
return 0;
if (!(error = git_attr_cache__get(
&preload, repo, source, base, file, git_attr_file__parse_buffer)))
git_attr_file__free(preload);
return error;
}
static int attr_setup(git_repository *repo)
{
int error = 0;
const char *workdir = git_repository_workdir(repo);
git_index *idx = NULL;
git_buf sys = GIT_BUF_INIT;
if ((error = git_attr_cache__init(repo)) < 0)
return error;
/* preload attribute files that could contain macros so the
* definitions will be available for later file parsing
*/
if (!(error = git_sysdir_find_system_file(&sys, GIT_ATTR_FILE_SYSTEM))) {
error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
git_buf_free(&sys);
}
if (error < 0) {
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
} else
return error;
}
if ((error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_FILE,
NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
return error;
if ((error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_FILE,
git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
return error;
if (workdir != NULL &&
(error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
return error;
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
(error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
return error;
return error;
}
int git_attr_add_macro(
git_repository *repo,
const char *name,
......@@ -226,8 +294,8 @@ int git_attr_add_macro(
git_attr_rule *macro = NULL;
git_pool *pool;
if (git_attr_cache__init(repo) < 0)
return -1;
if ((error = git_attr_cache__init(repo)) < 0)
return error;
macro = git__calloc(1, sizeof(git_attr_rule));
GITERR_CHECK_ALLOC(macro);
......@@ -348,7 +416,7 @@ static int collect_attr_files(
const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL };
if ((error = git_attr_cache__init(repo)) < 0)
if ((error = attr_setup(repo)) < 0)
return error;
/* Resolve path in a non-bare repo */
......
......@@ -248,9 +248,7 @@ int git_attr_file__parse_buffer(
repo, &attrs->pool, &rule->assigns, &scan)))
{
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
/* should generate error/warning if this is coming from any
* file other than .gitattributes at repo root.
*/
/* TODO: warning if macro found in file below repo root */
error = git_attr_cache__insert_macro(repo, rule);
else
error = git_vector_insert(&attrs->rules, rule);
......@@ -355,6 +353,8 @@ bool git_attr_fnmatch__match(
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
flags |= FNM_CASEFOLD;
if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
flags |= FNM_LEADING_DIR;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
filename = path->path;
......@@ -545,6 +545,14 @@ int git_attr_fnmatch__parse(
if (--slash_count <= 0)
spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
}
if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
spec->length >= 2 &&
pattern[spec->length - 1] == '*' &&
pattern[spec->length - 2] == '/') {
spec->length -= 2;
spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
/* leave FULLPATH match on, however */
}
if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
context != NULL && git_path_root(pattern) < 0)
......
......@@ -30,10 +30,12 @@
#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10)
#define GIT_ATTR_FNMATCH_LEADINGDIR (1U << 11)
#define GIT_ATTR_FNMATCH_NOLEADINGDIR (1U << 12)
#define GIT_ATTR_FNMATCH__INCOMING \
(GIT_ATTR_FNMATCH_ALLOWSPACE | \
GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO)
(GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \
GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR)
typedef enum {
GIT_ATTR_FILE__IN_MEMORY = 0,
......
......@@ -123,7 +123,7 @@ int git_ignore__for_path(
int error = 0;
const char *workdir = git_repository_workdir(repo);
assert(ignores);
assert(ignores && path);
memset(ignores, 0, sizeof(*ignores));
ignores->repo = repo;
......@@ -140,10 +140,13 @@ int git_ignore__for_path(
if (workdir && git_path_root(path) < 0)
error = git_path_find_dir(&ignores->dir, path, workdir);
else
error = git_buf_sets(&ignores->dir, path);
error = git_buf_joinpath(&ignores->dir, path, "");
if (error < 0)
goto cleanup;
if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
ignores->dir_root = strlen(workdir);
/* set up internals */
if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
goto cleanup;
......@@ -204,10 +207,10 @@ int git_ignore__pop_dir(git_ignores *ign)
if ((end = strrchr(start, '/')) != NULL) {
size_t dirlen = (end - start) + 1;
const char *relpath = ign->dir.ptr + ign->dir_root;
size_t pathlen = ign->dir.size - ign->dir_root;
if (ign->dir.size >= dirlen &&
!memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
{
if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
git_vector_pop(&ign->ign_path);
git_attr_file__free(file);
}
......
......@@ -28,6 +28,7 @@ typedef struct {
git_attr_file *ign_internal;
git_vector ign_path;
git_vector ign_global;
size_t dir_root; /* offset in dir to repo root */
int ignore_case;
int depth;
} git_ignores;
......
......@@ -624,7 +624,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base)
/* call dirname if this is not a directory */
if (!error) /* && git_path_isdir(dir->ptr) == false) */
error = git_path_dirname_r(dir, dir->ptr);
error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
if (!error)
error = git_path_to_dir(dir);
......
......@@ -83,7 +83,8 @@ int git_pathspec__vinit(
if (!match)
return -1;
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE |
GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_NOLEADINGDIR;
ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
if (ret == GIT_ENOTFOUND) {
......
......@@ -16,7 +16,7 @@ void test_attr_ignore__cleanup(void)
g_repo = NULL;
}
void assert_is_ignored_(
static void assert_is_ignored_(
bool expected, const char *filepath, const char *file, int line)
{
int is_ignored = 0;
......
......@@ -23,49 +23,74 @@ void test_attr_repo__cleanup(void)
g_repo = NULL;
}
static struct attr_expected get_one_test_cases[] = {
{ "root_test1", "repoattr", EXPECT_TRUE, NULL },
{ "root_test1", "rootattr", EXPECT_TRUE, NULL },
{ "root_test1", "missingattr", EXPECT_UNDEFINED, NULL },
{ "root_test1", "subattr", EXPECT_UNDEFINED, NULL },
{ "root_test1", "negattr", EXPECT_UNDEFINED, NULL },
{ "root_test2", "repoattr", EXPECT_TRUE, NULL },
{ "root_test2", "rootattr", EXPECT_FALSE, NULL },
{ "root_test2", "missingattr", EXPECT_UNDEFINED, NULL },
{ "root_test2", "multiattr", EXPECT_FALSE, NULL },
{ "root_test3", "repoattr", EXPECT_TRUE, NULL },
{ "root_test3", "rootattr", EXPECT_UNDEFINED, NULL },
{ "root_test3", "multiattr", EXPECT_STRING, "3" },
{ "root_test3", "multi2", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" },
{ "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL },
{ "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" },
{ "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL },
{ "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" },
{ "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL },
{ "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" },
{ "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL },
{ "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL },
{ "does-not-exist", "foo", EXPECT_STRING, "yes" },
{ "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL },
{ "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" },
{ "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL },
};
void test_attr_repo__get_one(void)
{
struct attr_expected test_cases[] = {
{ "root_test1", "repoattr", EXPECT_TRUE, NULL },
{ "root_test1", "rootattr", EXPECT_TRUE, NULL },
{ "root_test1", "missingattr", EXPECT_UNDEFINED, NULL },
{ "root_test1", "subattr", EXPECT_UNDEFINED, NULL },
{ "root_test1", "negattr", EXPECT_UNDEFINED, NULL },
{ "root_test2", "repoattr", EXPECT_TRUE, NULL },
{ "root_test2", "rootattr", EXPECT_FALSE, NULL },
{ "root_test2", "missingattr", EXPECT_UNDEFINED, NULL },
{ "root_test2", "multiattr", EXPECT_FALSE, NULL },
{ "root_test3", "repoattr", EXPECT_TRUE, NULL },
{ "root_test3", "rootattr", EXPECT_UNDEFINED, NULL },
{ "root_test3", "multiattr", EXPECT_STRING, "3" },
{ "root_test3", "multi2", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" },
{ "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL },
{ "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL },
{ "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" },
{ "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL },
{ "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" },
{ "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL },
{ "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" },
{ "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL },
{ "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL },
{ "does-not-exist", "foo", EXPECT_STRING, "yes" },
{ "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL },
{ "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" },
{ "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL },
{ NULL, NULL, 0, NULL }
}, *scan;
for (scan = test_cases; scan->path != NULL; scan++) {
int i;
for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) {
struct attr_expected *scan = &get_one_test_cases[i];
const char *value;
cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr));
attr_check_expected(
scan->expected, scan->expected_str, scan->attr, value);
}
cl_assert(git_attr_cache__is_cached(
g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/attributes"));
cl_assert(git_attr_cache__is_cached(
g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitattributes"));
cl_assert(git_attr_cache__is_cached(
g_repo, GIT_ATTR_FILE__FROM_FILE, "sub/.gitattributes"));
}
void test_attr_repo__get_one_start_deep(void)
{
int i;
for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) {
struct attr_expected *scan = &get_one_test_cases[i];
const char *value;
cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr));
attr_check_expected(scan->expected, scan->expected_str, scan->attr, value);
attr_check_expected(
scan->expected, scan->expected_str, scan->attr, value);
}
cl_assert(git_attr_cache__is_cached(
......
......@@ -9,20 +9,13 @@ int cb_status__normal(
if (counts->debug)
cb_status__print(path, status_flags, NULL);
if (counts->entry_count >= counts->expected_entry_count) {
if (counts->entry_count >= counts->expected_entry_count)
counts->wrong_status_flags_count++;
goto exit;
}
if (strcmp(path, counts->expected_paths[counts->entry_count])) {
else if (strcmp(path, counts->expected_paths[counts->entry_count]))
counts->wrong_sorted_path++;
goto exit;
}
if (status_flags != counts->expected_statuses[counts->entry_count])
else if (status_flags != counts->expected_statuses[counts->entry_count])
counts->wrong_status_flags_count++;
exit:
counts->entry_count++;
return 0;
}
......
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