summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/errors.h1
-rw-r--r--include/git2/sys/filter.h101
-rw-r--r--src/attr.c8
-rw-r--r--src/crlf.c55
-rw-r--r--src/filter.c263
-rw-r--r--tests-clar/attr/repo.c16
6 files changed, 364 insertions, 80 deletions
diff --git a/include/git2/errors.h b/include/git2/errors.h
index dc4486ade..a454ac956 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -68,6 +68,7 @@ typedef enum {
GITERR_FETCHHEAD,
GITERR_MERGE,
GITERR_SSH,
+ GITERR_FILTER,
} git_error_t;
/**
diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h
index b1193a538..b0a753019 100644
--- a/include/git2/sys/filter.h
+++ b/include/git2/sys/filter.h
@@ -46,16 +46,26 @@ GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src);
*/
GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src);
+/*
+ * struct git_filter
+ *
+ * The filter lifecycle:
+ * - initialize - first use of filter
+ * - shutdown - filter removed/unregistered from system
+ * - check - considering for file
+ * - apply - applied to file
+ * - cleanup - done with file
+ */
+
/**
- * Callback to actually perform the data filtering
+ * Initialize callback on filter
*/
-typedef int (*git_filter_apply_fn)(
- git_filter *self,
- void **payload, /* may be read and/or set */
- git_filter_mode_t mode,
- git_buffer *to,
- const git_buffer *from,
- const git_filter_source *src);
+typedef int (*git_filter_init_fn)(git_filter *self);
+
+/**
+ * Shutdown callback on filter
+ */
+typedef void (*git_filter_shutdown_fn)(git_filter *self);
/**
* Callback to decide if a given source needs this filter
@@ -64,6 +74,18 @@ typedef int (*git_filter_check_fn)(
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 char **attr_values);
+
+/**
+ * Callback to actually perform the data filtering
+ */
+typedef int (*git_filter_apply_fn)(
+ git_filter *self,
+ void **payload, /* may be read and/or set */
+ git_filter_mode_t mode,
+ git_buffer *to,
+ const git_buffer *from,
const git_filter_source *src);
/**
@@ -83,10 +105,32 @@ typedef void (*git_filter_cleanup_fn)(
*
* `version` should be set to GIT_FILTER_VERSION
*
- * `apply` is the callback that actually filters data.
+ * `attributes` is a list of attributes to check on a file to see if the
+ * filter applies. The format is a whitespace-delimited list of names
+ * (like "eol crlf text"). Each name may have an optional value that will
+ * be tested even without a `check` callback. If the value does not
+ * match, the filter will be skipped. The values are specified as in a
+ * .gitattributes file (e.g. "myattr=foobar" or "myattr" or "-myattr").
+ * If a check function is supplied, then the values of the attributes will
+ * be passed to that function.
+ *
+ * `initialize` is an optional callback invoked before a filter is first
+ * used. It will be called once at most.
+ *
+ * `shutdown` is an optional callback invoked when the filter is
+ * unregistered or when libgit2 is shutting down. It will be called once
+ * at most and should free any memory as needed.
*
* `check` is an optional callback that checks if filtering is needed for
- * a given source.
+ * a given source. It should return 0 if the filter should be applied
+ * (i.e. success), GIT_ENOTFOUND if the filter should not be applied, or
+ * an other error code to fail out of the filter processing pipeline and
+ * return to the caller.
+ *
+ * `apply` is the callback that actually filters data. If it successfully
+ * writes the output, it should return 0. Like `check`, it can return
+ * GIT_ENOTFOUND to indicate that the filter doesn't actually want to run.
+ * Other error codes will stop filter processing and return to the caller.
*
* `cleanup` is an optional callback that is made after the filter has
* been applied. Both the `check` and `apply` callbacks are able to
@@ -94,25 +138,46 @@ typedef void (*git_filter_cleanup_fn)(
* is given that value and can clean up as needed.
*/
struct git_filter {
- unsigned int version;
- git_filter_apply_fn apply;
- git_filter_check_fn check;
- git_filter_cleanup_fn cleanup;
+ unsigned int version;
+ const char *attributes;
+ git_filter_init_fn initialize;
+ git_filter_shutdown_fn shutdown;
+ git_filter_check_fn check;
+ git_filter_apply_fn apply;
+ git_filter_cleanup_fn cleanup;
};
#define GIT_FILTER_VERSION 1
/**
- * Register a filter under a given name
+ * Register a filter under a given name with a given priority.
+ *
+ * If non-NULL, the filter's initialize callback will be invoked before
+ * the first use of the filter, so you can defer expensive operations (in
+ * case libgit2 is being used in a way that doesn't need the filter).
*
- * Two filters will be preregistered with libgit2: GIT_FILTER_CRLF and
- * GIT_FILTER_IDENT.
+ * A filter's attribute checks and `check` and `apply` callbacks will be
+ * issued in order of `priority` on smudge (to workdir), and in reverse
+ * order of `priority` on clean (to odb).
+ *
+ * One filter will be preregistered with libgit2:
+ * - GIT_FILTER_CRLF with priority of 0.
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
*/
GIT_EXTERN(int) git_filter_register(
- const char *name, const git_filter *filter);
+ const char *name, git_filter *filter, int priority);
/**
* Remove the filter with the given name
+ *
+ * It is not allowed to remove the builtin libgit2 filters.
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
*/
GIT_EXTERN(int) git_filter_unregister(const char *name);
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
diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c
index ca3e71e7f..ef2ad5ce9 100644
--- a/tests-clar/attr/repo.c
+++ b/tests-clar/attr/repo.c
@@ -100,6 +100,22 @@ void test_attr_repo__get_many(void)
cl_assert_equal_s("yes", values[3]);
}
+void test_attr_repo__get_many_in_place(void)
+{
+ const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
+
+ /* it should be legal to look up values into the same array that has
+ * the attribute names, overwriting each name as the value is found.
+ */
+
+ cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals));
+
+ cl_assert(GIT_ATTR_TRUE(vals[0]));
+ cl_assert(GIT_ATTR_TRUE(vals[1]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(vals[2]));
+ cl_assert_equal_s("yes", vals[3]);
+}
+
static int count_attrs(
const char *name,
const char *value,