diff options
author | Vicent Martà <vicent@github.com> | 2013-09-17 09:57:55 -0700 |
---|---|---|
committer | Vicent Martà <vicent@github.com> | 2013-09-17 09:57:55 -0700 |
commit | bb371b62e950e3307d3acf2f772495a60565d266 (patch) | |
tree | b4479ec3ad261bcac13493ee3f5ad45d15dfdda2 /src | |
parent | 4581f9d8ab72e9b97817e1eaa7154bcec1c7f0b1 (diff) | |
parent | f60ed4e6495b8bf68d0604335672e6f300330b3b (diff) | |
download | libgit2-bb371b62e950e3307d3acf2f772495a60565d266.tar.gz |
Merge pull request #1847 from libgit2/filters-alternative
Alternative proposal for filter API
Diffstat (limited to 'src')
-rw-r--r-- | src/array.h | 2 | ||||
-rw-r--r-- | src/attr.c | 8 | ||||
-rw-r--r-- | src/blob.c | 76 | ||||
-rw-r--r-- | src/buf_text.c | 21 | ||||
-rw-r--r-- | src/buf_text.h | 8 | ||||
-rw-r--r-- | src/buffer.c | 23 | ||||
-rw-r--r-- | src/buffer.h | 40 | ||||
-rw-r--r-- | src/checkout.c | 63 | ||||
-rw-r--r-- | src/config_file.c | 2 | ||||
-rw-r--r-- | src/crlf.c | 226 | ||||
-rw-r--r-- | src/diff.c | 12 | ||||
-rw-r--r-- | src/diff_file.c | 43 | ||||
-rw-r--r-- | src/fileops.c | 29 | ||||
-rw-r--r-- | src/fileops.h | 5 | ||||
-rw-r--r-- | src/filter.c | 658 | ||||
-rw-r--r-- | src/filter.h | 71 | ||||
-rw-r--r-- | src/global.c | 38 | ||||
-rw-r--r-- | src/global.h | 4 | ||||
-rw-r--r-- | src/hash.h | 2 | ||||
-rw-r--r-- | src/hash/hash_generic.h | 1 | ||||
-rw-r--r-- | src/hash/hash_openssl.h | 1 | ||||
-rw-r--r-- | src/hash/hash_win32.c | 20 | ||||
-rw-r--r-- | src/ident.c | 125 | ||||
-rw-r--r-- | src/odb.c | 20 | ||||
-rw-r--r-- | src/odb.h | 3 | ||||
-rw-r--r-- | src/path.c | 2 | ||||
-rw-r--r-- | src/repository.c | 9 | ||||
-rw-r--r-- | src/util.c | 3 | ||||
-rw-r--r-- | src/win32/pthread.c | 19 | ||||
-rw-r--r-- | src/win32/pthread.h | 1 |
30 files changed, 1101 insertions, 434 deletions
diff --git a/src/array.h b/src/array.h index b82079bd8..d7272d78c 100644 --- a/src/array.h +++ b/src/array.h @@ -59,7 +59,7 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size) #define git_array_alloc(a) \ ((a).size >= (a).asize) ? \ git_array_grow(&(a), sizeof(*(a).ptr)) : \ - (a).ptr ? &(a).ptr[(a).size++] : NULL + ((a).ptr ? &(a).ptr[(a).size++] : NULL) #define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL) 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/blob.c b/src/blob.c index 6a289f43b..e18db4dfc 100644 --- a/src/blob.c +++ b/src/blob.c @@ -108,29 +108,21 @@ static int write_file_filtered( git_off_t *size, git_odb *odb, const char *full_path, - git_vector *filters) + git_filter_list *fl) { int error; - git_buf source = GIT_BUF_INIT; - git_buf dest = GIT_BUF_INIT; + git_buf tgt = GIT_BUF_INIT; - if ((error = git_futils_readbuffer(&source, full_path)) < 0) - return error; - - error = git_filters_apply(&dest, &source, filters); - - /* Free the source as soon as possible. This can be big in memory, - * and we don't want to ODB write to choke */ - git_buf_free(&source); + error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path); /* Write the file to disk if it was properly filtered */ if (!error) { - *size = dest.size; + *size = tgt.size; - error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); + error = git_odb_write(oid, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB); } - git_buf_free(&dest); + git_buf_free(&tgt); return error; } @@ -198,29 +190,25 @@ int git_blob__create_from_paths( if (S_ISLNK(mode)) { error = write_symlink(oid, odb, content_path, (size_t)size); } else { - git_vector write_filters = GIT_VECTOR_INIT; - int filter_count = 0; + git_filter_list *fl = NULL; - if (try_load_filters) { + if (try_load_filters) /* Load the filters for writing this file to the ODB */ - filter_count = git_filters_load( - &write_filters, repo, hint_path, GIT_FILTER_TO_ODB); - } + error = git_filter_list_load( + &fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB); - if (filter_count < 0) { - /* Negative value means there was a critical error */ - error = filter_count; - } else if (filter_count == 0) { + if (error < 0) + /* well, that didn't work */; + else if (fl == NULL) /* No filters need to be applied to the document: we can stream * directly from disk */ error = write_file_stream(oid, odb, content_path, size); - } else { + else { /* We need to apply one or more filters */ - error = write_file_filtered( - oid, &size, odb, content_path, &write_filters); - } + error = write_file_filtered(oid, &size, odb, content_path, fl); - git_filters_free(&write_filters); + git_filter_list_free(fl); + } /* * TODO: eventually support streaming filtered files, for files @@ -333,8 +321,34 @@ int git_blob_is_binary(git_blob *blob) assert(blob); - content.ptr = blob->odb_object->buffer; - content.size = min(blob->odb_object->cached.size, 4000); + content.ptr = blob->odb_object->buffer; + content.size = min(blob->odb_object->cached.size, 4000); + content.asize = 0; return git_buf_text_is_binary(&content); } + +int git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *path, + int check_for_binary_data) +{ + int error = 0; + git_filter_list *fl = NULL; + + assert(blob && path && out); + + if (check_for_binary_data && git_blob_is_binary(blob)) + return 0; + + if (!(error = git_filter_list_load( + &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) { + + error = git_filter_list_apply_to_blob(out, fl, blob); + + git_filter_list_free(fl); + } + + return error; +} diff --git a/src/buf_text.c b/src/buf_text.c index ecf592b51..631feb3f8 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -70,10 +70,10 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src) assert(tgt != src); if (!next) - return GIT_ENOTFOUND; + return git_buf_set(tgt, src->ptr, src->size); /* reduce reallocs while in the loop */ - if (git_buf_grow(tgt, src->size) < 0) + if (git_buf_grow(tgt, src->size + 1) < 0) return -1; out = tgt->ptr; tgt->size = 0; @@ -81,20 +81,25 @@ int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src) /* Find the next \r and copy whole chunk up to there to tgt */ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { if (next > scan) { - size_t copylen = next - scan; + size_t copylen = (size_t)(next - scan); memcpy(out, scan, copylen); out += copylen; } /* Do not drop \r unless it is followed by \n */ - if (next[1] != '\n') + if (next + 1 == scan_end || next[1] != '\n') *out++ = '\r'; } /* Copy remaining input into dest */ - memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */ - out += (scan_end - scan); - tgt->size = out - tgt->ptr; + if (scan < scan_end) { + size_t remaining = (size_t)(scan_end - scan); + memcpy(out, scan, remaining); + out += remaining; + } + + tgt->size = (size_t)(out - tgt->ptr); + tgt->ptr[tgt->size] = '\0'; return 0; } @@ -109,7 +114,7 @@ int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src) assert(tgt != src); if (!next) - return GIT_ENOTFOUND; + return git_buf_set(tgt, src->ptr, src->size); /* attempt to reduce reallocs while in the loop */ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0) diff --git a/src/buf_text.h b/src/buf_text.h index 58e4e26a7..3ac9d1443 100644 --- a/src/buf_text.h +++ b/src/buf_text.h @@ -56,16 +56,16 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string) extern void git_buf_text_unescape(git_buf *buf); /** - * Replace all \r\n with \n (or do nothing if no \r\n are found) + * Replace all \r\n with \n. Does not modify \r without trailing \n. * - * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error + * @return 0 on success, -1 on memory error */ extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src); /** - * Replace all \n with \r\n (or do nothing if no \n are found) + * Replace all \n with \r\n. Does not modify existing \r\n. * - * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error + * @return 0 on success, -1 on memory error */ extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src); diff --git a/src/buffer.c b/src/buffer.c index b5b2fd678..f8d47d928 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -6,6 +6,7 @@ */ #include "buffer.h" #include "posix.h" +#include "git2/buffer.h" #include <stdarg.h> #include <ctype.h> @@ -31,7 +32,8 @@ void git_buf_init(git_buf *buf, size_t initial_size) git_buf_grow(buf, initial_size); } -int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) +int git_buf_try_grow( + git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external) { char *new_ptr; size_t new_size; @@ -39,6 +41,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) if (buf->ptr == git_buf__oom) return -1; + if (!target_size) + target_size = buf->size; + if (target_size <= buf->asize) return 0; @@ -66,6 +71,9 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) return -1; } + if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0) + memcpy(new_ptr, buf->ptr, min(buf->size, new_size)); + buf->asize = new_size; buf->ptr = new_ptr; @@ -77,11 +85,16 @@ int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) return 0; } +int git_buf_grow(git_buf *buffer, size_t target_size) +{ + return git_buf_try_grow(buffer, target_size, true, true); +} + void git_buf_free(git_buf *buf) { if (!buf) return; - if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom) + if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom) git__free(buf->ptr); git_buf_init(buf, 0); @@ -90,11 +103,15 @@ void git_buf_free(git_buf *buf) void git_buf_clear(git_buf *buf) { buf->size = 0; + + if (!buf->ptr) + buf->ptr = git_buf__initbuf; + if (buf->asize > 0) buf->ptr[0] = '\0'; } -int git_buf_set(git_buf *buf, const char *data, size_t len) +int git_buf_set(git_buf *buf, const void *data, size_t len) { if (len == 0 || data == NULL) { git_buf_clear(buf); diff --git a/src/buffer.h b/src/buffer.h index f3e1d506f..4ca9d4d94 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -9,18 +9,26 @@ #include "common.h" #include "git2/strarray.h" +#include "git2/buffer.h" #include <stdarg.h> -typedef struct { - char *ptr; - size_t asize, size; -} git_buf; +/* typedef struct { + * char *ptr; + * size_t asize, size; + * } git_buf; + */ extern char git_buf__initbuf[]; extern char git_buf__oom[]; +/* Use to initialize buffer structure when git_buf is on stack */ #define GIT_BUF_INIT { git_buf__initbuf, 0, 0 } +GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf) +{ + return (buf->ptr != NULL && buf->asize > 0); +} + /** * Initialize a git_buf structure. * @@ -32,27 +40,16 @@ extern void git_buf_init(git_buf *buf, size_t initial_size); /** * Attempt to grow the buffer to hold at least `target_size` bytes. * - * If the allocation fails, this will return an error. If mark_oom is true, + * If the allocation fails, this will return an error. If `mark_oom` is true, * this will mark the buffer as invalid for future operations; if false, * existing buffer content will be preserved, but calling code must handle - * that buffer was not expanded. + * that buffer was not expanded. If `preserve_external` is true, then any + * existing data pointed to be `ptr` even if `asize` is zero will be copied + * into the newly allocated buffer. */ -extern int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom); - -/** - * Grow the buffer to hold at least `target_size` bytes. - * - * If the allocation fails, this will return an error and the buffer will be - * marked as invalid for future operations, invaliding contents. - * - * @return 0 on success or -1 on failure - */ -GIT_INLINE(int) git_buf_grow(git_buf *buf, size_t target_size) -{ - return git_buf_try_grow(buf, target_size, true); -} +extern int git_buf_try_grow( + git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external); -extern void git_buf_free(git_buf *buf); extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b); extern char *git_buf_detach(git_buf *buf); extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize); @@ -81,7 +78,6 @@ GIT_INLINE(bool) git_buf_oom(const git_buf *buf) * return code of these functions and call them in a series then just call * git_buf_oom at the end. */ -int git_buf_set(git_buf *buf, const char *data, size_t len); int git_buf_sets(git_buf *buf, const char *string); int git_buf_putc(git_buf *buf, char c); int git_buf_put(git_buf *buf, const char *data, size_t len); diff --git a/src/checkout.c b/src/checkout.c index eb92e8fd6..0e9d11bff 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -678,7 +678,7 @@ fail: static int buffer_to_file( struct stat *st, - git_buf *buffer, + git_buf *buf, const char *path, mode_t dir_mode, int file_open_flags, @@ -690,7 +690,7 @@ static int buffer_to_file( return error; if ((error = git_futils_writebuffer( - buffer, path, file_open_flags, file_mode)) < 0) + buf, path, file_open_flags, file_mode)) < 0) return error; if (st != NULL && (error = p_stat(path, st)) < 0) @@ -710,57 +710,28 @@ static int blob_content_to_file( mode_t entry_filemode, git_checkout_opts *opts) { - int error = -1, nb_filters = 0; - mode_t file_mode = opts->file_mode; - bool dont_free_filtered; - git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; - git_vector filters = GIT_VECTOR_INIT; - - /* Create a fake git_buf from the blob raw data... */ - filtered.ptr = (void *)git_blob_rawcontent(blob); - filtered.size = (size_t)git_blob_rawsize(blob); - /* ... and make sure it doesn't get unexpectedly freed */ - dont_free_filtered = true; - - if (!opts->disable_filters && - !git_buf_text_is_binary(&filtered) && - (nb_filters = git_filters_load( - &filters, - git_object_owner((git_object *)blob), - path, - GIT_FILTER_TO_WORKTREE)) > 0) - { - /* reset 'filtered' so it can be a filter target */ - git_buf_init(&filtered, 0); - dont_free_filtered = false; - } - - if (nb_filters < 0) - return nb_filters; + int error = 0; + mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode; + git_buf out = GIT_BUF_INIT; + git_filter_list *fl = NULL; - if (nb_filters > 0) { - if ((error = git_blob__getbuf(&unfiltered, blob)) < 0) - goto cleanup; + if (!opts->disable_filters) + error = git_filter_list_load( + &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE); - if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0) - goto cleanup; - } + if (!error) + error = git_filter_list_apply_to_blob(&out, fl, blob); - /* Allow overriding of file mode */ - if (!file_mode) - file_mode = entry_filemode; + git_filter_list_free(fl); - error = buffer_to_file( - st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); + if (!error) { + error = buffer_to_file( + st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode); - if (!error) st->st_mode = entry_filemode; -cleanup: - git_filters_free(&filters); - git_buf_free(&unfiltered); - if (!dont_free_filtered) - git_buf_free(&filtered); + git_buf_free(&out); + } return error; } diff --git a/src/config_file.c b/src/config_file.c index bd4fa7471..d0910a26c 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -293,6 +293,8 @@ static int config_iterator_new( diskfile_backend *b = (diskfile_backend *)backend; git_config_file_iter *it = git__calloc(1, sizeof(git_config_file_iter)); + GIT_UNUSED(b); + GITERR_CHECK_ALLOC(it); it->parent.backend = backend; diff --git a/src/crlf.c b/src/crlf.c index 65039f9cc..b4eda267b 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -8,6 +8,7 @@ #include "git2/attr.h" #include "git2/blob.h" #include "git2/index.h" +#include "git2/sys/filter.h" #include "common.h" #include "fileops.h" @@ -19,13 +20,11 @@ struct crlf_attrs { int crlf_action; int eol; + int auto_crlf; }; struct crlf_filter { git_filter f; - struct crlf_attrs attrs; - git_repository *repo; - char path[GIT_FLEX_ARRAY]; }; static int check_crlf(const char *value) @@ -76,41 +75,10 @@ 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) +static int has_cr_in_index(const git_filter_source *src) { -#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(git_filter *self) -{ - struct crlf_filter *filter = (struct crlf_filter *)self; + git_repository *repo = git_filter_source_repo(src); + const char *path = git_filter_source_path(src); git_index *index; const git_index_entry *entry; git_blob *blob; @@ -118,19 +86,22 @@ static int has_cr_in_index(git_filter *self) git_off_t blobsize; bool found_cr; - if (git_repository_index__weakptr(&index, filter->repo) < 0) { + if (!path) + return false; + + if (git_repository_index__weakptr(&index, repo) < 0) { giterr_clear(); return false; } - if (!(entry = git_index_get_bypath(index, filter->path, 0)) && - !(entry = git_index_get_bypath(index, filter->path, 1))) + if (!(entry = git_index_get_bypath(index, path, 0)) && + !(entry = git_index_get_bypath(index, path, 1))) return false; if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ return true; - if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0) + if (git_blob_lookup(&blob, repo, &entry->oid) < 0) return false; blobcontent = git_blob_rawcontent(blob); @@ -147,26 +118,23 @@ static int has_cr_in_index(git_filter *self) } static int crlf_apply_to_odb( - git_filter *self, git_buf *dest, const git_buf *source) + struct crlf_attrs *ca, + git_buf *to, + const git_buf *from, + const git_filter_source *src) { - struct crlf_filter *filter = (struct crlf_filter *)self; - - assert(self && dest && source); - /* Empty file? Nothing to do */ - if (git_buf_len(source) == 0) + if (!git_buf_len(from)) return 0; /* Heuristics to see if we can skip the conversion. * Straight from Core Git. */ - if (filter->attrs.crlf_action == GIT_CRLF_AUTO || - filter->attrs.crlf_action == GIT_CRLF_GUESS) { - + if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) { git_buf_text_stats stats; /* Check heuristics for binary vs text... */ - if (git_buf_text_gather_stats(&stats, source, false)) + if (git_buf_text_gather_stats(&stats, from, false)) return -1; /* @@ -175,28 +143,28 @@ static int crlf_apply_to_odb( * stuff? */ if (stats.cr != stats.crlf) - return -1; + return GIT_PASSTHROUGH; - if (filter->attrs.crlf_action == GIT_CRLF_GUESS) { + if (ca->crlf_action == GIT_CRLF_GUESS) { /* * If the file in the index has any CR in it, do not convert. * This is the new safer autocrlf handling. */ - if (has_cr_in_index(self)) - return -1; + if (has_cr_in_index(src)) + return GIT_PASSTHROUGH; } if (!stats.cr) - return -1; + return GIT_PASSTHROUGH; } /* Actually drop the carriage returns */ - return git_buf_text_crlf_to_lf(dest, source); + return git_buf_text_crlf_to_lf(to, from); } -static const char *line_ending(struct crlf_filter *filter) +static const char *line_ending(struct crlf_attrs *ca) { - switch (filter->attrs.crlf_action) { + switch (ca->crlf_action) { case GIT_CRLF_BINARY: case GIT_CRLF_INPUT: return "\n"; @@ -213,11 +181,9 @@ static const char *line_ending(struct crlf_filter *filter) goto line_ending_error; } - switch (filter->attrs.eol) { + switch (ca->eol) { case GIT_EOL_UNSET: - return GIT_EOL_NATIVE == GIT_EOL_CRLF - ? "\r\n" - : "\n"; + return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n"; case GIT_EOL_CRLF: return "\r\n"; @@ -235,41 +201,64 @@ line_ending_error: } static int crlf_apply_to_workdir( - git_filter *self, git_buf *dest, const git_buf *source) + struct crlf_attrs *ca, git_buf *to, const git_buf *from) { - struct crlf_filter *filter = (struct crlf_filter *)self; const char *workdir_ending = NULL; - assert(self && dest && source); - /* Empty file? Nothing to do. */ - if (git_buf_len(source) == 0) - return -1; + if (git_buf_len(from) == 0) + return 0; + + /* Don't filter binary files */ + if (git_buf_text_is_binary(from)) + return GIT_PASSTHROUGH; /* Determine proper line ending */ - workdir_ending = line_ending(filter); + workdir_ending = line_ending(ca); if (!workdir_ending) return -1; - if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */ - return -1; - /* for now, only lf->crlf conversion is supported here */ - assert(!strcmp("\r\n", workdir_ending)); - return git_buf_text_lf_to_crlf(dest, source); + if (!strcmp("\n", workdir_ending)) { + if (ca->crlf_action == GIT_CRLF_GUESS && ca->auto_crlf) + return GIT_PASSTHROUGH; + + if (git_buf_find(from, '\r') < 0) + return GIT_PASSTHROUGH; + + if (git_buf_text_crlf_to_lf(to, from) < 0) + return -1; + } else { + /* only other supported option is lf->crlf conversion */ + assert(!strcmp("\r\n", workdir_ending)); + + if (git_buf_text_lf_to_crlf(to, from) < 0) + return -1; + } + + return 0; } -static int find_and_add_filter( - git_vector *filters, git_repository *repo, const char *path, - int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) +static int crlf_check( + git_filter *self, + void **payload, /* points to NULL ptr on entry, may be set */ + const git_filter_source *src, + const char **attr_values) { - struct crlf_attrs ca; - struct crlf_filter *filter; - size_t pathlen; int error; + struct crlf_attrs ca; - /* Load gitattributes for the path */ - if ((error = crlf_load_attributes(&ca, repo, path)) < 0) - return error; + GIT_UNUSED(self); + + 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 @@ -278,41 +267,64 @@ static int find_and_add_filter( ca.crlf_action = crlf_input_action(&ca); if (ca.crlf_action == GIT_CRLF_BINARY) - return 0; + return GIT_PASSTHROUGH; if (ca.crlf_action == GIT_CRLF_GUESS) { - int auto_crlf; - - if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + error = git_repository__cvar( + &ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF); + if (error < 0) return error; - if (auto_crlf == GIT_AUTO_CRLF_FALSE) - return 0; + if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE) + return GIT_PASSTHROUGH; } - /* If we're good, we create a new filter object and push it - * into the filters array */ - pathlen = strlen(path); - filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); - GITERR_CHECK_ALLOC(filter); + *payload = git__malloc(sizeof(ca)); + GITERR_CHECK_ALLOC(*payload); + memcpy(*payload, &ca, sizeof(ca)); - filter->f.apply = apply; - filter->f.do_free = NULL; - memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); - filter->repo = repo; - memcpy(filter->path, path, pathlen + 1); + return 0; +} - return git_vector_insert(filters, filter); +static int crlf_apply( + git_filter *self, + void **payload, /* may be read and/or set */ + git_buf *to, + const git_buf *from, + const git_filter_source *src) +{ + /* initialize payload in case `check` was bypassed */ + if (!*payload) { + int error = crlf_check(self, payload, src, NULL); + if (error < 0 && error != GIT_PASSTHROUGH) + return error; + } + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return crlf_apply_to_workdir(*payload, to, from); + else + return crlf_apply_to_odb(*payload, to, from, src); } -int git_filter_add__crlf_to_odb( - git_vector *filters, git_repository *repo, const char *path) +static void crlf_cleanup( + git_filter *self, + void *payload) { - return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); + GIT_UNUSED(self); + git__free(payload); } -int git_filter_add__crlf_to_workdir( - git_vector *filters, git_repository *repo, const char *path) +git_filter *git_crlf_filter_new(void) { - return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); + 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 = git_filter_free; + f->f.check = crlf_check; + f->f.apply = crlf_apply; + f->f.cleanup = crlf_cleanup; + + return (git_filter *)f; } diff --git a/src/diff.c b/src/diff.c index 77dbbd8bc..4d9ace183 100644 --- a/src/diff.c +++ b/src/diff.c @@ -568,21 +568,21 @@ int git_diff__oid_for_file( giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path); result = -1; } else { - git_vector filters = GIT_VECTOR_INIT; + git_filter_list *fl = NULL; - result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB); - if (result >= 0) { + result = git_filter_list_load(&fl, repo, NULL, path, GIT_FILTER_TO_ODB); + if (!result) { int fd = git_futils_open_ro(full_path.ptr); if (fd < 0) result = fd; else { result = git_odb__hashfd_filtered( - oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters); + oid, fd, (size_t)size, GIT_OBJ_BLOB, fl); p_close(fd); } - } - git_filters_free(&filters); + git_filter_list_free(fl); + } } cleanup: diff --git a/src/diff_file.c b/src/diff_file.c index bcfef13cd..5939ee8b8 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -296,9 +296,9 @@ static int diff_file_content_load_workdir_file( git_diff_file_content *fc, git_buf *path) { int error = 0; - git_vector filters = GIT_VECTOR_INIT; - git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + git_filter_list *fl = NULL; git_file fd = git_futils_open_ro(git_buf_cstr(path)); + git_buf raw = GIT_BUF_INIT; if (fd < 0) return fd; @@ -310,41 +310,38 @@ static int diff_file_content_load_workdir_file( if (diff_file_content_binary_by_size(fc)) goto cleanup; - if ((error = git_filters_load( - &filters, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0) + if ((error = git_filter_list_load( + &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0) goto cleanup; - /* error >= is a filter count */ - if (error == 0) { + /* if there are no filters, try to mmap the file */ + if (fl == NULL) { if (!(error = git_futils_mmap_ro( - &fc->map, fd, 0, (size_t)fc->file->size))) + &fc->map, fd, 0, (size_t)fc->file->size))) { fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; - else /* fall through to try readbuffer below */ - giterr_clear(); + goto cleanup; + } + + /* if mmap failed, fall through to try readbuffer below */ + giterr_clear(); } - if (error != 0) { - error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size); - if (error < 0) - goto cleanup; + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { + git_buf out = GIT_BUF_INIT; - if (!filters.length) - git_buf_swap(&filtered, &raw); - else - error = git_filters_apply(&filtered, &raw, &filters); + error = git_filter_list_apply_to_data(&out, fl, &raw); + + git_buf_free(&raw); if (!error) { - fc->map.len = git_buf_len(&filtered); - fc->map.data = git_buf_detach(&filtered); + fc->map.len = out.size; + fc->map.data = out.ptr; fc->flags |= GIT_DIFF_FLAG__FREE_DATA; } - - git_buf_free(&raw); - git_buf_free(&filtered); } cleanup: - git_filters_free(&filters); + git_filter_list_free(fl); p_close(fd); return error; diff --git a/src/fileops.c b/src/fileops.c index 126d45f26..bd845e982 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -6,6 +6,7 @@ */ #include "common.h" #include "fileops.h" +#include "global.h" #include <ctype.h> #if GIT_WIN32 #include "win32/findfile.h" @@ -55,18 +56,8 @@ int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode int git_futils_creat_locked(const char *path, const mode_t mode) { - int fd; - -#ifdef GIT_WIN32 - git_win32_path buf; - - git_win32_path_from_c(buf, path); - fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | - O_EXCL | O_BINARY | O_CLOEXEC, mode); -#else - fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY | O_CLOEXEC, mode); -#endif if (fd < 0) { giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); @@ -635,6 +626,13 @@ static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { git_futils_guess_xdg_dirs, }; +static void git_futils_dirs_global_shutdown(void) +{ + int i; + for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) + git_buf_free(&git_futils__dirs[i]); +} + int git_futils_dirs_global_init(void) { git_futils_dir_t i; @@ -644,6 +642,8 @@ int git_futils_dirs_global_init(void) for (i = 0; !error && i < GIT_FUTILS_DIR__MAX; i++) error = git_futils_dirs_get(&path, i); + git__on_shutdown(git_futils_dirs_global_shutdown); + return error; } @@ -726,13 +726,6 @@ int git_futils_dirs_set(git_futils_dir_t which, const char *search_path) return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0; } -void git_futils_dirs_free(void) -{ - int i; - for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) - git_buf_free(&git_futils__dirs[i]); -} - static int git_futils_find_in_dirlist( git_buf *path, const char *name, git_futils_dir_t which, const char *label) { diff --git a/src/fileops.h b/src/fileops.h index 02f79b9e7..16bc58e93 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -354,11 +354,6 @@ extern int git_futils_dirs_get_str( extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths); /** - * Release / reset all search paths - */ -extern void git_futils_dirs_free(void); - -/** * Create a "fake" symlink (text file containing the target path). * * @param new symlink file to be created diff --git a/src/filter.c b/src/filter.c index 9f749dcbd..503f18555 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; - git_vector_foreach(filters, i, filter) { - if (filter->do_free != NULL) - filter->do_free(filter); - else - git__free(filter); + 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; } - git_vector_free(filters); + return 0; + +cleanup: + git_vector_free(®->filters); + git__free(reg); + return error; } -int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) +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)++; + + 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; - 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; + } - src = 0; + 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; +} + +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); +} + +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); +} - if (git_buf_len(source) == 0) { - git_buf_clear(dest); +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); + + if (!size) { + git_buf_init(out, 0); + } else { + out->ptr = (char *)ptr; + out->asize = 0; + out->size = size; + } - for (i = 0; i < filters->length; ++i) { - git_filter *filter = git_vector_get(filters, i); - unsigned int dst = 1 - src; + 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; + + 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; + } - git_buf_clear(dbuffer[dst]); + 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,64 @@ 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 = { + (char *)git_blob_rawcontent(blob), 0, git_blob_rawsize(blob) + }; + + if (filters) + git_oid_cpy(&filters->source.oid, git_blob_id(blob)); + + return git_filter_list_apply_to_data(out, filters, &in); +} diff --git a/src/filter.h b/src/filter.h index 42a44ebdb..d0ace0f9a 100644 --- a/src/filter.h +++ b/src/filter.h @@ -8,19 +8,7 @@ #define INCLUDE_filter_h__ #include "common.h" -#include "buffer.h" -#include "git2/odb.h" -#include "git2/repository.h" - -typedef struct git_filter { - int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source); - void (*do_free)(struct git_filter *self); -} git_filter; - -typedef enum { - GIT_FILTER_TO_WORKTREE, - GIT_FILTER_TO_ODB -} git_filter_mode; +#include "git2/filter.h" typedef enum { GIT_CRLF_GUESS = -1, @@ -31,64 +19,13 @@ typedef enum { GIT_CRLF_AUTO, } git_crlf_t; -/* - * FILTER API - */ - -/* - * For any given path in the working directory, fill the `filters` - * array with the relevant filters that need to be applied. - * - * Mode is either `GIT_FILTER_TO_WORKTREE` if you need to load the - * filters that will be used when checking out a file to the working - * directory, or `GIT_FILTER_TO_ODB` for the filters used when writing - * a file to the ODB. - * - * @param filters Vector where to store all the loaded filters - * @param repo Repository object that contains `path` - * @param path Relative path of the file to be filtered - * @param mode Filtering direction (WT->ODB or ODB->WT) - * @return the number of filters loaded for the file (0 if the file - * doesn't need filtering), or a negative error code - */ -extern int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode); - -/* - * Apply one or more filters to a file. - * - * The file must have been loaded as a `git_buf` object. Both the `source` - * and `dest` buffers are owned by the caller and must be freed once - * they are no longer needed. - * - * NOTE: Because of the double-buffering schema, the `source` buffer that contains - * the original file may be tampered once the filtering is complete. Regardless, - * the `dest` buffer will always contain the final result of the filtering - * - * @param dest Buffer to store the result of the filtering - * @param source Buffer containing the document to filter - * @param filters A non-empty vector of filters as supplied by `git_filters_load` - * @return 0 on success, an error code otherwise - */ -extern int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters); - -/* - * Free the `filters` array generated by `git_filters_load`. - * - * Note that this frees both the array and its contents. The array will - * be clean/reusable after this call. - * - * @param filters A filters array as supplied by `git_filters_load` - */ -extern void git_filters_free(git_vector *filters); +extern void git_filter_free(git_filter *filter); /* * Available filters */ -/* Strip CRLF, from Worktree to ODB */ -extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); - -/* Add CRLF, from ODB to worktree */ -extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path); +extern git_filter *git_crlf_filter_new(void); +extern git_filter *git_ident_filter_new(void); #endif diff --git a/src/global.c b/src/global.c index b504e5e0a..4f024f17e 100644 --- a/src/global.c +++ b/src/global.c @@ -14,6 +14,28 @@ git_mutex git__mwindow_mutex; +#define MAX_SHUTDOWN_CB 8 + +git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; +git_atomic git__n_shutdown_callbacks; + +void git__on_shutdown(git_global_shutdown_fn callback) +{ + int count = git_atomic_inc(&git__n_shutdown_callbacks); + assert(count <= MAX_SHUTDOWN_CB); + git__shutdown_callbacks[count - 1] = callback; +} + +static void git__shutdown(void) +{ + int pos; + + while ((pos = git_atomic_dec(&git__n_shutdown_callbacks)) >= 0) { + if (git__shutdown_callbacks[pos]) + git__shutdown_callbacks[pos](); + } +} + /** * Handle the global state with TLS * @@ -79,9 +101,7 @@ int git_threads_init(void) void git_threads_shutdown(void) { /* Shut down any subsystems that have global state */ - win32_pthread_shutdown(); - git_futils_dirs_free(); - git_hash_global_shutdown(); + git__shutdown(); TlsFree(_tls_index); _tls_init = 0; @@ -140,6 +160,9 @@ int git_threads_init(void) void git_threads_shutdown(void) { + /* Shut down any subsystems that have global state */ + git__shutdown(); + if (_tls_init) { void *ptr = pthread_getspecific(_tls_key); pthread_setspecific(_tls_key, NULL); @@ -149,10 +172,6 @@ void git_threads_shutdown(void) pthread_key_delete(_tls_key); _tls_init = 0; git_mutex_free(&git__mwindow_mutex); - - /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); } git_global_st *git__global_state(void) @@ -179,15 +198,14 @@ static git_global_st __state; int git_threads_init(void) { - /* noop */ + /* noop */ return 0; } void git_threads_shutdown(void) { /* Shut down any subsystems that have global state */ - git_hash_global_shutdown(); - git_futils_dirs_free(); + git__shutdown(); } git_global_st *git__global_state(void) diff --git a/src/global.h b/src/global.h index badbc0883..778250376 100644 --- a/src/global.h +++ b/src/global.h @@ -21,4 +21,8 @@ extern git_mutex git__mwindow_mutex; #define GIT_GLOBAL (git__global_state()) +typedef void (*git_global_shutdown_fn)(void); + +extern void git__on_shutdown(git_global_shutdown_fn callback); + #endif diff --git a/src/hash.h b/src/hash.h index 5b848981f..c47f33549 100644 --- a/src/hash.h +++ b/src/hash.h @@ -13,8 +13,6 @@ typedef struct git_hash_prov git_hash_prov; typedef struct git_hash_ctx git_hash_ctx; int git_hash_global_init(void); -void git_hash_global_shutdown(void); - int git_hash_ctx_init(git_hash_ctx *ctx); void git_hash_ctx_cleanup(git_hash_ctx *ctx); diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h index 6b60c98c4..daeb1cda8 100644 --- a/src/hash/hash_generic.h +++ b/src/hash/hash_generic.h @@ -17,7 +17,6 @@ struct git_hash_ctx { }; #define git_hash_global_init() 0 -#define git_hash_global_shutdown() /* noop */ #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h index f83279a5a..9a55d472d 100644 --- a/src/hash/hash_openssl.h +++ b/src/hash/hash_openssl.h @@ -17,7 +17,6 @@ struct git_hash_ctx { }; #define git_hash_global_init() 0 -#define git_hash_global_shutdown() /* noop */ #define git_hash_ctx_init(ctx) git_hash_init(ctx) #define git_hash_ctx_cleanup(ctx) diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c index 095ceb359..bb2231364 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/hash_win32.c @@ -89,7 +89,15 @@ GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) hash_prov.type = INVALID; } -int git_hash_global_init() +static void git_hash_global_shutdown(void) +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +int git_hash_global_init(void) { int error = 0; @@ -99,15 +107,9 @@ int git_hash_global_init() if ((error = hash_cng_prov_init()) < 0) error = hash_cryptoapi_prov_init(); - return error; -} + git__on_shutdown(git_hash_global_shutdown); -void git_hash_global_shutdown() -{ - if (hash_prov.type == CNG) - hash_cng_prov_shutdown(); - else if(hash_prov.type == CRYPTOAPI) - hash_cryptoapi_prov_shutdown(); + return error; } /* CryptoAPI: available in Windows XP and newer */ diff --git a/src/ident.c b/src/ident.c new file mode 100644 index 000000000..51630879d --- /dev/null +++ b/src/ident.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/sys/filter.h" +#include "filter.h" +#include "buffer.h" +#include "buf_text.h" + +static int ident_find_id( + const char **id_start, const char **id_end, const char *start, size_t len) +{ + const char *end = start + len, *found = NULL; + + while (len > 3 && (found = memchr(start, '$', len)) != NULL) { + size_t remaining = (size_t)(end - found) - 1; + if (remaining < 3) + return GIT_ENOTFOUND; + + start = found + 1; + len = remaining; + + if (start[0] == 'I' && start[1] == 'd') + break; + } + + if (len < 3 || !found) + return GIT_ENOTFOUND; + *id_start = found; + + if ((found = memchr(start + 2, '$', len - 2)) == NULL) + return GIT_ENOTFOUND; + + *id_end = found + 1; + return 0; +} + +static int ident_insert_id( + git_buf *to, const git_buf *from, const git_filter_source *src) +{ + char oid[GIT_OID_HEXSZ+1]; + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + /* replace $Id$ with blob id */ + + if (!git_filter_source_id(src)) + return GIT_PASSTHROUGH; + + git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 5 /* "$Id: " */ + GIT_OID_HEXSZ + 1 /* "$" */ + + (size_t)(from_end - id_end); + + if (git_buf_grow(to, need_size) < 0) + return -1; + + git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(to, "$Id: ", 5); + git_buf_put(to, oid, GIT_OID_HEXSZ); + git_buf_putc(to, '$'); + git_buf_put(to, id_end, (size_t)(from_end - id_end)); + + return git_buf_oom(to) ? -1 : 0; +} + +static int ident_remove_id( + git_buf *to, const git_buf *from) +{ + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 4 /* "$Id$" */ + (size_t)(from_end - id_end); + + if (git_buf_grow(to, need_size) < 0) + return -1; + + git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(to, "$Id$", 4); + git_buf_put(to, id_end, (size_t)(from_end - id_end)); + + return git_buf_oom(to) ? -1 : 0; +} + +static int ident_apply( + git_filter *self, + void **payload, + git_buf *to, + const git_buf *from, + const git_filter_source *src) +{ + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* Don't filter binary files */ + if (git_buf_text_is_binary(from)) + return GIT_PASSTHROUGH; + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return ident_insert_id(to, from, src); + else + return ident_remove_id(to, from); +} + +git_filter *git_ident_filter_new(void) +{ + git_filter *f = git__calloc(1, sizeof(git_filter)); + + f->version = GIT_FILTER_VERSION; + f->attributes = "+ident"; /* apply to files with ident attribute set */ + f->shutdown = git_filter_free; + f->apply = ident_apply; + + return f; +} @@ -179,28 +179,30 @@ done: } int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters) + git_oid *out, git_file fd, size_t size, git_otype type, git_filter_list *fl) { int error; git_buf raw = GIT_BUF_INIT; - git_buf filtered = GIT_BUF_INIT; - if (!filters || !filters->length) + if (!fl) return git_odb__hashfd(out, fd, size, type); /* size of data is used in header, so we have to read the whole file * into memory to apply filters before beginning to calculate the hash */ - if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) - error = git_filters_apply(&filtered, &raw, filters); + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { + git_buf post = GIT_BUF_INIT; - git_buf_free(&raw); + error = git_filter_list_apply_to_data(&post, fl, &raw); - if (!error) - error = git_odb_hash(out, filtered.ptr, filtered.size, type); + git_buf_free(&raw); - git_buf_free(&filtered); + if (!error) + error = git_odb_hash(out, post.ptr, post.size, type); + + git_buf_free(&post); + } return error; } @@ -14,6 +14,7 @@ #include "vector.h" #include "cache.h" #include "posix.h" +#include "filter.h" #define GIT_OBJECTS_DIR "objects/" #define GIT_OBJECT_DIR_MODE 0777 @@ -66,7 +67,7 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); * Acts just like git_odb__hashfd with the addition of filters... */ int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters); + git_oid *out, git_file fd, size_t len, git_otype type, git_filter_list *fl); /* * Hash a `path`, assuming it could be a POSIX symlink: if the path is a diff --git a/src/path.c b/src/path.c index 56b0b49ca..42b3d6f3e 100644 --- a/src/path.c +++ b/src/path.c @@ -565,7 +565,7 @@ static bool _check_dir_contents( size_t sub_size = strlen(sub); /* leave base valid even if we could not make space for subdir */ - if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0) + if (git_buf_try_grow(dir, dir_size + sub_size + 2, false, false) < 0) return false; /* save excursion */ diff --git a/src/repository.c b/src/repository.c index eead41201..76e8228b7 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1649,7 +1649,7 @@ int git_repository_hashfile( const char *as_path) { int error; - git_vector filters = GIT_VECTOR_INIT; + git_filter_list *fl = NULL; git_file fd = -1; git_off_t len; git_buf full_path = GIT_BUF_INIT; @@ -1671,7 +1671,8 @@ int git_repository_hashfile( /* passing empty string for "as_path" indicated --no-filters */ if (strlen(as_path) > 0) { - error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB); + error = git_filter_list_load( + &fl, repo, NULL, as_path, GIT_FILTER_TO_ODB); if (error < 0) return error; } else { @@ -1698,12 +1699,12 @@ int git_repository_hashfile( goto cleanup; } - error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters); + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); cleanup: if (fd >= 0) p_close(fd); - git_filters_free(&filters); + git_filter_list_free(fl); git_buf_free(&full_path); return error; diff --git a/src/util.c b/src/util.c index d0c326ae5..151782346 100644 --- a/src/util.c +++ b/src/util.c @@ -679,6 +679,9 @@ size_t git__unescape(char *str) { char *scan, *pos = str; + if (!str) + return 0; + for (scan = str; *scan; pos++, scan++) { if (*scan == '\\' && *(scan + 1) != '\0') scan++; /* skip '\' but include next char */ diff --git a/src/win32/pthread.c b/src/win32/pthread.c index d50ace695..db8927471 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -6,6 +6,7 @@ */ #include "pthread.h" +#include "../global.h" int pthread_create( pthread_t *GIT_RESTRICT thread, @@ -217,6 +218,14 @@ int pthread_rwlock_destroy(pthread_rwlock_t *lock) } +static void win32_pthread_shutdown(void) +{ + if (win32_kernel32_dll) { + FreeLibrary(win32_kernel32_dll); + win32_kernel32_dll = NULL; + } +} + int win32_pthread_initialize(void) { if (win32_kernel32_dll) @@ -239,15 +248,7 @@ int win32_pthread_initialize(void) win32_srwlock_release_exclusive = (win32_srwlock_fn) GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive"); - return 0; -} - -int win32_pthread_shutdown(void) -{ - if (win32_kernel32_dll) { - FreeLibrary(win32_kernel32_dll); - win32_kernel32_dll = NULL; - } + git__on_shutdown(win32_pthread_shutdown); return 0; } diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 2ba2ca552..af5b121f0 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -69,6 +69,5 @@ int pthread_rwlock_wrunlock(pthread_rwlock_t *); int pthread_rwlock_destroy(pthread_rwlock_t *); extern int win32_pthread_initialize(void); -extern int win32_pthread_shutdown(void); #endif |