diff options
Diffstat (limited to 'src/filter.c')
-rw-r--r-- | src/filter.c | 666 |
1 files changed, 624 insertions, 42 deletions
diff --git a/src/filter.c b/src/filter.c index 9f749dcbd..9f866fe88 100644 --- a/src/filter.c +++ b/src/filter.c @@ -10,68 +10,594 @@ #include "hash.h" #include "filter.h" #include "repository.h" +#include "global.h" +#include "git2/sys/filter.h" #include "git2/config.h" #include "blob.h" +#include "attr_file.h" +#include "array.h" -int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) +struct git_filter_source { + git_repository *repo; + const char *path; + git_oid oid; /* zero if unknown (which is likely) */ + uint16_t filemode; /* zero if unknown */ + git_filter_mode_t mode; +}; + +typedef struct { + git_filter *filter; + void *payload; +} git_filter_entry; + +struct git_filter_list { + git_array_t(git_filter_entry) filters; + git_filter_source source; + char path[GIT_FLEX_ARRAY]; +}; + +typedef struct { + const char *filter_name; + git_filter *filter; + int priority; + int initialized; + size_t nattrs, nmatches; + char *attrdata; + const char *attrs[GIT_FLEX_ARRAY]; +} git_filter_def; + +static int filter_def_priority_cmp(const void *a, const void *b) { - int error; + int pa = ((const git_filter_def *)a)->priority; + int pb = ((const git_filter_def *)b)->priority; + return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; +} - if (mode == GIT_FILTER_TO_ODB) { - /* Load the CRLF cleanup filter when writing to the ODB */ - error = git_filter_add__crlf_to_odb(filters, repo, path); - if (error < 0) - return error; - } else { - error = git_filter_add__crlf_to_workdir(filters, repo, path); - if (error < 0) - return error; +struct filter_registry { + git_vector filters; +}; + +static struct filter_registry *git__filter_registry = NULL; + +static void filter_registry_shutdown(void) +{ + struct filter_registry *reg = NULL; + size_t pos; + git_filter_def *fdef; + + if ((reg = git__swap(git__filter_registry, NULL)) == NULL) + return; + + git_vector_foreach(®->filters, pos, fdef) { + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->attrdata); + git__free(fdef); } - return (int)filters->length; + git_vector_free(®->filters); + git__free(reg); } -void git_filters_free(git_vector *filters) +static int filter_registry_initialize(void) { - size_t i; - git_filter *filter; + int error = 0; + struct filter_registry *reg; + + if (git__filter_registry) + return 0; + + reg = git__calloc(1, sizeof(struct filter_registry)); + GITERR_CHECK_ALLOC(reg); + + if ((error = git_vector_init( + ®->filters, 2, filter_def_priority_cmp)) < 0) + goto cleanup; + + reg = git__compare_and_swap(&git__filter_registry, NULL, reg); + if (reg != NULL) + goto cleanup; + + git__on_shutdown(filter_registry_shutdown); + + /* try to register both default filters */ + { + git_filter *crlf = git_crlf_filter_new(); + git_filter *ident = git_ident_filter_new(); + + if (crlf && git_filter_register( + GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0) + crlf = NULL; + if (ident && git_filter_register( + GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) + ident = NULL; + + if (!crlf || !ident) + return -1; + } + + return 0; + +cleanup: + git_vector_free(®->filters); + git__free(reg); + return error; +} + +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 || *start == '-' || *start == '+' || *start == '!') + (*nmatch)++; - git_vector_foreach(filters, i, filter) { - if (filter->do_free != NULL) - filter->do_free(filter); - else - git__free(filter); + if (has_eq) + git_buf_putc(attrs, '='); + git_buf_put(attrs, start, scan - start); + git_buf_putc(attrs, '\0'); + } } - git_vector_free(filters); + return 0; } -int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) +static void filter_def_set_attrs(git_filter_def *fdef) { + char *scan = fdef->attrdata; size_t i; - unsigned int src; - git_buf *dbuffer[2]; - dbuffer[0] = source; - dbuffer[1] = dest; + 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; + } +} + +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 ? git__strcmp(key, name) : -1; +} - src = 0; +static int filter_def_filter_key_check(const void *key, const void *fdef) +{ + const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; + return (key == filter) ? 0 : -1; +} + +static int filter_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2( + pos, &git__filter_registry->filters, filter_def_name_key_check, name); +} - if (git_buf_len(source) == 0) { - git_buf_clear(dest); +static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) +{ + git_filter_def *fdef = NULL; + + if (!filter_registry_find(pos, name)) + fdef = git_vector_get(&git__filter_registry->filters, *pos); + + return fdef; +} + +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 (filter_registry_initialize() < 0) + return -1; + + if (!filter_registry_find(NULL, name)) { + giterr_set( + GITERR_FILTER, "Attempt to reregister existing filter '%s'", name); + return GIT_EEXISTS; + } + + 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->filters, fdef) < 0) { + git__free(fdef->attrdata); + git__free(fdef); + return -1; + } + + git_vector_sort(&git__filter_registry->filters); + return 0; +} + +int git_filter_unregister(const char *name) +{ + size_t pos; + git_filter_def *fdef; + + /* cannot unregister default filters */ + if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { + giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name); + return -1; + } + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { + giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name); + return GIT_ENOTFOUND; + } + + (void)git_vector_remove(&git__filter_registry->filters, pos); + + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->attrdata); + git__free(fdef); + + return 0; +} + +static int filter_initialize(git_filter_def *fdef) +{ + int error = 0; + + if (!fdef->initialized && + fdef->filter && + fdef->filter->initialize && + (error = fdef->filter->initialize(fdef->filter)) < 0) + { + /* auto-unregister if initialize fails */ + git_filter_unregister(fdef->filter_name); + return error; + } + + fdef->initialized = true; + return 0; +} + +git_filter *git_filter_lookup(const char *name) +{ + size_t pos; + git_filter_def *fdef; + + if (filter_registry_initialize() < 0) + return NULL; + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) + return NULL; + + if (!fdef->initialized && filter_initialize(fdef) < 0) + return NULL; + + return fdef->filter; +} + +void git_filter_free(git_filter *filter) +{ + git__free(filter); +} + +git_repository *git_filter_source_repo(const git_filter_source *src) +{ + return src->repo; +} + +const char *git_filter_source_path(const git_filter_source *src) +{ + return src->path; +} + +uint16_t git_filter_source_filemode(const git_filter_source *src) +{ + return src->filemode; +} + +const git_oid *git_filter_source_id(const git_filter_source *src) +{ + return git_oid_iszero(&src->oid) ? NULL : &src->oid; +} + +git_filter_mode_t git_filter_source_mode(const git_filter_source *src) +{ + return src->mode; +} + +static int filter_list_new( + git_filter_list **out, const git_filter_source *src) +{ + git_filter_list *fl = NULL; + size_t pathlen = src->path ? strlen(src->path) : 0; + + fl = git__calloc(1, sizeof(git_filter_list) + pathlen + 1); + GITERR_CHECK_ALLOC(fl); + + if (src->path) + memcpy(fl->path, src->path, pathlen); + fl->source.repo = src->repo; + fl->source.path = fl->path; + fl->source.mode = src->mode; + + *out = fl; + 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((void *)strs); return 0; } - /* Pre-grow the destination buffer to more or less the size - * we expect it to have */ - if (git_buf_grow(dest, git_buf_len(source)) < 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((void *)strs); + else + *out = strs; + + return error; +} + +int git_filter_list_new( + git_filter_list **out, git_repository *repo, git_filter_mode_t mode) +{ + git_filter_source src = { 0 }; + src.repo = repo; + src.path = NULL; + src.mode = mode; + return filter_list_new(out, &src); +} + +int git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode) +{ + int error = 0; + git_filter_list *fl = NULL; + git_filter_source src = { 0 }; + git_filter_entry *fe; + size_t idx; + git_filter_def *fdef; + + if (filter_registry_initialize() < 0) + return -1; + + src.repo = repo; + src.path = path; + src.mode = mode; + if (blob) + git_oid_cpy(&src.oid, git_blob_id(blob)); + + git_vector_foreach(&git__filter_registry->filters, idx, fdef) { + const char **values = NULL; + void *payload = NULL; + + 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->initialized && (error = filter_initialize(fdef)) < 0) + break; + + if (fdef->filter->check) + error = fdef->filter->check( + fdef->filter, &payload, &src, values); + + git__free((void *)values); + + if (error == GIT_PASSTHROUGH) + error = 0; + else if (error < 0) + break; + else { + if (!fl && (error = filter_list_new(&fl, &src)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GITERR_CHECK_ALLOC(fe); + fe->filter = fdef->filter; + fe->payload = payload; + } + } + + if (error && fl != NULL) { + git_array_clear(fl->filters); + git__free(fl); + fl = NULL; + } + + *filters = fl; + return error; +} + +void git_filter_list_free(git_filter_list *fl) +{ + uint32_t i; + + if (!fl) + return; + + for (i = 0; i < git_array_size(fl->filters); ++i) { + git_filter_entry *fe = git_array_get(fl->filters, i); + if (fe->filter->cleanup) + fe->filter->cleanup(fe->filter, fe->payload); + } + + git_array_clear(fl->filters); + git__free(fl); +} + +int git_filter_list_push( + git_filter_list *fl, git_filter *filter, void *payload) +{ + int error = 0; + size_t pos; + git_filter_def *fdef; + git_filter_entry *fe; + + assert(fl && filter); + + if (git_vector_search2( + &pos, &git__filter_registry->filters, + filter_def_filter_key_check, filter) < 0) { + giterr_set(GITERR_FILTER, "Cannot use an unregistered filter"); return -1; + } + + fdef = git_vector_get(&git__filter_registry->filters, pos); + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GITERR_CHECK_ALLOC(fe); + fe->filter = filter; + fe->payload = payload; + + return 0; +} + +size_t git_filter_list_length(const git_filter_list *fl) +{ + return fl ? git_array_size(fl->filters) : 0; +} + +static int filter_list_out_buffer_from_raw( + git_buf *out, const void *ptr, size_t size) +{ + if (git_buf_is_allocated(out)) + git_buf_free(out); - for (i = 0; i < filters->length; ++i) { - git_filter *filter = git_vector_get(filters, i); - unsigned int dst = 1 - src; + if (!size) { + git_buf_init(out, 0); + } else { + out->ptr = (char *)ptr; + out->asize = 0; + out->size = size; + } + + return 0; +} + +int git_filter_list_apply_to_data( + git_buf *tgt, git_filter_list *fl, git_buf *src) +{ + int error = 0; + uint32_t i; + git_buf *dbuffer[2], local = GIT_BUF_INIT; + unsigned int si = 0; - git_buf_clear(dbuffer[dst]); + if (!fl) + return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size); + + dbuffer[0] = src; + dbuffer[1] = tgt; + + /* if `src` buffer is reallocable, then use it, otherwise copy it */ + if (!git_buf_is_allocated(src)) { + if (git_buf_set(&local, src->ptr, src->size) < 0) + return -1; + dbuffer[0] = &local; + } + + for (i = 0; i < git_array_size(fl->filters); ++i) { + unsigned int di = 1 - si; + uint32_t fidx = (fl->source.mode == GIT_FILTER_TO_WORKTREE) ? + i : git_array_size(fl->filters) - 1 - i; + git_filter_entry *fe = git_array_get(fl->filters, fidx); + + dbuffer[di]->size = 0; /* Apply the filter from dbuffer[src] to the other buffer; * if the filtering is canceled by the user mid-filter, @@ -79,16 +605,72 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) * of the double buffering (so that the text goes through * cleanly). */ - if (filter->apply(filter, dbuffer[dst], dbuffer[src]) == 0) - src = dst; - if (git_buf_oom(dbuffer[dst])) - return -1; + error = fe->filter->apply( + fe->filter, &fe->payload, dbuffer[di], dbuffer[si], &fl->source); + + if (error == GIT_PASSTHROUGH) { + /* PASSTHROUGH means filter decided not to process the buffer */ + error = 0; + } else if (!error) { + git_buf_shorten(dbuffer[di], 0); /* force NUL termination */ + si = di; /* swap buffers */ + } else { + tgt->size = 0; + return error; + } } /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */ - if (src != 1) - git_buf_swap(dest, source); + if (si != 1) + git_buf_swap(dbuffer[0], dbuffer[1]); + + git_buf_free(&local); /* don't leak if we allocated locally */ return 0; } + +int git_filter_list_apply_to_file( + git_buf *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + int error; + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_buf abspath = GIT_BUF_INIT, raw = GIT_BUF_INIT; + + if (!(error = git_path_join_unrooted(&abspath, path, base, NULL)) && + !(error = git_futils_readbuffer(&raw, abspath.ptr))) + { + error = git_filter_list_apply_to_data(out, filters, &raw); + + git_buf_free(&raw); + } + + git_buf_free(&abspath); + return error; +} + +int git_filter_list_apply_to_blob( + git_buf *out, + git_filter_list *filters, + git_blob *blob) +{ + git_buf in = GIT_BUF_INIT; + git_off_t rawsize = git_blob_rawsize(blob); + + if (!git__is_sizet(rawsize)) { + giterr_set(GITERR_OS, "Blob is too large to filter"); + return -1; + } + + in.ptr = (char *)git_blob_rawcontent(blob); + in.asize = 0; + in.size = (size_t)rawsize; + + if (filters) + git_oid_cpy(&filters->source.oid, git_blob_id(blob)); + + return git_filter_list_apply_to_data(out, filters, &in); +} |