Commit 40cb40fab93281c808255d980bbe81a18a4d9e9a

Russell Belfer 2013-09-11T14:23:39

Add functions to manipulate filter lists Extend the git2/sys/filter API with functions to look up a filter and add it manually to a filter list. This requires some trickery because the regular attribute lookups and checks are bypassed when this happens, but in the right hands, it will allow a user to have granular control over applying filters.

diff --git a/include/git2/filter.h b/include/git2/filter.h
index cb23ae4..8ef88d8 100644
--- a/include/git2/filter.h
+++ b/include/git2/filter.h
@@ -63,23 +63,6 @@ typedef struct git_filter git_filter;
 typedef struct git_filter_list git_filter_list;
 
 /**
- * Look up a filter by name
- */
-GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
-
-#define GIT_FILTER_CRLF "crlf"
-
-/**
- * Apply a single filter to a buffer of data
- */
-GIT_EXTERN(int) git_filter_apply_to_buffer(
-	git_buffer *out,
-	git_filter *filter,
-	const git_buffer *input,
-	const char *as_path,
-	git_filter_mode_t mode);
-
-/**
  * Load the filter list for a given path.
  *
  * This will return 0 (success) but set the output git_filter_list to NULL
diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h
index dbb086b..ca5738a 100644
--- a/include/git2/sys/filter.h
+++ b/include/git2/sys/filter.h
@@ -19,6 +19,43 @@
 GIT_BEGIN_DECL
 
 /**
+ * Look up a filter by name
+ *
+ * @param name The name of the filter
+ * @return Pointer to the filter object or NULL if not found
+ */
+GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
+
+#define GIT_FILTER_CRLF "crlf"
+
+/**
+ * Create a new empty filter list
+ *
+ * Normally you won't use this because `git_filter_list_load` will create
+ * the filter list for you, but you can use this in combination with the
+ * `git_filter_lookup` and `git_filter_list_push` functions to assemble
+ * your own chains of filters.
+ */
+GIT_EXTERN(int) git_filter_list_new(
+	git_filter_list **out, git_repository *repo, git_filter_mode_t mode);
+
+/**
+ * Add a filter to a filter list with the given payload.
+ *
+ * Normally you won't have to do this because the filter list is created
+ * by calling the "check" function on registered filters when the filter
+ * attributes are set, but this does allow more direct manipulation of
+ * filter lists when desired.
+ *
+ * Note that normally the "check" function can set up a payload for the
+ * filter.  Using this function, you can either pass in a payload if you
+ * know the expected payload format, or you can pass NULL.  Some filters
+ * may fail with a NULL payload.  Good luck!
+ */
+GIT_EXTERN(int) git_filter_list_push(
+	git_filter_list *fl, git_filter *filter, void *payload);
+
+/**
  * A filter source represents a file/blob to be processed
  */
 typedef struct git_filter_source git_filter_source;
diff --git a/src/buffer.c b/src/buffer.c
index aaebac7..07725b9 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -548,9 +548,10 @@ int git_buffer_resize(git_buffer *buffer, size_t want_size)
 int git_buffer_copy(
 	git_buffer *buffer, const void *data, size_t datalen)
 {
-	if (git_buffer__resize(buffer, datalen, false) < 0)
+	if (git_buffer__resize(buffer, datalen + 1, false) < 0)
 		return -1;
 	memcpy(buffer->ptr, data, datalen);
+	buffer->ptr[datalen] = '\0';
 	buffer->size = datalen;
 	return 0;
 }
diff --git a/src/crlf.c b/src/crlf.c
index e974208..99c154f 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -86,6 +86,9 @@ static int has_cr_in_index(const git_filter_source *src)
 	git_off_t blobsize;
 	bool found_cr;
 
+	if (!path)
+		return false;
+
 	if (git_repository_index__weakptr(&index, repo) < 0) {
 		giterr_clear();
 		return false;
@@ -189,9 +192,7 @@ static const char *line_ending(struct crlf_attrs *ca)
 
 	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";
@@ -302,7 +303,12 @@ static int crlf_apply(
 	const git_buffer  *from,
 	const git_filter_source *src)
 {
-	GIT_UNUSED(self);
+	/* initialize payload in case `check` was bypassed */
+	if (!*payload) {
+		int error = crlf_check(self, payload, src, NULL);
+		if (error < 0 && error != GIT_ENOTFOUND)
+			return error;
+	}
 
 	if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
 		return crlf_apply_to_workdir(*payload, to, from);
diff --git a/src/filter.c b/src/filter.c
index 73c2cea..79ccac0 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -181,7 +181,13 @@ static int filter_def_name_key_check(const void *key, const void *fdef)
 {
 	const char *name =
 		fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
-	return name ? -1 : git__strcmp(key, name);
+	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)
@@ -331,7 +337,7 @@ git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
 	return src->mode;
 }
 
-static int git_filter_list_new(
+static int filter_list_new(
 	git_filter_list **out, const git_filter_source *src)
 {
 	git_filter_list *fl = NULL;
@@ -391,6 +397,16 @@ static int filter_list_check_attributes(
 	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,
@@ -441,7 +457,7 @@ int git_filter_list_load(
 		else if (error < 0)
 			break;
 		else {
-			if (!fl && (error = git_filter_list_new(&fl, &src)) < 0)
+			if (!fl && (error = filter_list_new(&fl, &src)) < 0)
 				return error;
 
 			fe = git_array_alloc(fl->filters);
@@ -478,6 +494,36 @@ void git_filter_list_free(git_filter_list *fl)
 	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;
+}
+
 static int filter_list_out_buffer_from_raw(
 	git_buffer *out, const void *ptr, size_t size)
 {
diff --git a/tests-clar/filter/crlf.c b/tests-clar/filter/crlf.c
new file mode 100644
index 0000000..098a85d
--- /dev/null
+++ b/tests-clar/filter/crlf.c
@@ -0,0 +1,83 @@
+#include "clar_libgit2.h"
+#include "git2/sys/filter.h"
+
+static git_repository *g_repo = NULL;
+
+void test_filter_crlf__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");
+}
+
+void test_filter_crlf__cleanup(void)
+{
+	cl_git_sandbox_cleanup();
+}
+
+void test_filter_crlf__to_worktree(void)
+{
+	git_filter_list *fl;
+	git_filter *crlf;
+	git_buffer in = GIT_BUFFER_INIT, out = GIT_BUFFER_INIT;
+
+	{
+		git_config *cfg;
+		cl_git_pass(git_repository_config(&cfg, g_repo));
+		cl_git_pass(git_config_set_string(cfg, "core.autocrlf", "true"));
+		git_config_free(cfg);
+	}
+
+	cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_WORKTREE));
+
+	crlf = git_filter_lookup(GIT_FILTER_CRLF);
+	cl_assert(crlf != NULL);
+
+	cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+	in.ptr = "Some text\nRight here\n";
+	in.size = strlen(in.ptr);
+
+	cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+#ifdef GIT_WIN32
+	cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr);
+#else
+	cl_assert_equal_s("Some text\nRight here\n", out.ptr);
+#endif
+
+	git_filter_list_free(fl);
+	git_buffer_free(&out);
+}
+
+void test_filter_crlf__to_odb(void)
+{
+	git_filter_list *fl;
+	git_filter *crlf;
+	git_buffer in = GIT_BUFFER_INIT, out = GIT_BUFFER_INIT;
+
+	{
+		git_config *cfg;
+		cl_git_pass(git_repository_config(&cfg, g_repo));
+		cl_git_pass(git_config_set_string(cfg, "core.autocrlf", "true"));
+		git_config_free(cfg);
+	}
+
+	cl_git_pass(git_filter_list_new(&fl, g_repo, GIT_FILTER_TO_ODB));
+
+	crlf = git_filter_lookup(GIT_FILTER_CRLF);
+	cl_assert(crlf != NULL);
+
+	cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+	in.ptr = "Some text\r\nRight here\r\n";
+	in.size = strlen(in.ptr);
+
+	cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+	cl_assert_equal_s("Some text\nRight here\n", out.ptr);
+
+	git_filter_list_free(fl);
+	git_buffer_free(&out);
+}