Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2013-09-10 03:57:34 +0400
committerRussell Belfer <rb@github.com>2013-09-17 20:31:44 +0400
commit974774c7b00c08585b05ff87174872be005a1f29 (patch)
treecea2a1d9b29a4601b90fe23a47775eb39773dcb5 /src/filter.c
parent570ba25cb0f757f993e06df629faced32fdf2f8f (diff)
Add attributes to filters and fix registry
The filter registry as implemented was too primitive to actually work once multiple filters were coming into play. This expands the implementation of the registry to handle multiple prioritized filters correctly. Additionally, this adds an "attributes" field to a filter that makes it really really easy to implement filters that are based on one or more attribute values. The lookup and even simple value checking can all happen automatically without custom filter code. Lastly, with the registry improvements, this fills out the filter lifecycle callbacks, with initialize and shutdown callbacks that will be called before the filter is first used and after it is last invoked. This allows for system-wide initialization and cleanup by the filter.
Diffstat (limited to 'src/filter.c')
-rw-r--r--src/filter.c263
1 files changed, 243 insertions, 20 deletions
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