crlf: raise a warning for safecrlf=warn
diff --git a/include/git2/warning.h b/include/git2/warning.h
index 3c25f62..579c64a 100644
--- a/include/git2/warning.h
+++ b/include/git2/warning.h
@@ -26,6 +26,11 @@
 	 * available.
 	 */
 	GIT_WARNING_GENERIC,
+
+	/**
+	 * Warning related to line ending conversion.
+	 */
+	GIT_WARNING_CRLF,
 } git_warning_t;
 
 /**
@@ -45,6 +50,14 @@
 	const char *str;
 } git_warning;
 
+typedef struct {
+	/** The base struct */
+	git_warning parent;
+
+	/** The file this warning refers to */
+	const char *path;
+} git_warning_crlf;
+
 /**
  * User-specified callback for warnings
  *
diff --git a/src/crlf.c b/src/crlf.c
index 5d7510a..bae6711 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -16,6 +16,7 @@
 #include "filter.h"
 #include "buf_text.h"
 #include "repository.h"
+#include "warning.h"
 
 struct crlf_attrs {
 	int crlf_action;
@@ -118,6 +119,8 @@
 	return found_cr;
 }
 
+static const char *lfwarning = "LF would be replaced by CRLF in '%s'";
+
 static int crlf_apply_to_odb(
 	struct crlf_attrs *ca,
 	git_buf *to,
@@ -147,11 +150,28 @@
 			switch (ca->safe_crlf) {
 			case GIT_SAFE_CRLF_FAIL:
 				giterr_set(
-					GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
-					git_filter_source_path(src));
+					GITERR_FILTER, lfwarning, git_filter_source_path(src));
 				return -1;
 			case GIT_SAFE_CRLF_WARN:
-				/* TODO: issue warning when warning API is available */;
+			{
+				int ret;
+				git_warning_crlf warning;
+				git_buf buf = GIT_BUF_INIT;
+
+				if (git_buf_printf(&buf, lfwarning, git_filter_source_path(src)) < 0)
+					return -1;
+
+				warning.parent.type = GIT_WARNING_CRLF;
+				warning.parent.str = buf.ptr;
+				warning.path = git_filter_source_path(src);
+
+				ret = git_warning__raise(&warning.parent);
+				git_buf_free(&buf);
+
+				giterr_set_after_callback(ret);
+				if (ret < 0)
+					return ret;
+			}
 				break;
 			default:
 				break;
diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c
index a8ebd94..7607d89 100644
--- a/tests/filter/crlf.c
+++ b/tests/filter/crlf.c
@@ -16,6 +16,7 @@
 
 void test_filter_crlf__cleanup(void)
 {
+	git_warning_set_callback(NULL, NULL);
 	cl_git_sandbox_cleanup();
 }
 
@@ -192,11 +193,28 @@
 	git_buf_free(&out);
 }
 
+static int crlf_warn_cb(git_warning *warning, void *payload)
+{
+	git_warning_crlf *warn_crlf;
+	int *called = (int *) payload;
+
+	*called = 1;
+	cl_assert_equal_i(GIT_WARNING_CRLF, warning->type);
+	cl_assert_equal_s("LF would be replaced by CRLF in ''", warning->str);
+
+	warn_crlf = (git_warning_crlf *) warning;
+	/* We have "bare" filter lists, so we don't know the path */
+	cl_assert_equal_s("", warn_crlf->path);
+
+	return 0;
+}
+
 void test_filter_crlf__safecrlf_warn(void)
 {
 	git_filter_list *fl;
 	git_filter *crlf;
 	git_buf in = {0}, out = GIT_BUF_INIT;
+	int warning_called;
 
 	cl_repo_set_string(g_repo, "core.safecrlf", "warn");
 
@@ -219,9 +237,12 @@
 	in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n";
 	in.size = strlen(in.ptr);
 
+	warning_called = 0;
+	cl_git_pass(git_warning_set_callback(crlf_warn_cb, &warning_called));
 	cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
-	/* TODO: check for warning */
 	cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr);
+	cl_assert(warning_called);
+	cl_git_pass(git_warning_set_callback(NULL, NULL));
 
 	/* Normalized \n is reversible, so does not fail with safecrlf=warn */
 	in.ptr = "Normal\nLF\nonly\nline-endings.\n";