Commit a51cd8e6 by Russell Belfer

Fix handling of relative paths for attrs

Per issue #533, the handling of relative paths in attribute
and ignore files was not right.  Fixed this by pre-joining
the relative path of the attribute/ignore file onto the match
string when a full path match is required.

Unfortunately, fixing this required a bit more code than I
would have liked because I had to juggle things around so that
the fnmatch parser would have sufficient information to prepend
the relative path when it was needed.
parent 6e03b12f
......@@ -214,7 +214,7 @@ int git_attr_cache__push_file(
git_vector *stack,
const char *base,
const char *filename,
int (*loader)(git_repository *, const char *, git_attr_file **))
int (*loader)(git_repository *, const char *, git_attr_file *))
{
int error = GIT_SUCCESS;
git_attr_cache *cache = &repo->attrcache;
......@@ -231,11 +231,12 @@ int git_attr_cache__push_file(
/* either get attr_file from cache or read from disk */
file = git_hashtable_lookup(cache->files, filename);
if (file == NULL && git_futils_exists(filename) == GIT_SUCCESS) {
error = (*loader)(repo, filename, &file);
if ((error = git_attr_file__new(&file)) == GIT_SUCCESS)
error = (*loader)(repo, filename, file);
add_to_cache = (error == GIT_SUCCESS);
}
if (file != NULL) {
if (error == GIT_SUCCESS && file != NULL) {
/* add file to vector, if we found it */
error = git_vector_insert(stack, file);
......
......@@ -25,6 +25,6 @@ extern int git_attr_cache__push_file(
git_vector *stack,
const char *base,
const char *filename,
int (*loader)(git_repository *, const char *, git_attr_file **));
int (*loader)(git_repository *, const char *, git_attr_file *));
#endif
......@@ -31,21 +31,46 @@ int git_attr_file__new(git_attr_file **attrs_ptr)
return error;
}
int git_attr_file__set_path(
git_repository *repo, const char *path, git_attr_file *file)
{
if (file->path != NULL) {
git__free(file->path);
file->path = NULL;
}
if (repo == NULL)
file->path = git__strdup(path);
else {
const char *workdir = git_repository_workdir(repo);
if (workdir && git__prefixcmp(path, workdir) == 0)
file->path = git__strdup(path + strlen(workdir));
else
file->path = git__strdup(path);
}
return (file->path == NULL) ? GIT_ENOMEM : GIT_SUCCESS;
}
int git_attr_file__from_buffer(
git_repository *repo, const char *buffer, git_attr_file **out)
git_repository *repo, const char *buffer, git_attr_file *attrs)
{
int error = GIT_SUCCESS;
git_attr_file *attrs = NULL;
const char *scan = NULL;
char *context = NULL;
git_attr_rule *rule = NULL;
*out = NULL;
if ((error = git_attr_file__new(&attrs)) < GIT_SUCCESS)
goto cleanup;
assert(buffer && attrs);
scan = buffer;
if (attrs->path && git__suffixcmp(attrs->path, GIT_ATTR_FILE) == 0) {
context = git__strndup(attrs->path,
strlen(attrs->path) - strlen(GIT_ATTR_FILE));
if (!context) error = GIT_ENOMEM;
}
while (error == GIT_SUCCESS && *scan) {
/* allocate rule if needed */
if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
......@@ -54,7 +79,7 @@ int git_attr_file__from_buffer(
}
/* parse the next "pattern attr attr attr" line */
if (!(error = git_attr_fnmatch__parse(&rule->match, &scan)) &&
if (!(error = git_attr_fnmatch__parse(&rule->match, context, &scan)) &&
!(error = git_attr_assignment__parse(repo, &rule->assigns, &scan)))
{
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
......@@ -76,35 +101,30 @@ int git_attr_file__from_buffer(
}
}
cleanup:
if (error != GIT_SUCCESS) {
git_attr_rule__free(rule);
git_attr_file__free(attrs);
} else {
*out = attrs;
}
git_attr_rule__free(rule);
git__free(context);
return error;
}
int git_attr_file__from_file(
git_repository *repo, const char *path, git_attr_file **out)
git_repository *repo, const char *path, git_attr_file *file)
{
int error = GIT_SUCCESS;
git_fbuffer fbuf = GIT_FBUFFER_INIT;
*out = NULL;
assert(path && file);
if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS ||
(error = git_attr_file__from_buffer(repo, fbuf.data, out)) < GIT_SUCCESS)
{
git__rethrow(error, "Could not open attribute file '%s'", path);
} else {
/* save path (okay to fail) */
(*out)->path = git__strdup(path);
}
if (file->path == NULL)
error = git_attr_file__set_path(repo, path, file);
if (error == GIT_SUCCESS &&
(error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS)
error = git_attr_file__from_buffer(repo, fbuf.data, file);
git_futils_freebuffer(&fbuf);
if (error != GIT_SUCCESS)
git__rethrow(error, "Could not open attribute file '%s'", path);
return error;
}
......@@ -267,6 +287,7 @@ int git_attr_path__init(
*/
int git_attr_fnmatch__parse(
git_attr_fnmatch *spec,
const char *source,
const char **base)
{
const char *pattern, *scan;
......@@ -312,32 +333,40 @@ int git_attr_fnmatch__parse(
*base = scan;
spec->length = scan - pattern;
spec->pattern = git__strndup(pattern, spec->length);
if (!spec->pattern) {
*base = git__next_line(pattern);
return GIT_ENOMEM;
} else {
/* remove '\' that might have be used for internal whitespace */
char *from = spec->pattern, *to = spec->pattern;
while (*from) {
if (*from == '\\') {
from++;
spec->length--;
}
*to++ = *from++;
}
*to = '\0';
}
if (pattern[spec->length - 1] == '/') {
spec->length--;
spec->pattern[spec->length] = '\0';
spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
if (--slash_count <= 0)
spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
}
if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
source != NULL && git_path_root(pattern) < 0)
{
size_t sourcelen = strlen(source);
/* given an unrooted fullpath match from a file inside a repo,
* prefix the pattern with the relative directory of the source file
*/
spec->pattern = git__malloc(sourcelen + spec->length + 1);
if (spec->pattern) {
memcpy(spec->pattern, source, sourcelen);
memcpy(spec->pattern + sourcelen, pattern, spec->length);
spec->length += sourcelen;
spec->pattern[spec->length] = '\0';
}
} else {
spec->pattern = git__strndup(pattern, spec->length);
}
if (!spec->pattern) {
*base = git__next_line(pattern);
return GIT_ENOMEM;
} else {
/* remove '\' that might have be used for internal whitespace */
spec->length = git__removechar(spec->pattern, '\\');
}
return GIT_SUCCESS;
}
......
......@@ -62,13 +62,16 @@ typedef struct {
* git_attr_file API
*/
extern int git_attr_file__new(git_attr_file **attrs_ptr);
extern void git_attr_file__free(git_attr_file *file);
extern int git_attr_file__from_buffer(
git_repository *repo, const char *buf, git_attr_file **out);
git_repository *repo, const char *buf, git_attr_file *file);
extern int git_attr_file__from_file(
git_repository *repo, const char *path, git_attr_file **out);
git_repository *repo, const char *path, git_attr_file *file);
extern int git_attr_file__new(git_attr_file **attrs_ptr);
extern void git_attr_file__free(git_attr_file *file);
extern int git_attr_file__set_path(
git_repository *repo, const char *path, git_attr_file *file);
extern int git_attr_file__lookup_one(
git_attr_file *file,
......@@ -90,6 +93,7 @@ extern unsigned long git_attr_file__name_hash(const char *name);
extern int git_attr_fnmatch__parse(
git_attr_fnmatch *spec,
const char *source,
const char **base);
extern int git_attr_fnmatch__match(
......
......@@ -8,22 +8,25 @@
#define GIT_IGNORE_CONFIG "core.excludesfile"
static int load_ignore_file(
git_repository *GIT_UNUSED(repo), const char *path, git_attr_file **out)
git_repository *repo, const char *path, git_attr_file *ignores)
{
int error = GIT_SUCCESS;
git_fbuffer fbuf = GIT_FBUFFER_INIT;
git_attr_file *ignores = NULL;
git_attr_fnmatch *match = NULL;
const char *scan = NULL;
char *context = NULL;
GIT_UNUSED_ARG(repo);
if (ignores->path == NULL)
error = git_attr_file__set_path(repo, path, ignores);
*out = NULL;
if ((error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS)
error = git_attr_file__new(&ignores);
if (git__suffixcmp(ignores->path, GIT_IGNORE_FILE) == 0) {
context = git__strndup(ignores->path,
strlen(ignores->path) - strlen(GIT_IGNORE_FILE));
if (!context) error = GIT_ENOMEM;
}
ignores->path = git__strdup(path);
if (error == GIT_SUCCESS)
error = git_futils_readbuffer(&fbuf, path);
scan = fbuf.data;
......@@ -33,7 +36,7 @@ static int load_ignore_file(
break;
}
if (!(error = git_attr_fnmatch__parse(match, &scan))) {
if (!(error = git_attr_fnmatch__parse(match, context, &scan))) {
match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE;
scan = git__next_line(scan);
error = git_vector_insert(&ignores->rules, match);
......@@ -52,13 +55,10 @@ static int load_ignore_file(
git_futils_freebuffer(&fbuf);
git__free(match);
git__free(context);
if (error != GIT_SUCCESS) {
if (error != GIT_SUCCESS)
git__rethrow(error, "Could not open ignore file '%s'", path);
git_attr_file__free(ignores);
} else {
*out = ignores;
}
return error;
}
......
......@@ -156,6 +156,23 @@ void git__strtolower(char *str)
git__strntolower(str, strlen(str));
}
size_t git__removechar(char *str, char remove)
{
char *from = str, *to = str;
while (*from) {
if (*from == remove)
from++;
if (to != from)
*to = *from;
to++;
from++;
}
*to = '\0';
return (to - str);
}
int git__prefixcmp(const char *str, const char *prefix)
{
for (;;) {
......
......@@ -102,6 +102,8 @@ extern char *git__strtok(char **end, const char *sep);
extern void git__strntolower(char *str, size_t len);
extern void git__strtolower(char *str);
extern size_t git__removechar(char *str, char remove);
GIT_INLINE(const char *) git__next_line(const char *s)
{
while (*s && *s != '\n') s++;
......
......@@ -6,11 +6,12 @@
void test_attr_file__simple_read(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_assignment *assign;
git_attr_rule *rule;
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), file));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1);
......@@ -32,11 +33,12 @@ void test_attr_file__simple_read(void)
void test_attr_file__match_variants(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), file));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10);
......@@ -119,11 +121,12 @@ static void check_one_assign(
void test_attr_file__assign_variants(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), file));
cl_assert_strequal(cl_fixture("attr/attr2"), file->path);
cl_assert(file->rules.length == 11);
......@@ -184,11 +187,12 @@ void test_attr_file__assign_variants(void)
void test_attr_file__check_attr_examples(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), file));
cl_assert_strequal(cl_fixture("attr/attr3"), file->path);
cl_assert(file->rules.length == 3);
......
......@@ -3,11 +3,12 @@
void test_attr_lookup__simple(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_path path;
const char *value = NULL;
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), file));
cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
cl_assert(file->rules.length == 1);
......@@ -60,7 +61,7 @@ static void run_test_cases(git_attr_file *file, test_case *cases)
void test_attr_lookup__match_variants(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
git_attr_path path;
test_case cases[] = {
/* pat0 -> simple match */
......@@ -132,7 +133,8 @@ void test_attr_lookup__match_variants(void)
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), file));
cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
cl_assert(file->rules.length == 10);
......@@ -146,7 +148,7 @@ void test_attr_lookup__match_variants(void)
void test_attr_lookup__assign_variants(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
test_case cases[] = {
/* pat0 -> simple assign */
{ "pat0", "simple", GIT_ATTR_TRUE, 0, 0 },
......@@ -190,7 +192,8 @@ void test_attr_lookup__assign_variants(void)
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), file));
cl_assert(file->rules.length == 11);
run_test_cases(file, cases);
......@@ -200,7 +203,7 @@ void test_attr_lookup__assign_variants(void)
void test_attr_lookup__check_attr_examples(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
test_case cases[] = {
{ "foo.java", "diff", "java", 1, 0 },
{ "foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
......@@ -224,7 +227,8 @@ void test_attr_lookup__check_attr_examples(void)
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), file));
cl_assert(file->rules.length == 3);
run_test_cases(file, cases);
......@@ -234,7 +238,7 @@ void test_attr_lookup__check_attr_examples(void)
void test_attr_lookup__from_buffer(void)
{
git_attr_file *file = NULL;
git_attr_file *file;
test_case cases[] = {
{ "abc", "foo", GIT_ATTR_TRUE, 0, 0 },
{ "abc", "bar", GIT_ATTR_TRUE, 0, 0 },
......@@ -248,7 +252,8 @@ void test_attr_lookup__from_buffer(void)
{ NULL, NULL, NULL, 0, 0 }
};
cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", &file));
cl_git_pass(git_attr_file__new(&file));
cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", file));
cl_assert(file->rules.length == 3);
run_test_cases(file, cases);
......
......@@ -45,19 +45,24 @@ void test_attr_repo__get_one(void)
{ "root_test3", "rootattr", NULL },
{ "root_test3", "multiattr", "3" },
{ "root_test3", "multi2", NULL },
{ "subdir/subdir_test1", "repoattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test1", "rootattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test1", "missingattr", NULL },
{ "subdir/subdir_test1", "subattr", "yes" },
{ "subdir/subdir_test1", "negattr", GIT_ATTR_FALSE },
{ "subdir/subdir_test1", "another", NULL },
{ "subdir/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE },
{ "subdir/subdir_test2.txt", "missingattr", NULL },
{ "subdir/subdir_test2.txt", "subattr", "yes" },
{ "subdir/subdir_test2.txt", "negattr", GIT_ATTR_FALSE },
{ "subdir/subdir_test2.txt", "another", "one" },
{ "sub/subdir_test1", "repoattr", GIT_ATTR_TRUE },
{ "sub/subdir_test1", "rootattr", GIT_ATTR_TRUE },
{ "sub/subdir_test1", "missingattr", NULL },
{ "sub/subdir_test1", "subattr", "yes" },
{ "sub/subdir_test1", "negattr", GIT_ATTR_FALSE },
{ "sub/subdir_test1", "another", NULL },
{ "sub/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE },
{ "sub/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE },
{ "sub/subdir_test2.txt", "missingattr", NULL },
{ "sub/subdir_test2.txt", "subattr", "yes" },
{ "sub/subdir_test2.txt", "negattr", GIT_ATTR_FALSE },
{ "sub/subdir_test2.txt", "another", "zero" },
{ "sub/subdir_test2.txt", "reposub", GIT_ATTR_TRUE },
{ "sub/sub/subdir.txt", "another", "one" },
{ "sub/sub/subdir.txt", "reposubsub", GIT_ATTR_TRUE },
{ "sub/sub/subdir.txt", "reposub", NULL },
{ "does-not-exist", "foo", "yes" },
{ "sub/deep/file", "deepdeep", GIT_ATTR_TRUE },
{ NULL, NULL, NULL }
}, *scan;
......@@ -105,7 +110,7 @@ void test_attr_repo__get_many(void)
cl_assert(values[2] == NULL);
cl_assert(values[3] == NULL);
cl_git_pass(git_attr_get_many(g_repo, "subdir/subdir_test1", 4, names, values));
cl_git_pass(git_attr_get_many(g_repo, "sub/subdir_test1", 4, names, values));
cl_assert(values[0] == GIT_ATTR_TRUE);
cl_assert(values[1] == GIT_ATTR_TRUE);
......@@ -136,33 +141,33 @@ void test_attr_repo__foreach(void)
cl_assert(count == 2);
count = 0;
cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test1",
cl_git_pass(git_attr_foreach(g_repo, "sub/subdir_test1",
&count_attrs, &count));
cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */
count = 0;
cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test2.txt",
cl_git_pass(git_attr_foreach(g_repo, "sub/subdir_test2.txt",
&count_attrs, &count));
cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */
cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */
}
void test_attr_repo__manpage_example(void)
{
const char *value;
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "foo", &value));
cl_git_pass(git_attr_get(g_repo, "sub/abc", "foo", &value));
cl_assert(value == GIT_ATTR_TRUE);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "bar", &value));
cl_git_pass(git_attr_get(g_repo, "sub/abc", "bar", &value));
cl_assert(value == NULL);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "baz", &value));
cl_git_pass(git_attr_get(g_repo, "sub/abc", "baz", &value));
cl_assert(value == GIT_ATTR_FALSE);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "merge", &value));
cl_git_pass(git_attr_get(g_repo, "sub/abc", "merge", &value));
cl_assert_strequal("filfre", value);
cl_git_pass(git_attr_get(g_repo, "subdir/abc", "frotz", &value));
cl_git_pass(git_attr_get(g_repo, "sub/abc", "frotz", &value));
cl_assert(value == NULL);
}
......
#include "clay_libgit2.h"
#include "fileops.h"
#include "git2/attr.h"
static git_repository *g_repo = NULL;
void test_status_ignore__initialize(void)
{
/* Before each test, instantiate the attr repo from the fixtures and
* rename the .gitted to .git so it is a repo with a working dir. Also
* rename gitignore to .gitignore.
*/
cl_fixture_sandbox("attr");
cl_git_pass(p_rename("attr/.gitted", "attr/.git"));
cl_git_pass(p_rename("attr/gitignore", "attr/.gitignore"));
cl_git_pass(git_repository_open(&g_repo, "attr/.git"));
}
void test_status_ignore__cleanup(void)
{
git_repository_free(g_repo);
g_repo = NULL;
cl_fixture_cleanup("attr");
}
void test_status_ignore__0(void)
{
struct {
const char *path;
int expected;
} test_cases[] = {
{ "file", 0 },
{ "ign", 1 },
{ "sub", 1 },
{ "sub/file", 0 },
{ "sub/ign", 1 },
{ "sub/sub", 1 },
{ "sub/sub/file", 0 },
{ "sub/sub/ign", 1 },
{ "sub/sub/sub", 1 },
{ NULL, 0 }
}, *one_test;
for (one_test = test_cases; one_test->path != NULL; one_test++) {
int ignored;
cl_git_pass(git_status_should_ignore(g_repo, one_test->path, &ignored));
cl_assert_(ignored == one_test->expected, one_test->path);
}
}
* repoattr
a* foo !bar -baz
sub/*.txt reposub
sub/sub/*.txt reposubsub
* subattr=yes -negattr
subdir/*.txt another=one
*.txt another=zero
sub/*.txt another=one
ab* merge=filfre
abc -foo -bar
*.c frotz
deep/file deepdeep
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