From 8cc0fc4f830f48200b2c07fc4868e9ffcbb49d13 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 27 Feb 2025 15:18:00 +0100 Subject: [PATCH 1/5] 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/5] 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/5] 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 d7b11671f20737b5a1ce8656b2c37da2ca1bcb76 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Feb 2025 15:05:09 +0000 Subject: [PATCH 4/5] 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 ddaaab78b6f4423040a846bc77e24b4da621628c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 3 Mar 2025 19:48:59 +0000 Subject: [PATCH 5/5] checkpoint --- include/git2/notification.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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: *