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: ...@@ -217,6 +217,74 @@ cleanup:
return error; 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( int git_attr_add_macro(
git_repository *repo, git_repository *repo,
const char *name, const char *name,
...@@ -226,8 +294,8 @@ int git_attr_add_macro( ...@@ -226,8 +294,8 @@ int git_attr_add_macro(
git_attr_rule *macro = NULL; git_attr_rule *macro = NULL;
git_pool *pool; git_pool *pool;
if (git_attr_cache__init(repo) < 0) if ((error = git_attr_cache__init(repo)) < 0)
return -1; return error;
macro = git__calloc(1, sizeof(git_attr_rule)); macro = git__calloc(1, sizeof(git_attr_rule));
GITERR_CHECK_ALLOC(macro); GITERR_CHECK_ALLOC(macro);
...@@ -348,7 +416,7 @@ static int collect_attr_files( ...@@ -348,7 +416,7 @@ static int collect_attr_files(
const char *workdir = git_repository_workdir(repo); const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL }; attr_walk_up_info info = { NULL };
if ((error = git_attr_cache__init(repo)) < 0) if ((error = attr_setup(repo)) < 0)
return error; return error;
/* Resolve path in a non-bare repo */ /* Resolve path in a non-bare repo */
......
...@@ -248,9 +248,7 @@ int git_attr_file__parse_buffer( ...@@ -248,9 +248,7 @@ int git_attr_file__parse_buffer(
repo, &attrs->pool, &rule->assigns, &scan))) repo, &attrs->pool, &rule->assigns, &scan)))
{ {
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
/* should generate error/warning if this is coming from any /* TODO: warning if macro found in file below repo root */
* file other than .gitattributes at repo root.
*/
error = git_attr_cache__insert_macro(repo, rule); error = git_attr_cache__insert_macro(repo, rule);
else else
error = git_vector_insert(&attrs->rules, rule); error = git_vector_insert(&attrs->rules, rule);
...@@ -355,6 +353,8 @@ bool git_attr_fnmatch__match( ...@@ -355,6 +353,8 @@ bool git_attr_fnmatch__match(
if (match->flags & GIT_ATTR_FNMATCH_ICASE) if (match->flags & GIT_ATTR_FNMATCH_ICASE)
flags |= FNM_CASEFOLD; flags |= FNM_CASEFOLD;
if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
flags |= FNM_LEADING_DIR;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
filename = path->path; filename = path->path;
...@@ -545,6 +545,14 @@ int git_attr_fnmatch__parse( ...@@ -545,6 +545,14 @@ int git_attr_fnmatch__parse(
if (--slash_count <= 0) if (--slash_count <= 0)
spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; 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 && if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
context != NULL && git_path_root(pattern) < 0) context != NULL && git_path_root(pattern) < 0)
......
...@@ -30,10 +30,12 @@ ...@@ -30,10 +30,12 @@
#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) #define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) #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 \ #define GIT_ATTR_FNMATCH__INCOMING \
(GIT_ATTR_FNMATCH_ALLOWSPACE | \ (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \
GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR)
typedef enum { typedef enum {
GIT_ATTR_FILE__IN_MEMORY = 0, GIT_ATTR_FILE__IN_MEMORY = 0,
......
...@@ -123,7 +123,7 @@ int git_ignore__for_path( ...@@ -123,7 +123,7 @@ int git_ignore__for_path(
int error = 0; int error = 0;
const char *workdir = git_repository_workdir(repo); const char *workdir = git_repository_workdir(repo);
assert(ignores); assert(ignores && path);
memset(ignores, 0, sizeof(*ignores)); memset(ignores, 0, sizeof(*ignores));
ignores->repo = repo; ignores->repo = repo;
...@@ -140,10 +140,13 @@ int git_ignore__for_path( ...@@ -140,10 +140,13 @@ int git_ignore__for_path(
if (workdir && git_path_root(path) < 0) if (workdir && git_path_root(path) < 0)
error = git_path_find_dir(&ignores->dir, path, workdir); error = git_path_find_dir(&ignores->dir, path, workdir);
else else
error = git_buf_sets(&ignores->dir, path); error = git_buf_joinpath(&ignores->dir, path, "");
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
ignores->dir_root = strlen(workdir);
/* set up internals */ /* set up internals */
if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
goto cleanup; goto cleanup;
...@@ -204,10 +207,10 @@ int git_ignore__pop_dir(git_ignores *ign) ...@@ -204,10 +207,10 @@ int git_ignore__pop_dir(git_ignores *ign)
if ((end = strrchr(start, '/')) != NULL) { if ((end = strrchr(start, '/')) != NULL) {
size_t dirlen = (end - start) + 1; 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 && if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
!memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
{
git_vector_pop(&ign->ign_path); git_vector_pop(&ign->ign_path);
git_attr_file__free(file); git_attr_file__free(file);
} }
......
...@@ -28,6 +28,7 @@ typedef struct { ...@@ -28,6 +28,7 @@ typedef struct {
git_attr_file *ign_internal; git_attr_file *ign_internal;
git_vector ign_path; git_vector ign_path;
git_vector ign_global; git_vector ign_global;
size_t dir_root; /* offset in dir to repo root */
int ignore_case; int ignore_case;
int depth; int depth;
} git_ignores; } git_ignores;
......
...@@ -624,7 +624,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) ...@@ -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 */ /* call dirname if this is not a directory */
if (!error) /* && git_path_isdir(dir->ptr) == false) */ 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) if (!error)
error = git_path_to_dir(dir); error = git_path_to_dir(dir);
......
...@@ -83,7 +83,8 @@ int git_pathspec__vinit( ...@@ -83,7 +83,8 @@ int git_pathspec__vinit(
if (!match) if (!match)
return -1; 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); ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
if (ret == GIT_ENOTFOUND) { if (ret == GIT_ENOTFOUND) {
......
...@@ -16,7 +16,7 @@ void test_attr_ignore__cleanup(void) ...@@ -16,7 +16,7 @@ void test_attr_ignore__cleanup(void)
g_repo = NULL; g_repo = NULL;
} }
void assert_is_ignored_( static void assert_is_ignored_(
bool expected, const char *filepath, const char *file, int line) bool expected, const char *filepath, const char *file, int line)
{ {
int is_ignored = 0; int is_ignored = 0;
......
...@@ -23,49 +23,74 @@ void test_attr_repo__cleanup(void) ...@@ -23,49 +23,74 @@ void test_attr_repo__cleanup(void)
g_repo = NULL; 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) void test_attr_repo__get_one(void)
{ {
struct attr_expected test_cases[] = { int i;
{ "root_test1", "repoattr", EXPECT_TRUE, NULL },
{ "root_test1", "rootattr", EXPECT_TRUE, NULL }, for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) {
{ "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, struct attr_expected *scan = &get_one_test_cases[i];
{ "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, const char *value;
{ "root_test1", "negattr", EXPECT_UNDEFINED, NULL },
{ "root_test2", "repoattr", EXPECT_TRUE, NULL }, cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr));
{ "root_test2", "rootattr", EXPECT_FALSE, NULL }, attr_check_expected(
{ "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, scan->expected, scan->expected_str, scan->attr, value);
{ "root_test2", "multiattr", EXPECT_FALSE, NULL }, }
{ "root_test3", "repoattr", EXPECT_TRUE, NULL },
{ "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, cl_assert(git_attr_cache__is_cached(
{ "root_test3", "multiattr", EXPECT_STRING, "3" }, g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/attributes"));
{ "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, cl_assert(git_attr_cache__is_cached(
{ "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitattributes"));
{ "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, cl_assert(git_attr_cache__is_cached(
{ "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, g_repo, GIT_ATTR_FILE__FROM_FILE, "sub/.gitattributes"));
{ "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, }
{ "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL },
{ "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, void test_attr_repo__get_one_start_deep(void)
{ "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, {
{ "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, int i;
{ "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL },
{ "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) {
{ "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, struct attr_expected *scan = &get_one_test_cases[i];
{ "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++) {
const char *value; const char *value;
cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); 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( cl_assert(git_attr_cache__is_cached(
......
...@@ -9,20 +9,13 @@ int cb_status__normal( ...@@ -9,20 +9,13 @@ int cb_status__normal(
if (counts->debug) if (counts->debug)
cb_status__print(path, status_flags, NULL); 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++; counts->wrong_status_flags_count++;
goto exit; else if (strcmp(path, counts->expected_paths[counts->entry_count]))
}
if (strcmp(path, counts->expected_paths[counts->entry_count])) {
counts->wrong_sorted_path++; counts->wrong_sorted_path++;
goto exit; else if (status_flags != counts->expected_statuses[counts->entry_count])
}
if (status_flags != counts->expected_statuses[counts->entry_count])
counts->wrong_status_flags_count++; counts->wrong_status_flags_count++;
exit:
counts->entry_count++; counts->entry_count++;
return 0; 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