From 8cc0fc4f830f48200b2c07fc4868e9ffcbb49d13 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 27 Feb 2025 15:18:00 +0100 Subject: [PATCH 1/9] notifications: a notification system for callers Users can now configure a "notification callback"; notifications are raised when a condition occurs in a git repository that an end-user should know about, or that the calling application may wish to act on. Notifications provide structured data, that is provided based on the type of notification (for example, file paths). In addition, an an informative message is provided when a notification is raised; the structured data should be sufficient for callers to build their own message, but the provided message should make that unnecessary. Some examples of likely future use of notifications: * `core.safecrlf=warn` messages are warning-level notifications. This allows the calling application to receive these notifications and display them to the user (for example, sending them to the console). * When a file cannot be written during checkout, we should inform the calling application. By default, git continues to check out when one (or more) paths fail to be written, but conclude that the checkout failed overall. Given this somewhat odd behavior, callers may wish to short-circuit this when any path fails to be written. * Callers may want structured failure information for a `safe.directory` failure so that they can get the file path without having to try to "screen scrape" the git_error message. --- include/git2.h | 1 + include/git2/common.h | 30 ++++++++++++- include/git2/notification.h | 89 +++++++++++++++++++++++++++++++++++++ src/libgit2/notification.c | 17 +++++++ src/libgit2/notification.h | 25 +++++++++++ src/libgit2/settings.c | 21 +++++++++ 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 include/git2/notification.h create mode 100644 src/libgit2/notification.c create mode 100644 src/libgit2/notification.h 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..22ba8f3079f --- /dev/null +++ b/include/git2/notification.h @@ -0,0 +1,89 @@ +/* + * 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 { + GIT_NOTIFICATION_NONE = 0 +} git_notification_t; + +/** @} */ +GIT_END_DECL +#endif 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..d77d2ccff6f --- /dev/null +++ b/src/libgit2/notification.h @@ -0,0 +1,25 @@ +/* + * 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(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; From 5ba12e3884e38c03dcf3135a14e79d316bd7a719 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Feb 2025 13:38:30 +0100 Subject: [PATCH 2/9] filter: produce warnings when `core.safecrlf=warn` Produce warning-level notifications when `core.safecrlf=warn`. --- include/git2/notification.h | 11 ++++++++- src/libgit2/crlf.c | 47 +++++++++++++++++++++++++++++++++++-- src/libgit2/notification.h | 3 ++- tests/libgit2/filter/crlf.c | 43 ++++++++++++++++++++++++++++++--- 4 files changed, 97 insertions(+), 7 deletions(-) diff --git a/include/git2/notification.h b/include/git2/notification.h index 22ba8f3079f..659c3632fae 100644 --- a/include/git2/notification.h +++ b/include/git2/notification.h @@ -81,7 +81,16 @@ typedef enum { * state of the notification. */ typedef enum { - GIT_NOTIFICATION_NONE = 0 + /** + * A notification provided when `core.safecrlf` is configured and a + * file has line-ending reversability problems. + * + * 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; /** @} */ diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c index 1e1f1e84558..cc7e542981a 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, @@ -150,6 +151,42 @@ static git_configmap_value output_eol(struct crlf_attrs *ca) return ca->core_eol; } +static int warn_safecrlf(int direction, const char *filename) +{ + git_str message = GIT_STR_INIT; + int error; + + if (filename && !*filename) + filename = NULL; + + git_str_puts(&message, "in the working copy"); + + if (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"); + + if (git_str_oom(&message)) + error = -1; + else + error = git_notification(GIT_NOTIFICATION_WARN, + GIT_NOTIFICATION_CRLF, + message.ptr, filename); + + git_str_dispose(&message); + return error; +} + GIT_INLINE(int) check_safecrlf( struct crlf_attrs *ca, const git_filter_source *src, @@ -167,7 +204,10 @@ GIT_INLINE(int) check_safecrlf( */ if (stats->crlf) { if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ + int error = warn_safecrlf(GIT_EOL_LF, filename); + + if (error != 0) + return error; } else { if (filename && *filename) git_error_set( @@ -187,7 +227,10 @@ GIT_INLINE(int) check_safecrlf( */ if (stats->crlf != stats->lf) { if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ + int error = warn_safecrlf(GIT_EOL_CRLF, filename); + + if (error != 0) + return error; } else { if (filename && *filename) git_error_set( diff --git a/src/libgit2/notification.h b/src/libgit2/notification.h index d77d2ccff6f..e12810a2a6b 100644 --- a/src/libgit2/notification.h +++ b/src/libgit2/notification.h @@ -20,6 +20,7 @@ extern void *git_notification__data; #define git_notification(level, notification, message, ...) \ ((git_notification__callback == NULL) ? 0 : \ - git_notification__callback(notification, message, git_notification__data, __VA_ARGS__)) + git_notification__callback(level, notification, message, \ + git_notification__data, __VA_ARGS__)) #endif diff --git a/tests/libgit2/filter/crlf.c b/tests/libgit2/filter/crlf.c index 925ea58d2ec..38a0e20989d 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,34 @@ void test_filter_crlf__to_odb(void) git_buf_dispose(&out); } +static int 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, notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -96,6 +116,7 @@ void test_filter_crlf__with_safecrlf(void) 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"; @@ -103,6 +124,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); /* Normalized \n fails for autocrlf=true when safecrlf=true */ in = "Normal\nLF\nonly\nline-endings.\n"; @@ -110,6 +132,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); /* String with \r but without \r\n does not fail with safecrlf */ in = "Normal\nCR only\rand some more\nline-endings.\n"; @@ -117,6 +140,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); git_filter_list_free(fl); git_buf_dispose(&out); @@ -127,10 +151,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, notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)); @@ -146,22 +172,23 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) 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_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_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 +199,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, notification_cb, ¬ification_count)); + cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -189,6 +219,7 @@ void test_filter_crlf__no_safecrlf(void) 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"; @@ -196,6 +227,7 @@ void test_filter_crlf__no_safecrlf(void) 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"; @@ -203,6 +235,7 @@ void test_filter_crlf__no_safecrlf(void) 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 +246,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, notification_cb, ¬ification_count)); cl_git_pass(git_filter_list_new( &fl, g_repo, GIT_FILTER_TO_ODB, 0)); @@ -232,14 +267,15 @@ void test_filter_crlf__safecrlf_warn(void) 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_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"; @@ -247,6 +283,7 @@ void test_filter_crlf__safecrlf_warn(void) 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); From 808e6e8eb3860cf95b64e6b993f2bd7a951009a5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Feb 2025 13:42:21 +0100 Subject: [PATCH 3/9] filter: assert on invalid crlf state Produce an assertion when internally invalid CRLF configuration state exists. --- src/libgit2/crlf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c index cc7e542981a..e2087f940e2 100644 --- a/src/libgit2/crlf.c +++ b/src/libgit2/crlf.c @@ -147,7 +147,7 @@ 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; } From 849cf8e47de362e94817704fe58164793cf2e93d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 4 Mar 2025 12:30:40 +0000 Subject: [PATCH 4/9] filter: use cl_invoke in tests for stack traceability The `cl_invoke` mechanism saves the current state so that helper methods (and callbacks) can use `cl_assert` logic, but attributing the failure to the correct lines (at the invoke-point). --- tests/libgit2/filter/crlf.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/libgit2/filter/crlf.c b/tests/libgit2/filter/crlf.c index 38a0e20989d..b3962a2551a 100644 --- a/tests/libgit2/filter/crlf.c +++ b/tests/libgit2/filter/crlf.c @@ -114,7 +114,7 @@ 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); @@ -122,7 +122,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); @@ -130,7 +130,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); @@ -138,7 +138,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); @@ -170,7 +170,7 @@ 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); @@ -178,7 +178,7 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) 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(1, notification_count); @@ -186,7 +186,7 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) 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(2, notification_count); @@ -217,7 +217,7 @@ 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); @@ -225,7 +225,7 @@ void test_filter_crlf__no_safecrlf(void) 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); @@ -233,7 +233,7 @@ void test_filter_crlf__no_safecrlf(void) 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); @@ -265,7 +265,7 @@ 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); @@ -273,7 +273,7 @@ void test_filter_crlf__safecrlf_warn(void) 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(1, notification_count); @@ -281,7 +281,7 @@ void test_filter_crlf__safecrlf_warn(void) 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); From 9cb599a85c60f7d21eda93fc210bcc4848c412ba Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 3 Mar 2025 19:48:59 +0000 Subject: [PATCH 5/9] filter: raise fatal notifications for core.safecrlf failures Don't just raise CRLF notifications when `core.safecrlf=warn`, also raise notifications when `core.safecrlf=true`. The `core.safecrlf=true` notifications are _fatal_ errors, however. These exist so that users can get the filename that failed during a fatal error. --- include/git2/notification.h | 4 +- src/libgit2/crlf.c | 75 ++++++++++++++++++++----------------- tests/libgit2/filter/crlf.c | 33 ++++++++++++---- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/include/git2/notification.h b/include/git2/notification.h index 659c3632fae..197b7462f18 100644 --- a/include/git2/notification.h +++ b/include/git2/notification.h @@ -83,7 +83,9 @@ typedef enum { typedef enum { /** * A notification provided when `core.safecrlf` is configured and a - * file has line-ending reversability problems. + * 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: * diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c index e2087f940e2..999be05a56c 100644 --- a/src/libgit2/crlf.c +++ b/src/libgit2/crlf.c @@ -151,7 +151,10 @@ static git_configmap_value output_eol(struct crlf_attrs *ca) return ca->core_eol; } -static int warn_safecrlf(int direction, const char *filename) +static int notify_safecrlf( + git_notification_level_t level, + int direction, + const char *filename) { git_str message = GIT_STR_INIT; int error; @@ -179,7 +182,7 @@ static int warn_safecrlf(int direction, const char *filename) if (git_str_oom(&message)) error = -1; else - error = git_notification(GIT_NOTIFICATION_WARN, + error = git_notification(level, GIT_NOTIFICATION_CRLF, message.ptr, filename); @@ -187,38 +190,51 @@ static int warn_safecrlf(int direction, const char *filename) return error; } +static int error_safecrlf( + int direction, + const char *filename) +{ + const char *message = (direction == GIT_EOL_LF) ? + "CRLF would be replaced by LF" : + "LF would be replaced by CRLF"; + + if (filename && *filename) + git_error_set(GIT_ERROR_FILTER, "%s in '%s'", message, + filename); + else + git_error_set(GIT_ERROR_FILTER, "%s", 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) { - int error = warn_safecrlf(GIT_EOL_LF, filename); - - if (error != 0) - return error; - } 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) { /* @@ -226,22 +242,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) { - int error = warn_safecrlf(GIT_EOL_CRLF, filename); - - if (error != 0) - return error; - } 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/tests/libgit2/filter/crlf.c b/tests/libgit2/filter/crlf.c index b3962a2551a..ec51cc447f3 100644 --- a/tests/libgit2/filter/crlf.c +++ b/tests/libgit2/filter/crlf.c @@ -73,7 +73,24 @@ void test_filter_crlf__to_odb(void) git_buf_dispose(&out); } -static int notification_cb( +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, @@ -100,7 +117,7 @@ void test_filter_crlf__with_safecrlf(void) size_t in_len; cl_repo_set_bool(g_repo, "core.safecrlf", true); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, notification_cb, ¬ification_count)); + 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)); @@ -124,7 +141,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); + cl_assert_equal_i(1, notification_count); /* Normalized \n fails for autocrlf=true when safecrlf=true */ in = "Normal\nLF\nonly\nline-endings.\n"; @@ -132,7 +149,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); + 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"; @@ -140,7 +157,7 @@ void test_filter_crlf__with_safecrlf(void) 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(0, notification_count); + cl_assert_equal_i(2, notification_count); git_filter_list_free(fl); git_buf_dispose(&out); @@ -156,7 +173,7 @@ void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) size_t in_len; cl_repo_set_bool(g_repo, "core.safecrlf", true); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, notification_cb, ¬ification_count)); + 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)); @@ -203,7 +220,7 @@ void test_filter_crlf__no_safecrlf(void) const char *in; size_t in_len; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, notification_cb, ¬ification_count)); + 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)); @@ -251,7 +268,7 @@ void test_filter_crlf__safecrlf_warn(void) size_t in_len; cl_repo_set_string(g_repo, "core.safecrlf", "warn"); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_NOTIFICATION_CALLBACK, notification_cb, ¬ification_count)); + 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)); From 8eb89ba558636caa37885354d13471429fe3e3e4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Feb 2025 15:05:09 +0000 Subject: [PATCH 6/9] cli: add a notification handler Provide a notification handler in the CLI so that (for example) warnings and non-fatal error information can be sent to stdout. The CLI ignores "fatal" level errors since they're superfluous; it will get the same error after the function returns. --- src/cli/common.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/common.h | 3 +++ src/cli/main.c | 8 ++------ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/cli/common.c b/src/cli/common.c index dbeefea48ed..e14862acba2 100644 --- a/src/cli/common.c +++ b/src/cli/common.c @@ -15,6 +15,55 @@ #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; + + GIT_UNUSED(notification); + GIT_UNUSED(data); + + /* + * Don't display fatal notifications; we'll get an error back from + * functions for those. + */ + if (level == GIT_NOTIFICATION_FATAL) + return 0; + + switch (level) { + case GIT_NOTIFICATION_ERROR: + level_string = "error"; + break; + case GIT_NOTIFICATION_INFO: + level_string = "info"; + break; + default: + level_string = "warning"; + } + + fprintf(stderr, "%s: %s\n", level_string, message); + fflush(stderr); + + return 0; +} + +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/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; } From 4f24b2729f6f58ea89dd02998b9a484da76b2248 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 4 Mar 2025 12:43:16 +0000 Subject: [PATCH 7/9] cli fixup --- src/cli/common.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/cli/common.c b/src/cli/common.c index e14862acba2..6528dcdfeae 100644 --- a/src/cli/common.c +++ b/src/cli/common.c @@ -26,14 +26,10 @@ static int notification_cb( GIT_UNUSED(notification); GIT_UNUSED(data); - /* - * Don't display fatal notifications; we'll get an error back from - * functions for those. - */ - if (level == GIT_NOTIFICATION_FATAL) - return 0; - switch (level) { + case GIT_NOTIFICATION_FATAL: + level_string = "fatal"; + break; case GIT_NOTIFICATION_ERROR: level_string = "error"; break; @@ -47,7 +43,7 @@ static int notification_cb( fprintf(stderr, "%s: %s\n", level_string, message); fflush(stderr); - return 0; + return (level == GIT_NOTIFICATION_FATAL) ? -1 : 0; } void cli_init(void) From 298a484af2c4feb10319b01bdd68ebfdb2b3bf59 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 6 Mar 2025 09:42:37 +0000 Subject: [PATCH 8/9] cli --- src/cli/common.c | 10 +++++++++- src/cli/error.h | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/cli/common.c b/src/cli/common.c index 6528dcdfeae..bd6cdad72bc 100644 --- a/src/cli/common.c +++ b/src/cli/common.c @@ -22,13 +22,20 @@ static int notification_cb( 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"; @@ -38,12 +45,13 @@ static int notification_cb( break; default: level_string = "warning"; + break; } fprintf(stderr, "%s: %s\n", level_string, message); fflush(stderr); - return (level == GIT_NOTIFICATION_FATAL) ? -1 : 0; + return error; } void cli_init(void) 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; } From 4a51af0c2514ad259cef411f2880a3bada1c501f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 6 Mar 2025 09:42:50 +0000 Subject: [PATCH 9/9] crlf --- src/libgit2/crlf.c | 71 +++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c index 999be05a56c..50729d46153 100644 --- a/src/libgit2/crlf.c +++ b/src/libgit2/crlf.c @@ -151,37 +151,60 @@ static git_configmap_value output_eol(struct crlf_attrs *ca) return ca->core_eol; } -static int notify_safecrlf( - git_notification_level_t level, +static int warning_message( + git_str *message, int direction, const char *filename) { - git_str message = GIT_STR_INIT; - int error; + git_str_puts(message, "in the working copy"); - if (filename && !*filename) - filename = NULL; - - git_str_puts(&message, "in the working copy"); - - if (filename) { - git_str_puts(&message, " of '"); - git_str_puts(&message, filename); - git_str_puts(&message, "'"); + 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"); + 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"); + git_str_puts(message, ", LF will be replaced by CRLF"); else GIT_ASSERT(false); - git_str_printf(&message, " the next time git touches it"); + 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); - if (git_str_oom(&message)) - error = -1; + 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); @@ -194,16 +217,12 @@ static int error_safecrlf( int direction, const char *filename) { - const char *message = (direction == GIT_EOL_LF) ? - "CRLF would be replaced by LF" : - "LF would be replaced by CRLF"; + git_str message = GIT_STR_INIT; - if (filename && *filename) - git_error_set(GIT_ERROR_FILTER, "%s in '%s'", message, - filename); - else - git_error_set(GIT_ERROR_FILTER, "%s", message); + if (error_message(&message, direction, filename) == 0) + git_error_set_str(GIT_ERROR_FILTER, message.ptr); + git_str_dispose(&message); return -1; }