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/cmd.h b/src/cli/cmd.h index bce4709fb7a..b7fe253b286 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -25,6 +25,7 @@ extern const cli_cmd_spec cli_cmds[]; extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); /* Commands */ +extern int cmd_add(int argc, char **argv); extern int cmd_blame(int argc, char **argv); extern int cmd_cat_file(int argc, char **argv); extern int cmd_clone(int argc, char **argv); diff --git a/src/cli/cmd_add.c b/src/cli/cmd_add.c new file mode 100644 index 00000000000..3209c0829ef --- /dev/null +++ b/src/cli/cmd_add.c @@ -0,0 +1,81 @@ +/* + * 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 +#include "common.h" +#include "cmd.h" + +#define COMMAND_NAME "add" + +static char **paths; + +static const cli_opt_spec opts[] = { + CLI_COMMON_OPT, + + { CLI_OPT_TYPE_LITERAL }, + { CLI_OPT_TYPE_ARGS, "pathspecs", 0, &paths, 0, + CLI_OPT_USAGE_REQUIRED, "pathspecs", "the paths to add to stage" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0); + printf("\n"); + + printf("Stage the changes in a file or files.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +int cmd_add(int argc, char **argv) +{ + cli_repository_open_options open_opts = { argv + 1, argc - 1}; + cli_opt invalid_opt; + git_repository *repo = NULL; + git_index *index = NULL; + git_strarray pathspec = { 0 }; + size_t path_count = 0; + char **path; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (cli_opt__show_help) { + print_help(); + return 0; + } + + for (path = paths; *path; path++, path_count++) + ; + + { + size_t i = 0; + for(i = 0;imessage : "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..8e1f474de2a 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -30,6 +30,7 @@ const cli_opt_spec cli_common_opts[] = { }; const cli_cmd_spec cli_cmds[] = { + { "add", cmd_add, "Stage the changes in a file or files" }, { "blame", cmd_blame, "Show the origin of each line of a file" }, { "cat-file", cmd_cat_file, "Display an object in the repository" }, { "clone", cmd_clone, "Clone a repository into a new directory" }, @@ -90,11 +91,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 +134,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);