diff --git a/include/git2.h b/include/git2.h index 3457e5f0476..1cb77d4a54a 100644 --- a/include/git2.h +++ b/include/git2.h @@ -40,6 +40,7 @@ #include "git2/message.h" #include "git2/net.h" #include "git2/notes.h" +#include "git2/notification.h" #include "git2/object.h" #include "git2/odb.h" #include "git2/odb_backend.h" diff --git a/include/git2/common.h b/include/git2/common.h index 0be84fa77bd..fe102e0db48 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -257,7 +257,9 @@ typedef enum { GIT_OPT_GET_SERVER_TIMEOUT, GIT_OPT_SET_USER_AGENT_PRODUCT, GIT_OPT_GET_USER_AGENT_PRODUCT, - GIT_OPT_ADD_SSL_X509_CERT + GIT_OPT_ADD_SSL_X509_CERT, + GIT_OPT_SET_NOTIFICATION_CALLBACK, + GIT_OPT_GET_NOTIFICATION_CALLBACK } git_libgit2_opt_t; /** @@ -563,6 +565,32 @@ typedef enum { * > Sets the timeout (in milliseconds) for reading from and writing * > to a remote server. Set to 0 to use the system default. * + * opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, int (*cb)(git_notification_level_t, git_notification_t, const char *, void *, ...), void *data) + * > Sets the notification callback, which will be invoked when + * > notifications occur that the calling program can display or + * > otherwise act on. + * > + * > The callback will be invoked for all informational messages, + * > warnings, and non-fatal errors, as well as continuable errors + * > that libgit2 would otherwise treat as fatal. + * > + * > Users should examine the notification level (which is the first + * > argument) and the notification type (the second argument) to + * > understand whether they want to act and how. The third argument + * > is the default message, so that callers can display warning + * > messages without needing to create them. The fourth argument is + * > the callback data, and the remainder of arguments are the + * > per-notification data; see the notifications for information + * > about what is returned for each notification type. + * > + * > - `cb` the callback to invoke when a warning occurs + * > - `data` data to be provided to warning callbacks, or NULL + * + * opts(GIT_OPT_GET_NOTIFICATION_CALLBACK, int *(*cb)(git_notification_level_t, git_notification_t, const char *, void *, ...), void **data) + * > Gets the current notification callback and callback data, which + * > will be invoked when notifications occur that the calling program + * > can display or otherwise act on. + * * @param option Option key * @return 0 on success, <0 on failure */ diff --git a/include/git2/notification.h b/include/git2/notification.h new file mode 100644 index 00000000000..197b7462f18 --- /dev/null +++ b/include/git2/notification.h @@ -0,0 +1,100 @@ +/* + * 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. + */ +#ifndef INCLUDE_git_notification_h__ +#define INCLUDE_git_notification_h__ + +#include "common.h" + +/** + * @file git2/notification.h + * @brief Git notification routines + * @defgroup git_notification Git notification routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * The notification level. Most of these notifications are "informational"; + * by default, the notification levels below `GIT_NOTIFICATION_FATAL` will + * be raised but continue program execution. For these informational + * notifications, an application _may_ decide to stop processing (by + * returning a non-zero code from the notification callback). An example of + * an informational notification is a line ending misconfiguration when + * `core.safecrlf=warn` is configured. + * + * However, the notification `GIT_NOTIFICATION_FATAL` has different + * behavior; these notifications are raised before libgit2 stops processing + * and gives callers the ability to continue anyway. + */ +typedef enum { + /** + * An informational message; by default, libgit2 will continue + * function execution. + */ + GIT_NOTIFICATION_INFO = 0, + + /** + * A warning; by default, libgit2 will continue function execution + * and will not return an error code. A notification callback can + * override this behavior and cause libgit2 to return immediately. + * + * For example, when line-ending issues are encountered and + * `core.safecrlf=warn`, a warning notification is raised, but + * function execution otherwise continues. + */ + GIT_NOTIFICATION_WARN = 1, + + /** + * An error where, by default, libgit2 would continue function + * execution but return an error code at the end of execution. + * A notification callback can override this behavior and cause + * libgit2 to return immediately. + * + * For example, during checkout, non-fatal errors may be raised + * while trying to write an individual file (perhaps due to + * platform filename limitations). In this case, an error-level + * notification will be raised, checkout will continue to put files + * on disk, but the function will return an error code upon + * completion. + */ + GIT_NOTIFICATION_ERROR = 2, + + /** + * A severe error where, by default, libgit2 would stop function + * execution immediately and return an error code. A caller may + * wish to get additional insight into the error in the structured + * notification content. + * + * For example, a `safe.directory` is a fatal error. + */ + GIT_NOTIFICATION_FATAL = 3 +} git_notification_level_t; + +/** + * The notification type. Any notification that is sent by libgit2 will + * be a unique type, potentially with detailed information about the + * state of the notification. + */ +typedef enum { + /** + * A notification provided when `core.safecrlf` is configured and a + * file has line-ending reversability problems. The level will be + * `WARN` (when `core.safecrlf=warn`) or `FATAL` (when + * `core.safecrlf=on`). + * + * The data will be: + * + * - `const char *path`: the path to the file + * - `const char *message`: the notification message + */ + GIT_NOTIFICATION_CRLF = 1 +} git_notification_t; + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/cli/common.c b/src/cli/common.c index dbeefea48ed..bd6cdad72bc 100644 --- a/src/cli/common.c +++ b/src/cli/common.c @@ -15,6 +15,59 @@ #include "common.h" #include "error.h" +static int notification_cb( + git_notification_level_t level, + git_notification_t notification, + const char *message, + void *data) +{ + const char *level_string; + int error = 0; + + GIT_UNUSED(notification); + GIT_UNUSED(data); + + switch (level) { + case GIT_NOTIFICATION_FATAL: + /* + * Set an error class of 1 to suppress printing the error + * message a second time in our error handler. + */ + level_string = "fatal"; + git_error_set_str(CLI_ERROR_SUPPRESS, message); + error = GIT_EUSER; + break; + case GIT_NOTIFICATION_ERROR: + level_string = "error"; + break; + case GIT_NOTIFICATION_INFO: + level_string = "info"; + break; + default: + level_string = "warning"; + break; + } + + fprintf(stderr, "%s: %s\n", level_string, message); + fflush(stderr); + + return error; +} + +void cli_init(void) +{ + if (git_libgit2_init() < 0 || + git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, notification_cb, NULL) < 0) { + cli_error("failed to initialize libgit2"); + exit(CLI_EXIT_GIT); + } +} + +void cli_shutdown(void) +{ + git_libgit2_shutdown(); +} + static int parse_option(cli_opt *opt, void *data) { git_str kv = GIT_STR_INIT, env = GIT_STR_INIT; diff --git a/src/cli/common.h b/src/cli/common.h index 330f776c91e..ac46a8a7769 100644 --- a/src/cli/common.h +++ b/src/cli/common.h @@ -51,6 +51,9 @@ typedef struct { int args_len; } cli_repository_open_options; +extern void cli_init(void); +extern void cli_shutdown(void); + extern int cli_repository_open( git_repository **out, cli_repository_open_options *opts); diff --git a/src/cli/error.h b/src/cli/error.h index abf8a5160d1..627090ea2ff 100644 --- a/src/cli/error.h +++ b/src/cli/error.h @@ -17,6 +17,8 @@ #define CLI_EXIT_GIT 128 #define CLI_EXIT_USAGE 129 +#define CLI_ERROR_SUPPRESS -1 + #define cli_error__print(fmt) do { \ va_list ap; \ va_start(ap, fmt); \ @@ -41,8 +43,16 @@ GIT_INLINE(int) cli_error_usage(const char *fmt, ...) GIT_INLINE(int) cli_error_git(void) { const git_error *err = git_error_last(); - fprintf(stderr, "%s: %s\n", PROGRAM_NAME, - err ? err->message : "unknown error"); + + /* + * We set an error class of `CLI_ERROR_SUPPRESS` in our + * notification handler since we've shown the message already. + */ + if (err && err->klass != CLI_ERROR_SUPPRESS) { + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, + err ? err->message : "unknown error"); + } + return CLI_EXIT_GIT; } diff --git a/src/cli/main.c b/src/cli/main.c index 4716d6ddee9..a2dcb82c654 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -90,11 +90,7 @@ int main(int argc, char **argv) cli_opt opt; int ret = 0; - if (git_libgit2_init() < 0) { - cli_error("failed to initialize libgit2"); - exit(CLI_EXIT_GIT); - } - + cli_init(); cli_opt_parser_init(&optparser, cli_common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU); /* Parse the top-level (common) options and command information */ @@ -137,6 +133,6 @@ int main(int argc, char **argv) ret = cmd->fn(argc - 1, &argv[1]); done: - git_libgit2_shutdown(); + cli_shutdown(); return ret; } diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c index 1e1f1e84558..50729d46153 100644 --- a/src/libgit2/crlf.c +++ b/src/libgit2/crlf.c @@ -17,6 +17,7 @@ #include "hash.h" #include "filter.h" #include "repository.h" +#include "notification.h" typedef enum { GIT_CRLF_UNDEFINED, @@ -146,39 +147,113 @@ static git_configmap_value output_eol(struct crlf_attrs *ca) return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF; } - /* TODO: warn when available */ + GIT_ASSERT(!"unknown line ending configuration"); return ca->core_eol; } +static int warning_message( + git_str *message, + int direction, + const char *filename) +{ + git_str_puts(message, "in the working copy"); + + if (filename && *filename) { + git_str_puts(message, " of '"); + git_str_puts(message, filename); + git_str_puts(message, "'"); + } + + if (direction == GIT_EOL_LF) + git_str_puts(message, ", CRLF will be replaced by LF"); + else if (direction == GIT_EOL_CRLF) + git_str_puts(message, ", LF will be replaced by CRLF"); + else + GIT_ASSERT(false); + + git_str_printf(message, " the next time git touches it"); + + return git_str_oom(message) ? -1 : 0; +} + +static int error_message( + git_str *message, + int direction, + const char *filename) +{ + git_str_puts(message, (direction == GIT_EOL_LF) ? + "CRLF would be replaced by LF" : + "LF would be replaced by CRLF"); + + if (filename && *filename) + git_str_printf(message, " in '%s'", filename); + + return git_str_oom(message) ? -1 : 0; +} + +static int notify_safecrlf( + git_notification_level_t level, + int direction, + const char *filename) +{ + git_str message = GIT_STR_INIT; + int error; + + if (level == GIT_NOTIFICATION_WARN) + error = warning_message(&message, direction, filename); + else + error = error_message(&message, direction, filename); + + if (!error) + error = git_notification(level, + GIT_NOTIFICATION_CRLF, + message.ptr, filename); + + git_str_dispose(&message); + return error; +} + +static int error_safecrlf( + int direction, + const char *filename) +{ + git_str message = GIT_STR_INIT; + + if (error_message(&message, direction, filename) == 0) + git_error_set_str(GIT_ERROR_FILTER, message.ptr); + + git_str_dispose(&message); + return -1; +} + GIT_INLINE(int) check_safecrlf( struct crlf_attrs *ca, const git_filter_source *src, git_str_text_stats *stats) { const char *filename = git_filter_source_path(src); + git_notification_level_t level; if (!ca->safe_crlf) return 0; + level = (ca->safe_crlf == GIT_SAFE_CRLF_WARN) ? + GIT_NOTIFICATION_WARN : + GIT_NOTIFICATION_FATAL; + if (output_eol(ca) == GIT_EOL_LF) { /* * CRLFs would not be restored by checkout: * check if we'd remove CRLFs */ if (stats->crlf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF"); - - return -1; - } + int error = notify_safecrlf(level, + GIT_EOL_LF, filename); + + if (error != 0) + return error; + else if (ca->safe_crlf != GIT_SAFE_CRLF_WARN) + return error_safecrlf(GIT_EOL_LF, filename); } } else if (output_eol(ca) == GIT_EOL_CRLF) { /* @@ -186,19 +261,13 @@ GIT_INLINE(int) check_safecrlf( * check if we have "naked" LFs */ if (stats->crlf != stats->lf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF"); - - return -1; - } + int error = notify_safecrlf(level, + GIT_EOL_CRLF, filename); + + if (error != 0) + return error; + else if (ca->safe_crlf != GIT_SAFE_CRLF_WARN) + return error_safecrlf(GIT_EOL_CRLF, filename); } } diff --git a/src/libgit2/notification.c b/src/libgit2/notification.c new file mode 100644 index 00000000000..65b6ae354be --- /dev/null +++ b/src/libgit2/notification.c @@ -0,0 +1,17 @@ +/* + * 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 "notification.h" + +int GIT_CALLBACK(git_notification__callback)( + git_notification_level_t, + git_notification_t, + const char *, + void *, + ...) = NULL; + +void *git_notification__data = NULL; diff --git a/src/libgit2/notification.h b/src/libgit2/notification.h new file mode 100644 index 00000000000..e12810a2a6b --- /dev/null +++ b/src/libgit2/notification.h @@ -0,0 +1,26 @@ +/* + * 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. + */ +#ifndef INCLUDE_notification_h__ +#define INCLUDE_notification_h__ + +#include "common.h" +#include "git2/notification.h" + +extern int GIT_CALLBACK(git_notification__callback)( + git_notification_level_t, + git_notification_t, + const char *, + void *, + ...); +extern void *git_notification__data; + +#define git_notification(level, notification, message, ...) \ + ((git_notification__callback == NULL) ? 0 : \ + git_notification__callback(level, notification, message, \ + git_notification__data, __VA_ARGS__)) + +#endif diff --git a/src/libgit2/settings.c b/src/libgit2/settings.c index 2e000f3c69f..ab6635d6b8d 100644 --- a/src/libgit2/settings.c +++ b/src/libgit2/settings.c @@ -26,6 +26,7 @@ #include "runtime.h" #include "sysdir.h" #include "thread.h" +#include "notification.h" #include "git2/global.h" #include "streams/registry.h" #include "streams/mbedtls.h" @@ -459,6 +460,26 @@ int git_libgit2_opts(int key, ...) } break; + case GIT_OPT_SET_NOTIFICATION_CALLBACK: + git_notification__callback = va_arg(ap, + int GIT_CALLBACK()(git_notification_level_t, + git_notification_t, + const char *, + void *, + ...)); + git_notification__data = va_arg(ap, void *); + break; + + case GIT_OPT_GET_NOTIFICATION_CALLBACK: + *(va_arg(ap, int GIT_CALLBACK(*)(git_notification_level_t, + git_notification_t, + const char *, + void *, + ...))) = + git_notification__callback; + *(va_arg(ap, void **)) = git_notification__data; + break; + default: git_error_set(GIT_ERROR_INVALID, "invalid option key"); error = -1; diff --git a/tests/libgit2/filter/crlf.c b/tests/libgit2/filter/crlf.c index 925ea58d2ec..ec51cc447f3 100644 --- a/tests/libgit2/filter/crlf.c +++ b/tests/libgit2/filter/crlf.c @@ -15,6 +15,7 @@ void test_filter_crlf__initialize(void) void test_filter_crlf__cleanup(void) { + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, NULL, NULL)); cl_git_sandbox_cleanup(); } @@ -72,15 +73,51 @@ void test_filter_crlf__to_odb(void) git_buf_dispose(&out); } +static int fatal_notification_cb( + git_notification_level_t notification_level, + git_notification_t notification_type, + const char *message, + void *data, + ...) +{ + GIT_UNUSED(message); + + cl_assert_equal_i(notification_level, GIT_NOTIFICATION_FATAL); + cl_assert_equal_i(notification_type, GIT_NOTIFICATION_CRLF); + + (*((int *)data))++; + + return 0; +} + +static int warn_notification_cb( + git_notification_level_t notification_level, + git_notification_t notification_type, + const char *message, + void *data, + ...) +{ + GIT_UNUSED(message); + + cl_assert_equal_i(notification_level, GIT_NOTIFICATION_WARN); + cl_assert_equal_i(notification_type, GIT_NOTIFICATION_CRLF); + + (*((int *)data))++; + + return 0; +} + void test_filter_crlf__with_safecrlf(void) { git_filter_list *fl; git_filter *crlf; git_buf out = GIT_BUF_INIT; + int notification_count = 0; const char *in; size_t in_len; cl_repo_set_bool(g_repo, "core.safecrlf", true); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, fatal_notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -94,29 +131,33 @@ void test_filter_crlf__with_safecrlf(void) in = "Normal\r\nCRLF\r\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); /* Mix of line endings fails with safecrlf */ in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; in_len = strlen(in); - cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); + cl_assert_equal_i(1, notification_count); /* Normalized \n fails for autocrlf=true when safecrlf=true */ in = "Normal\nLF\nonly\nline-endings.\n"; in_len = strlen(in); - cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); + cl_assert_equal_i(2, notification_count); /* String with \r but without \r\n does not fail with safecrlf */ in = "Normal\nCR only\rand some more\nline-endings.\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nCR only\rand some more\nline-endings.\n", out.ptr); + cl_assert_equal_i(2, notification_count); git_filter_list_free(fl); git_buf_dispose(&out); @@ -127,10 +168,12 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) git_filter_list *fl; git_filter *crlf; git_buf out = GIT_BUF_INIT; + int notification_count = 0; const char *in; size_t in_len; cl_repo_set_bool(g_repo, "core.safecrlf", true); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, warn_notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)); @@ -144,24 +187,25 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) in = "Normal\r\nCRLF\r\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); /* Mix of line endings fails with safecrlf, but allowed to pass */ in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(1, notification_count); /* Normalized \n fails with safecrlf, but allowed to pass */ in = "Normal\nLF\nonly\nline-endings.\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); + cl_assert_equal_i(2, notification_count); git_filter_list_free(fl); git_buf_dispose(&out); @@ -172,9 +216,12 @@ void test_filter_crlf__no_safecrlf(void) git_filter_list *fl; git_filter *crlf; git_buf out = GIT_BUF_INIT; + int notification_count = 0; const char *in; size_t in_len; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, warn_notification_cb, ¬ification_count)); + cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -187,22 +234,25 @@ void test_filter_crlf__no_safecrlf(void) in = "Normal\r\nCRLF\r\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); /* Mix of line endings fails with safecrlf */ in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); /* Normalized \n fails with safecrlf */ in = "Normal\nLF\nonly\nline-endings.\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); git_filter_list_free(fl); git_buf_dispose(&out); @@ -213,10 +263,12 @@ void test_filter_crlf__safecrlf_warn(void) git_filter_list *fl; git_filter *crlf; git_buf out = GIT_BUF_INIT; + int notification_count = 0; const char *in; size_t in_len; cl_repo_set_string(g_repo, "core.safecrlf", "warn"); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, warn_notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -230,23 +282,25 @@ void test_filter_crlf__safecrlf_warn(void) in = "Normal\r\nCRLF\r\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(0, notification_count); /* Mix of line endings succeeds with safecrlf=warn */ in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + cl_assert_equal_i(1, notification_count); /* Normalized \n is reversible, so does not fail with safecrlf=warn */ in = "Normal\nLF\nonly\nline-endings.\n"; in_len = strlen(in); - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_invoke(cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len))); cl_assert_equal_s(in, out.ptr); + cl_assert_equal_i(2, notification_count); git_filter_list_free(fl); git_buf_dispose(&out);