diff --git a/include/git2/filter.h b/include/git2/filter.h index 8ef88d81b..649ed97cf 100644 --- a/include/git2/filter.h +++ b/include/git2/filter.h @@ -42,11 +42,13 @@ typedef enum { * file data. Libgit2 includes one built in filter and it is possible to * write your own (see git2/sys/filter.h for information on that). * - * The built in filter is: + * The two builtin filters are: * * * "crlf" which uses the complex rules with the "text", "eol", and * "crlf" file attributes to decide how to convert between LF and CRLF * line endings + * * "ident" which replaces "$Id$" in a blob with "$Id: $" upon + * checkout and replaced "$Id: $" with "$Id$" on checkin. */ typedef struct git_filter git_filter; @@ -70,6 +72,7 @@ typedef struct git_filter_list git_filter_list; * * @param filters Output newly created git_filter_list (or NULL) * @param repo Repository object that contains `path` + * @param blob The blob to which the filter will be applied (if known) * @param path Relative path of the file to be filtered * @param mode Filtering direction (WT->ODB or ODB->WT) * @return 0 on success (which could still return NULL if no filters are @@ -78,6 +81,7 @@ typedef struct git_filter_list git_filter_list; GIT_EXTERN(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); diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h index ca5738a53..c35fe55f6 100644 --- a/include/git2/sys/filter.h +++ b/include/git2/sys/filter.h @@ -26,7 +26,11 @@ GIT_BEGIN_DECL */ GIT_EXTERN(git_filter *) git_filter_lookup(const char *name); -#define GIT_FILTER_CRLF "crlf" +#define GIT_FILTER_CRLF "crlf" +#define GIT_FILTER_IDENT "ident" + +#define GIT_FILTER_CRLF_PRIORITY 0 +#define GIT_FILTER_IDENT_PRIORITY 100 /** * Create a new empty filter list @@ -199,8 +203,9 @@ struct git_filter { * 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. + * Two filters are preregistered with libgit2: + * - GIT_FILTER_CRLF with priority 0 + * - GIT_FILTER_IDENT with priority 100 * * Currently the filter registry is not thread safe, so any registering or * deregistering of filters must be done outside of any possible usage of diff --git a/src/blob.c b/src/blob.c index e6bba033a..97fd6f70d 100644 --- a/src/blob.c +++ b/src/blob.c @@ -195,7 +195,7 @@ int git_blob__create_from_paths( if (try_load_filters) /* Load the filters for writing this file to the ODB */ error = git_filter_list_load( - &fl, repo, hint_path, GIT_FILTER_TO_ODB); + &fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB); if (error < 0) /* well, that didn't work */; @@ -331,19 +331,19 @@ int git_blob_is_binary(git_blob *blob) int git_blob_filtered_content( git_buffer *out, git_blob *blob, - const char *as_path, + const char *path, int check_for_binary_data) { int error = 0; git_filter_list *fl = NULL; - assert(blob && as_path && out); + 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), as_path, GIT_FILTER_TO_WORKTREE))) { + &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) { error = git_filter_list_apply_to_blob(out, fl, blob); diff --git a/src/checkout.c b/src/checkout.c index 1def58b0a..7e79c9b5e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -718,7 +718,7 @@ static int blob_content_to_file( if (!opts->disable_filters && !git_blob_is_binary(blob)) error = git_filter_list_load( - &fl, git_blob_owner(blob), path, GIT_FILTER_TO_WORKTREE); + &fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE); if (!error) error = git_filter_list_apply_to_blob(&out, fl, blob); diff --git a/src/crlf.c b/src/crlf.c index 99c154f70..f61a870da 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -324,11 +324,6 @@ static void crlf_cleanup( git__free(payload); } -static void crlf_shutdown(git_filter *self) -{ - git__free(self); -} - git_filter *git_crlf_filter_new(void) { struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); @@ -336,7 +331,7 @@ git_filter *git_crlf_filter_new(void) f->f.version = GIT_FILTER_VERSION; f->f.attributes = "crlf eol text"; f->f.initialize = NULL; - f->f.shutdown = crlf_shutdown; + f->f.shutdown = git_filter_free; f->f.check = crlf_check; f->f.apply = crlf_apply; f->f.cleanup = crlf_cleanup; diff --git a/src/diff.c b/src/diff.c index b1cde36bc..4d9ace183 100644 --- a/src/diff.c +++ b/src/diff.c @@ -570,7 +570,7 @@ int git_diff__oid_for_file( } else { git_filter_list *fl = NULL; - result = git_filter_list_load(&fl, repo, path, GIT_FILTER_TO_ODB); + 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) diff --git a/src/diff_file.c b/src/diff_file.c index e0e244b65..d02787c75 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -311,7 +311,7 @@ static int diff_file_content_load_workdir_file( goto cleanup; if ((error = git_filter_list_load( - &fl, fc->repo, fc->file->path, GIT_FILTER_TO_ODB)) < 0) + &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0) goto cleanup; /* if there are no filters, try to mmap the file */ diff --git a/src/filter.c b/src/filter.c index 79ccac0cf..f20611471 100644 --- a/src/filter.c +++ b/src/filter.c @@ -103,7 +103,23 @@ static int filter_registry_initialize(void) git__on_shutdown(filter_registry_shutdown); - return git_filter_register(GIT_FILTER_CRLF, git_crlf_filter_new(), 0); + /* 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); @@ -132,7 +148,7 @@ static int filter_def_scan_attrs( if (scan > start) { (*nattr)++; - if (has_eq || *scan == '-' || *scan == '+' || *scan == '!') + if (has_eq || *start == '-' || *start == '+' || *start == '!') (*nmatch)++; if (has_eq) @@ -312,6 +328,11 @@ git_filter *git_filter_lookup(const char *name) 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; @@ -410,6 +431,7 @@ int git_filter_list_new( 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) { @@ -426,6 +448,8 @@ int git_filter_list_load( 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; @@ -630,5 +654,8 @@ int git_filter_list_apply_to_blob( (char *)git_blob_rawcontent(blob), git_blob_rawsize(blob), 0 }; + 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 1bde1e306..d0ace0f9a 100644 --- a/src/filter.h +++ b/src/filter.h @@ -19,10 +19,13 @@ typedef enum { GIT_CRLF_AUTO, } git_crlf_t; +extern void git_filter_free(git_filter *filter); + /* * Available filters */ extern git_filter *git_crlf_filter_new(void); +extern git_filter *git_ident_filter_new(void); #endif diff --git a/src/ident.c b/src/ident.c new file mode 100644 index 000000000..aedb973f9 --- /dev/null +++ b/src/ident.c @@ -0,0 +1,128 @@ +/* + * 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" + +int ident_find_id( + const char **id_start, const char **id_end, const char *start, size_t len) +{ + const char *found; + + while (len > 0 && (found = memchr(start, '$', len)) != NULL) { + size_t remaining = len - (size_t)(found - start); + if (remaining < 3) + return GIT_ENOTFOUND; + if (found[1] == 'I' && found[2] == 'd') + break; + start = found + 1; + len = remaining - 1; + } + + if (len < 3) + return GIT_ENOTFOUND; + *id_start = found; + + if ((found = memchr(found + 3, '$', len - 3)) == NULL) + return GIT_ENOTFOUND; + + *id_end = found + 1; + return 0; +} + +static int ident_insert_id( + git_buffer *to, const git_buffer *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; + git_buf to_buf = GIT_BUF_FROM_BUFFER(to); + + /* replace $Id$ with blob id */ + + if (!git_filter_source_id(src)) + return GIT_ENOTFOUND; + + 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_ENOTFOUND; + + 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_buf, need_size) < 0) + return -1; + + git_buf_set(&to_buf, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(&to_buf, "$Id: ", 5); + git_buf_put(&to_buf, oid, GIT_OID_HEXSZ); + git_buf_putc(&to_buf, '$'); + git_buf_put(&to_buf, id_end, (size_t)(from_end - id_end)); + + if (git_buf_oom(&to_buf)) + return -1; + + git_buffer_from_buf(to, &to_buf); + return 0; +} + +static int ident_remove_id( + git_buffer *to, const git_buffer *from) +{ + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + git_buf to_buf = GIT_BUF_FROM_BUFFER(to); + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_ENOTFOUND; + + need_size = (size_t)(id_start - from->ptr) + + 4 /* "$Id$" */ + (size_t)(from_end - id_end); + + if (git_buf_grow(&to_buf, need_size) < 0) + return -1; + + git_buf_set(&to_buf, from->ptr, (size_t)(id_start - from->ptr)); + git_buf_put(&to_buf, "$Id$", 4); + git_buf_put(&to_buf, id_end, (size_t)(from_end - id_end)); + + if (git_buf_oom(&to_buf)) + return -1; + + git_buffer_from_buf(to, &to_buf); + return 0; +} + +static int ident_apply( + git_filter *self, + void **payload, + git_buffer *to, + const git_buffer *from, + const git_filter_source *src) +{ + GIT_UNUSED(self); GIT_UNUSED(payload); + + 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; +} diff --git a/src/repository.c b/src/repository.c index 94700e4e3..76e8228b7 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1671,7 +1671,8 @@ int git_repository_hashfile( /* passing empty string for "as_path" indicated --no-filters */ if (strlen(as_path) > 0) { - error = git_filter_list_load(&fl, 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 { diff --git a/tests-clar/filter/blob.c b/tests-clar/filter/blob.c index 27e001f99..c265ed67a 100644 --- a/tests-clar/filter/blob.c +++ b/tests-clar/filter/blob.c @@ -7,7 +7,12 @@ void test_filter_blob__initialize(void) { g_repo = cl_git_sandbox_init("crlf"); cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); + "*.txt text\n*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol.lf\n"); } void test_filter_blob__cleanup(void) @@ -41,3 +46,36 @@ void test_filter_blob__all_crlf(void) git_buffer_free(&buf); git_blob_free(blob); } + +void test_filter_blob__ident(void) +{ + git_oid id; + git_blob *blob; + git_buffer buf = GIT_BUFFER_INIT; + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n"); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + git_blob_free(blob); + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n"); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.bin", 1)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identcrlf", 1)); + cl_assert_equal_s( + "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\r\nGoes there\r\n", buf.ptr); + + cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identlf", 1)); + cl_assert_equal_s( + "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\nGoes there\n", buf.ptr); + +} diff --git a/tests-clar/filter/ident.c b/tests-clar/filter/ident.c new file mode 100644 index 000000000..55774fbdd --- /dev/null +++ b/tests-clar/filter/ident.c @@ -0,0 +1,131 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" + +static git_repository *g_repo = NULL; + +void test_filter_ident__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); +} + +void test_filter_ident__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void add_blob_and_filter( + const char *data, + git_filter_list *fl, + const char *expected) +{ + git_oid id; + git_blob *blob; + git_buffer out = GIT_BUFFER_INIT; + + cl_git_mkfile("crlf/identtest", data); + cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "identtest")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + + cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); + + cl_assert_equal_s(expected, out.ptr); + + git_blob_free(blob); + git_buffer_free(&out); +} + +void test_filter_ident__to_worktree(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", fl, + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Junky$\nFun stuff\n", fl, + "Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9$\nFun stuff\n"); + add_blob_and_filter( + "$Id$\nAt the start\n", fl, + "$Id: b13415c767abc196fb95bd17070e8c1113e32160$\nAt the start\n"); + add_blob_and_filter( + "At the end\n$Id$", fl, + "At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272$"); + add_blob_and_filter( + "$Id$", fl, + "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$"); + add_blob_and_filter( + "$Id: Some sort of junk goes here$", fl, + "$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06$"); + + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} + +void test_filter_ident__to_odb(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Any junk you may have left here$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:x$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + + add_blob_and_filter( + "$Id$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n"); + + add_blob_and_filter( + "At the end\n$Id$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id", fl, "At the end\n$Id"); + add_blob_and_filter( + "At the end\n$IddI", fl, "At the end\n$IddI"); + + add_blob_and_filter("$Id$", fl, "$Id$"); + add_blob_and_filter("$Id: any$", fl, "$Id$"); + add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$"); + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c index a23f897f9..1e82b69cd 100644 --- a/tests-clar/object/blob/filter.c +++ b/tests-clar/object/blob/filter.c @@ -112,8 +112,8 @@ void test_object_blob_filter__to_odb(void) git_attr_cache_flush(g_repo); cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); - cl_git_pass( - git_filter_list_load(&fl, g_repo, "filename.txt", GIT_FILTER_TO_ODB)); + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB)); cl_assert(fl != NULL); for (i = 0; i < NUM_TEST_OBJECTS; i++) {