summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attr.c8
-rw-r--r--src/crlf.c55
-rw-r--r--src/filter.c263
3 files changed, 264 insertions, 62 deletions
diff --git a/src/attr.c b/src/attr.c
index 6cdff29f9..7946db4d6 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -26,7 +26,6 @@ git_attr_t git_attr_value(const char *attr)
return GIT_ATTR_VALUE_T;
}
-
static int collect_attr_files(
git_repository *repo,
uint32_t flags,
@@ -103,8 +102,6 @@ int git_attr_get_many(
attr_get_many_info *info = NULL;
size_t num_found = 0;
- memset((void *)values, 0, sizeof(const char *) * num_attr);
-
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
return -1;
@@ -141,6 +138,11 @@ int git_attr_get_many(
}
}
+ for (k = 0; k < num_attr; k++) {
+ if (!info[k].found)
+ values[k] = NULL;
+ }
+
cleanup:
git_vector_free(&files);
git_attr_path__free(&path);
diff --git a/src/crlf.c b/src/crlf.c
index cfc2d1eb1..1242450d8 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -74,39 +74,6 @@ static int crlf_input_action(struct crlf_attrs *ca)
return ca->crlf_action;
}
-static int crlf_load_attributes(
- struct crlf_attrs *ca, git_repository *repo, const char *path)
-{
-#define NUM_CONV_ATTRS 3
-
- static const char *attr_names[NUM_CONV_ATTRS] = {
- "crlf", "eol", "text",
- };
-
- const char *attr_vals[NUM_CONV_ATTRS];
- int error;
-
- error = git_attr_get_many(attr_vals,
- repo, 0, path, NUM_CONV_ATTRS, attr_names);
-
- if (error == GIT_ENOTFOUND) {
- ca->crlf_action = GIT_CRLF_GUESS;
- ca->eol = GIT_EOL_UNSET;
- return 0;
- }
-
- if (error == 0) {
- ca->crlf_action = check_crlf(attr_vals[2]); /* text */
- if (ca->crlf_action == GIT_CRLF_GUESS)
- ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */
-
- ca->eol = check_eol(attr_vals[1]); /* eol */
- return 0;
- }
-
- return -1;
-}
-
static int has_cr_in_index(const git_filter_source *src)
{
git_repository *repo = git_filter_source_repo(src);
@@ -283,7 +250,8 @@ static int crlf_check(
git_filter *self,
void **payload, /* points to NULL ptr on entry, may be set */
git_filter_mode_t mode,
- const git_filter_source *src)
+ const git_filter_source *src,
+ const char **attr_values)
{
int error;
struct crlf_attrs ca;
@@ -291,11 +259,16 @@ static int crlf_check(
GIT_UNUSED(self);
GIT_UNUSED(mode);
- /* Load gitattributes for the path */
- error = crlf_load_attributes(
- &ca, git_filter_source_repo(src), git_filter_source_path(src));
- if (error < 0)
- return error;
+ if (!attr_values) {
+ ca.crlf_action = GIT_CRLF_GUESS;
+ ca.eol = GIT_EOL_UNSET;
+ } else {
+ ca.crlf_action = check_crlf(attr_values[2]); /* text */
+ if (ca.crlf_action == GIT_CRLF_GUESS)
+ ca.crlf_action = check_crlf(attr_values[0]); /* clrf */
+ ca.eol = check_eol(attr_values[1]); /* eol */
+ }
+ ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
/*
* Use the core Git logic to see if we should perform CRLF for this file
@@ -350,7 +323,11 @@ static void crlf_cleanup(
git_filter *git_crlf_filter_new(void)
{
struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
+
f->f.version = GIT_FILTER_VERSION;
+ f->f.attributes = "crlf eol text";
+ f->f.initialize = NULL;
+ f->f.shutdown = NULL;
f->f.check = crlf_check;
f->f.apply = crlf_apply;
f->f.cleanup = crlf_cleanup;
diff --git a/src/filter.c b/src/filter.c
index 3d4c6d6ce..7fbc20a41 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -12,6 +12,7 @@
#include "repository.h"
#include "git2/config.h"
#include "blob.h"
+#include "attr_file.h"
struct git_filter_source {
git_repository *repo;
@@ -35,9 +36,187 @@ struct git_filter_list {
typedef struct {
const char *filter_name;
git_filter *filter;
+ int priority;
+ size_t nattrs, nmatches;
+ char *attrdata;
+ const char *attrs[GIT_FLEX_ARRAY];
} git_filter_def;
-static git_array_t(git_filter_def) filter_registry = GIT_ARRAY_INIT;
+static int filter_def_priority_cmp(const void *a, const void *b)
+{
+ int pa = ((const git_filter_def *)a)->priority;
+ int pb = ((const git_filter_def *)b)->priority;
+ return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
+}
+
+static git_vector git__filter_registry = {
+ 0, filter_def_priority_cmp, NULL, 0, 0
+};
+
+static int filter_def_scan_attrs(
+ git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
+{
+ const char *start, *scan = attr_str;
+ int has_eq;
+
+ *nattr = *nmatch = 0;
+
+ if (!scan)
+ return 0;
+
+ while (*scan) {
+ while (git__isspace(*scan)) scan++;
+
+ for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
+ if (*scan == '=')
+ has_eq = 1;
+ }
+
+ if (scan > start) {
+ (*nattr)++;
+ if (has_eq || *scan == '-' || *scan == '+' || *scan == '!')
+ (*nmatch)++;
+
+ if (has_eq)
+ git_buf_putc(attrs, '=');
+ git_buf_put(attrs, start, scan - start);
+ git_buf_putc(attrs, '\0');
+ }
+ }
+
+ return 0;
+}
+
+static void filter_def_set_attrs(git_filter_def *fdef)
+{
+ char *scan = fdef->attrdata;
+ size_t i;
+
+ for (i = 0; i < fdef->nattrs; ++i) {
+ const char *name, *value;
+
+ switch (*scan) {
+ case '=':
+ name = scan + 1;
+ for (scan++; *scan != '='; scan++) /* find '=' */;
+ *scan++ = '\0';
+ value = scan;
+ break;
+ case '-':
+ name = scan + 1; value = git_attr__false; break;
+ case '+':
+ name = scan + 1; value = git_attr__true; break;
+ case '!':
+ name = scan + 1; value = git_attr__unset; break;
+ default:
+ name = scan; value = NULL; break;
+ }
+
+ fdef->attrs[i] = name;
+ fdef->attrs[i + fdef->nattrs] = value;
+
+ scan += strlen(scan) + 1;
+ }
+}
+
+int git_filter_register(
+ const char *name, git_filter *filter, int priority)
+{
+ git_filter_def *fdef;
+ size_t nattr = 0, nmatch = 0;
+ git_buf attrs = GIT_BUF_INIT;
+
+ if (git_filter_lookup(name) != NULL) {
+ giterr_set(
+ GITERR_FILTER, "Attempt to reregister existing filter '%s'", name);
+ return -1;
+ }
+
+ if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
+ return -1;
+
+ fdef = git__calloc(
+ sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1);
+ GITERR_CHECK_ALLOC(fdef);
+
+ fdef->filter_name = name;
+ fdef->filter = filter;
+ fdef->priority = priority;
+ fdef->nattrs = nattr;
+ fdef->nmatches = nmatch;
+ fdef->attrdata = git_buf_detach(&attrs);
+
+ filter_def_set_attrs(fdef);
+
+ if (git_vector_insert(&git__filter_registry, fdef) < 0) {
+ git__free(fdef->attrdata);
+ git__free(fdef);
+ return -1;
+ }
+
+ git_vector_sort(&git__filter_registry);
+ return 0;
+}
+
+static int filter_def_name_key_check(const void *key, const void *fdef)
+{
+ const char *name =
+ fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
+ return name ? -1 : git__strcmp(key, name);
+}
+
+static git_filter_def *filter_find_by_name(size_t *pos, const char *name)
+{
+ git_filter_def *fdef = NULL;
+
+ if (!git_vector_search2(
+ pos, &git__filter_registry, filter_def_name_key_check, name))
+ fdef = git_vector_get(&git__filter_registry, *pos);
+
+ return fdef;
+}
+
+int git_filter_unregister(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef;
+
+ /* cannot unregister default filters */
+ if (!strcmp(GIT_FILTER_CRLF, name)) {
+ giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name);
+ return -1;
+ }
+
+ if ((fdef = filter_find_by_name(&pos, name)) == NULL) {
+ giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name);
+ return GIT_ENOTFOUND;
+ }
+
+ (void)git_vector_remove(&git__filter_registry, pos);
+
+ if (fdef->filter->shutdown)
+ fdef->filter->shutdown(fdef->filter);
+
+ git__free(fdef->attrdata);
+ git__free(fdef);
+
+ return 0;
+}
+
+git_filter *git_filter_lookup(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef = filter_find_by_name(&pos, name);
+ return fdef ? fdef->filter : NULL;
+}
+
+static int filter_load_defaults(void)
+{
+ if (!git_vector_length(&git__filter_registry))
+ return git_filter_register(GIT_FILTER_CRLF, git_crlf_filter_new(), 0);
+
+ return 0;
+}
git_repository *git_filter_source_repo(const git_filter_source *src)
{
@@ -59,20 +238,6 @@ const git_oid *git_filter_source_id(const git_filter_source *src)
return git_oid_iszero(&src->oid) ? NULL : &src->oid;
}
-static int filter_load_defaults(void)
-{
- if (!git_array_size(filter_registry)) {
- git_filter_def *fdef = git_array_alloc(filter_registry);
- GITERR_CHECK_ALLOC(fdef);
-
- fdef->filter_name = GIT_FILTER_CRLF;
- fdef->filter = git_crlf_filter_new();
- GITERR_CHECK_ALLOC(fdef->filter);
- }
-
- return 0;
-}
-
static int git_filter_list_new(
git_filter_list **out, git_filter_mode_t mode, const git_filter_source *src)
{
@@ -92,6 +257,47 @@ static int git_filter_list_new(
return 0;
}
+static int filter_list_check_attributes(
+ const char ***out, git_filter_def *fdef, const git_filter_source *src)
+{
+ int error;
+ size_t i;
+ const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
+ GITERR_CHECK_ALLOC(strs);
+
+ error = git_attr_get_many(
+ strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs);
+
+ /* if no values were found but no matches are needed, it's okay! */
+ if (error == GIT_ENOTFOUND && !fdef->nmatches) {
+ giterr_clear();
+ git__free(strs);
+ return 0;
+ }
+
+ for (i = 0; !error && i < fdef->nattrs; ++i) {
+ const char *want = fdef->attrs[fdef->nattrs + i];
+ git_attr_t want_type, found_type;
+
+ if (!want)
+ continue;
+
+ want_type = git_attr_value(want);
+ found_type = git_attr_value(strs[i]);
+
+ if (want_type != found_type ||
+ (want_type == GIT_ATTR_VALUE_T && strcmp(want, strs[i])))
+ error = GIT_ENOTFOUND;
+ }
+
+ if (error)
+ git__free(strs);
+ else
+ *out = strs;
+
+ return error;
+}
+
int git_filter_list_load(
git_filter_list **filters,
git_repository *repo,
@@ -102,7 +308,8 @@ int git_filter_list_load(
git_filter_list *fl = NULL;
git_filter_source src = { 0 };
git_filter_entry *fe;
- uint32_t f;
+ size_t idx;
+ git_filter_def *fdef;
if (filter_load_defaults() < 0)
return -1;
@@ -110,15 +317,27 @@ int git_filter_list_load(
src.repo = repo;
src.path = path;
- for (f = 0; f < git_array_size(filter_registry); ++f) {
+ git_vector_foreach(&git__filter_registry, idx, fdef) {
+ const char **values = NULL;
void *payload = NULL;
- git_filter_def *fdef = git_array_get(filter_registry, f);
if (!fdef || !fdef->filter)
continue;
+ if (fdef->nattrs > 0) {
+ error = filter_list_check_attributes(&values, fdef, &src);
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ continue;
+ } else if (error < 0)
+ break;
+ }
+
if (fdef->filter->check)
- error = fdef->filter->check(fdef->filter, &payload, mode, &src);
+ error = fdef->filter->check(
+ fdef->filter, &payload, mode, &src, values);
+
+ git__free(values);
if (error == GIT_ENOTFOUND)
error = 0;
@@ -171,6 +390,7 @@ int git_filter_list_apply(
uint32_t i;
unsigned int src;
git_buf *dbuffer[2];
+ git_filter_entry *fe;
if (!fl) {
git_buf_swap(dest, source);
@@ -188,11 +408,14 @@ int git_filter_list_apply(
return -1;
for (i = 0; i < git_array_size(fl->filters); ++i) {
- git_filter_entry *fe = git_array_get(fl->filters, i);
unsigned int dst = 1 - src;
git_buf_clear(dbuffer[dst]);
+ fe = git_array_get(
+ fl->filters, (fl->mode == GIT_FILTER_TO_ODB) ?
+ i : git_array_size(fl->filters) - 1 - i);
+
/* Apply the filter from dbuffer[src] to the other buffer;
* if the filtering is canceled by the user mid-filter,
* we skip to the next filter without changing the source