From 97613b9cb91eb97b8a4df547396465f3184ccdef Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:45:56 +0200 Subject: [PATCH 01/46] transport-helper: fix leaking helper name When initializing the transport helper in `transport_get()`, we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks. Fix this memory leak by freeing the string at the calling side in `transport_get()`. `transport_helper_init()` now creates its own copy of the string and thus can free it as required. An alterantive way to fix this would be to transfer ownership of the string passed into `transport_helper_init()`, which would avoid the call to xstrdup(1). But it does make for a more surprising calling convention as we do not typically transfer ownership of strings like this. Mark now-passing tests as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t0611-reftable-httpd.sh | 1 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + transport-helper.c | 6 ++++-- transport.c | 1 + 6 files changed, 9 insertions(+), 2 deletions(-) diff --git a/t/t0611-reftable-httpd.sh b/t/t0611-reftable-httpd.sh index 5e05b9c1f2..2805995cc8 100755 --- a/t/t0611-reftable-httpd.sh +++ b/t/t0611-reftable-httpd.sh @@ -2,6 +2,7 @@ test_description='reftable HTTPD tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 5d5caa3f58..4af796de67 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -2,6 +2,7 @@ test_description='test http auth header and credential helper interop' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index 9da5134614..bb35b87071 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh @@ -2,6 +2,7 @@ test_description="test fetching through http proxy" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5581-http-curl-verbose.sh b/t/t5581-http-curl-verbose.sh index cded79c16b..724f610054 100755 --- a/t/t5581-http-curl-verbose.sh +++ b/t/t5581-http-curl-verbose.sh @@ -4,6 +4,7 @@ test_description='test GIT_CURL_VERBOSE' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/transport-helper.c b/transport-helper.c index 780fcaf529..9820947ab2 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -22,7 +22,7 @@ static int debug; struct helper_data { - const char *name; + char *name; struct child_process *helper; FILE *out; unsigned fetch : 1, @@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data; transport_take_over(transport, data->helper); fclose(data->out); + free(data->name); free(data); } @@ -253,6 +254,7 @@ static int disconnect_helper(struct transport *transport) close(data->helper->out); fclose(data->out); res = finish_command(data->helper); + FREE_AND_NULL(data->name); FREE_AND_NULL(data->helper); } return res; @@ -1297,7 +1299,7 @@ static struct transport_vtable vtable = { int transport_helper_init(struct transport *transport, const char *name) { struct helper_data *data = xcalloc(1, sizeof(*data)); - data->name = name; + data->name = xstrdup(name); transport_check_allowed(name); diff --git a/transport.c b/transport.c index 0ad04b77fd..83ddea8fbc 100644 --- a/transport.c +++ b/transport.c @@ -1176,6 +1176,7 @@ struct transport *transport_get(struct remote *remote, const char *url) int len = external_specification_len(url); char *handler = xmemdupz(url, len); transport_helper_init(ret, handler); + free(handler); } if (ret->smart_options) { From 94e2aa555e7dab4f5296a8dcd8605d751e02b12d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:01 +0200 Subject: [PATCH 02/46] strbuf: fix leak when `appendwholeline()` fails with EOF In `strbuf_appendwholeline()` we call `strbuf_getwholeline()` with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can cause a leak though because `strbuf_getwholeline()` calls getline(3). Quoting its documentation: If *lineptr was set to NULL before the call, then the buffer should be freed by the user program even on failure. Consequently, the temporary buffer may hold allocated memory even when the call to `strbuf_getwholeline()` fails. Fix this by releasing the temporary buffer on error. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- strbuf.c | 4 +++- t/t1400-update-ref.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/strbuf.c b/strbuf.c index 0d929e4e19..e1076c9891 100644 --- a/strbuf.c +++ b/strbuf.c @@ -691,8 +691,10 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term) int strbuf_appendwholeline(struct strbuf *sb, FILE *fp, int term) { struct strbuf line = STRBUF_INIT; - if (strbuf_getwholeline(&line, fp, term)) + if (strbuf_getwholeline(&line, fp, term)) { + strbuf_release(&line); return EOF; + } strbuf_addbuf(sb, &line); strbuf_release(&line); return 0; diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ec3443cc87..bbee2783ab 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -4,6 +4,8 @@ # test_description='Test git update-ref and basic ref logging' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh Z=$ZERO_OID From cc395d6b47e4af59b3e87a64b34dffa79e8dc262 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:06 +0200 Subject: [PATCH 03/46] checkout: clarify memory ownership in `unique_tracking_name()` The function `unique_tracking_name()` returns an allocated string, but does not clearly indicate this because its return type is `const char *` instead of `char *`. This has led to various callsites where we never free its returned memory at all, which causes memory leaks. Plug those leaks and mark now-passing tests as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/checkout.c | 14 +++++++------ builtin/worktree.c | 20 ++++++++++--------- checkout.c | 4 ++-- checkout.h | 6 +++--- t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + 14 files changed, 34 insertions(+), 20 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index f90a4ca4b7..3cf44b4683 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1275,12 +1275,12 @@ static void setup_new_branch_info_and_source_tree( } } -static const char *parse_remote_branch(const char *arg, - struct object_id *rev, - int could_be_checkout_paths) +static char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) { int num_matches = 0; - const char *remote = unique_tracking_name(arg, rev, &num_matches); + char *remote = unique_tracking_name(arg, rev, &num_matches); if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n" @@ -1316,6 +1316,7 @@ static int parse_branchname_arg(int argc, const char **argv, const char **new_branch = &opts->new_branch; int argcount = 0; const char *arg; + char *remote = NULL; int dash_dash_pos; int has_dash_dash = 0; int i; @@ -1416,8 +1417,8 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim = 0; if (recover_with_dwim) { - const char *remote = parse_remote_branch(arg, rev, - could_be_checkout_paths); + remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { *new_branch = arg; arg = remote; @@ -1459,6 +1460,7 @@ static int parse_branchname_arg(int argc, const char **argv, argc--; } + free(remote); return argcount; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 7e0868df72..937da6c0ee 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -736,16 +736,14 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote) return 1; } -static const char *dwim_branch(const char *path, const char **new_branch) +static char *dwim_branch(const char *path, char **new_branch) { int n; int branch_exists; const char *s = worktree_basename(path, &n); - const char *branchname = xstrndup(s, n); + char *branchname = xstrndup(s, n); struct strbuf ref = STRBUF_INIT; - UNLEAK(branchname); - branch_exists = !strbuf_check_branch_ref(&ref, branchname) && refs_ref_exists(get_main_ref_store(the_repository), ref.buf); @@ -756,8 +754,7 @@ static const char *dwim_branch(const char *path, const char **new_branch) *new_branch = branchname; if (guess_remote) { struct object_id oid; - const char *remote = - unique_tracking_name(*new_branch, &oid, NULL); + char *remote = unique_tracking_name(*new_branch, &oid, NULL); return remote; } return NULL; @@ -769,6 +766,8 @@ static int add(int ac, const char **av, const char *prefix) const char *new_branch_force = NULL; char *path; const char *branch; + char *branch_to_free = NULL; + char *new_branch_to_free = NULL; const char *new_branch = NULL; const char *opt_track = NULL; const char *lock_reason = NULL; @@ -859,16 +858,17 @@ static int add(int ac, const char **av, const char *prefix) opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { /* DWIM: Guess branch name from path. */ - const char *s = dwim_branch(path, &new_branch); + char *s = dwim_branch(path, &new_branch_to_free); if (s) - branch = s; + branch = branch_to_free = s; + new_branch = new_branch_to_free; /* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1); } else if (ac == 2) { struct object_id oid; struct commit *commit; - const char *remote; + char *remote; commit = lookup_commit_reference_by_name(branch); if (!commit) { @@ -923,6 +923,8 @@ static int add(int ac, const char **av, const char *prefix) ret = add_worktree(path, branch, &opts); free(path); + free(branch_to_free); + free(new_branch_to_free); return ret; } diff --git a/checkout.c b/checkout.c index 4256e71a7c..cfaea4bd10 100644 --- a/checkout.c +++ b/checkout.c @@ -45,8 +45,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data) return 0; } -const char *unique_tracking_name(const char *name, struct object_id *oid, - int *dwim_remotes_matched) +char *unique_tracking_name(const char *name, struct object_id *oid, + int *dwim_remotes_matched) { struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT; const char *default_remote = NULL; diff --git a/checkout.h b/checkout.h index 3c514a5ab4..ba15a13fb3 100644 --- a/checkout.h +++ b/checkout.h @@ -8,8 +8,8 @@ * tracking branch. Return the name of the remote if such a branch * exists, NULL otherwise. */ -const char *unique_tracking_name(const char *name, - struct object_id *oid, - int *dwim_remotes_matched); +char *unique_tracking_name(const char *name, + struct object_id *oid, + int *dwim_remotes_matched); #endif /* CHECKOUT_H */ diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index a3b1449ef1..2caada3d83 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -4,6 +4,7 @@ test_description='checkout Ensures that checkout on an unborn branch does what the user expects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Is the current branch "refs/heads/$1"? diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh index c91c4db936..77b2346291 100755 --- a/t/t2060-switch.sh +++ b/t/t2060-switch.sh @@ -5,6 +5,7 @@ test_description='switch basic functionality' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index ba069dccbd..94ea88e384 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -2,6 +2,7 @@ test_description='rebase can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index f22d1ddead..9387a22a9e 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -5,6 +5,7 @@ test_description='cherry-pick can handle submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh index 8bfe3ed246..e178968b40 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -2,6 +2,7 @@ test_description='revert can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 98259e2ada..31ac31d4bc 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -8,6 +8,7 @@ test_description='Test of the various options to git rm.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Setup some files to be removed, some with funny characters diff --git a/t/t3906-stash-submodule.sh b/t/t3906-stash-submodule.sh index 0f7348ec21..0f61f01ef4 100755 --- a/t/t3906-stash-submodule.sh +++ b/t/t3906-stash-submodule.sh @@ -2,6 +2,7 @@ test_description='stash can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t4137-apply-submodule.sh b/t/t4137-apply-submodule.sh index 07d5262537..ebd0d4ad17 100755 --- a/t/t4137-apply-submodule.sh +++ b/t/t4137-apply-submodule.sh @@ -2,6 +2,7 @@ test_description='git apply handling submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh index 82013fc903..3946e18089 100755 --- a/t/t6041-bisect-submodule.sh +++ b/t/t6041-bisect-submodule.sh @@ -2,6 +2,7 @@ test_description='bisect can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh index 8df67a0ef9..3594190af8 100755 --- a/t/t6438-submodule-directory-file-conflicts.sh +++ b/t/t6438-submodule-directory-file-conflicts.sh @@ -2,6 +2,7 @@ test_description='merge can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh From f962ffc392fe1831d047a10a28a55710d987d746 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:10 +0200 Subject: [PATCH 04/46] http: refactor code to clarify memory ownership There are various variables assigned via `git_config_string()` and `git_config_pathname()` which are never free'd. This bug is relatable because the out parameter of those functions are a `const char **`, even though memory ownership is transferred to the caller. We're about to adapt the functions to instead use `char **`. Prepare the code accordingly. Note that the `(const char **)` casts will go away once we have adapted the functions. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- http.c | 62 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/http.c b/http.c index 752c879c1f..db2e2f1d39 100644 --- a/http.c +++ b/http.c @@ -39,8 +39,8 @@ char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; static int curl_ssl_try; static const char *curl_http_version = NULL; -static const char *ssl_cert; -static const char *ssl_cert_type; +static char *ssl_cert; +static char *ssl_cert_type; static const char *ssl_cipherlist; static const char *ssl_version; static struct { @@ -59,23 +59,23 @@ static struct { { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, #endif }; -static const char *ssl_key; -static const char *ssl_key_type; -static const char *ssl_capath; -static const char *curl_no_proxy; +static char *ssl_key; +static char *ssl_key_type; +static char *ssl_capath; +static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY static const char *ssl_pinnedkey; #endif -static const char *ssl_cainfo; +static char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; -static const char *curl_http_proxy; -static const char *http_proxy_authmethod; +static char *curl_http_proxy; +static char *http_proxy_authmethod; -static const char *http_proxy_ssl_cert; -static const char *http_proxy_ssl_key; -static const char *http_proxy_ssl_ca_info; +static char *http_proxy_ssl_cert; +static char *http_proxy_ssl_key; +static char *http_proxy_ssl_ca_info; static struct credential proxy_cert_auth = CREDENTIAL_INIT; static int proxy_ssl_cert_password_required; @@ -112,7 +112,7 @@ static const char *curl_cookie_file; static int curl_save_cookies; struct credential http_auth = CREDENTIAL_INIT; static int http_proactive_auth; -static const char *user_agent; +static char *user_agent; static int curl_empty_auth = -1; enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL; @@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value, if (!strcmp("http.sslversion", var)) return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) - return git_config_pathname(&ssl_cert, var, value); + return git_config_pathname((const char **)&ssl_cert, var, value); if (!strcmp("http.sslcerttype", var)) - return git_config_string(&ssl_cert_type, var, value); + return git_config_string((const char **)&ssl_cert_type, var, value); if (!strcmp("http.sslkey", var)) - return git_config_pathname(&ssl_key, var, value); + return git_config_pathname((const char **)&ssl_key, var, value); if (!strcmp("http.sslkeytype", var)) - return git_config_string(&ssl_key_type, var, value); + return git_config_string((const char **)&ssl_key_type, var, value); if (!strcmp("http.sslcapath", var)) - return git_config_pathname(&ssl_capath, var, value); + return git_config_pathname((const char **)&ssl_capath, var, value); if (!strcmp("http.sslcainfo", var)) - return git_config_pathname(&ssl_cainfo, var, value); + return git_config_pathname((const char **)&ssl_cainfo, var, value); if (!strcmp("http.sslcertpasswordprotected", var)) { ssl_cert_password_required = git_config_bool(var, value); return 0; @@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value, return 0; } if (!strcmp("http.proxy", var)) - return git_config_string(&curl_http_proxy, var, value); + return git_config_string((const char **)&curl_http_proxy, var, value); if (!strcmp("http.proxyauthmethod", var)) - return git_config_string(&http_proxy_authmethod, var, value); + return git_config_string((const char **)&http_proxy_authmethod, var, value); if (!strcmp("http.proxysslcert", var)) - return git_config_string(&http_proxy_ssl_cert, var, value); + return git_config_string((const char **)&http_proxy_ssl_cert, var, value); if (!strcmp("http.proxysslkey", var)) - return git_config_string(&http_proxy_ssl_key, var, value); + return git_config_string((const char **)&http_proxy_ssl_key, var, value); if (!strcmp("http.proxysslcainfo", var)) - return git_config_string(&http_proxy_ssl_ca_info, var, value); + return git_config_string((const char **)&http_proxy_ssl_ca_info, var, value); if (!strcmp("http.proxysslcertpasswordprotected", var)) { proxy_ssl_cert_password_required = git_config_bool(var, value); @@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, } if (!strcmp("http.useragent", var)) - return git_config_string(&user_agent, var, value); + return git_config_string((const char **)&user_agent, var, value); if (!strcmp("http.emptyauth", var)) { if (value && !strcmp("auto", value)) @@ -592,10 +592,10 @@ static void init_curl_http_auth(CURL *result) } /* *var must be free-able */ -static void var_override(const char **var, char *value) +static void var_override(char **var, char *value) { if (value) { - free((void *)*var); + free(*var); *var = xstrdup(value); } } @@ -1233,11 +1233,13 @@ static CURL *get_curl_handle(void) return result; } -static void set_from_env(const char **var, const char *envname) +static void set_from_env(char **var, const char *envname) { const char *val = getenv(envname); - if (val) - *var = val; + if (val) { + FREE_AND_NULL(*var); + *var = xstrdup(val); + } } void http_init(struct remote *remote, const char *url, int proactive_auth) From 6073b3b5c37716c50244d635e7c358f41f43e286 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:15 +0200 Subject: [PATCH 05/46] config: clarify memory ownership in `git_config_pathname()` The out parameter of `git_config_pathname()` is a `const char **` even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be `char **`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/blame.c | 2 +- builtin/commit.c | 2 +- builtin/config.c | 2 +- builtin/log.c | 2 +- builtin/receive-pack.c | 4 ++-- config.c | 10 +++++----- config.h | 8 ++++---- diff.c | 2 +- environment.c | 6 +++--- environment.h | 6 +++--- fetch-pack.c | 4 ++-- fsck.c | 4 ++-- fsmonitor-settings.c | 5 ++++- gpg-interface.c | 4 +++- http.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- setup.c | 6 +++--- 18 files changed, 44 insertions(+), 39 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 6bc7aa6085..838cd476be 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -718,7 +718,7 @@ static int git_blame_config(const char *var, const char *value, return 0; } if (!strcmp(var, "blame.ignorerevsfile")) { - const char *str; + char *str; int ret; ret = git_config_pathname(&str, var, value); diff --git a/builtin/commit.c b/builtin/commit.c index 78bfae2164..1cc88e92bf 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -107,7 +107,7 @@ static enum { } commit_style; static const char *logfile, *force_author; -static const char *template_file; +static char *template_file; /* * The _message variables are commit names from which to take * the commit message and/or authorship. diff --git a/builtin/config.c b/builtin/config.c index 80aa9d8a66..cc343f55ca 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -277,7 +277,7 @@ static int format_config(struct strbuf *buf, const char *key_, else strbuf_addstr(buf, v ? "true" : "false"); } else if (type == TYPE_PATH) { - const char *v; + char *v; if (git_config_pathname(&v, key_, value_) < 0) return -1; strbuf_addstr(buf, v); diff --git a/builtin/log.c b/builtin/log.c index b17dd8b40a..a2f5845556 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -957,7 +957,7 @@ static int do_signoff; static enum auto_base_setting auto_base; static char *from; static const char *signature = git_version_string; -static const char *signature_file; +static char *signature_file; static enum cover_setting config_cover_letter; static const char *config_output_directory; static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index be8969a84a..56228ad314 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -168,13 +168,13 @@ static int receive_pack_config(const char *var, const char *value, } if (strcmp(var, "receive.fsck.skiplist") == 0) { - const char *path; + char *path; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&fsck_msg_types, "%cskiplist=%s", fsck_msg_types.len ? ',' : '=', path); - free((char *)path); + free(path); return 0; } diff --git a/config.c b/config.c index d57996240b..fb56e11276 100644 --- a/config.c +++ b/config.c @@ -1346,7 +1346,7 @@ int git_config_string(const char **dest, const char *var, const char *value) return 0; } -int git_config_pathname(const char **dest, const char *var, const char *value) +int git_config_pathname(char **dest, const char *var, const char *value) { if (!value) return config_error_nonbool(var); @@ -1597,7 +1597,7 @@ static int git_default_core_config(const char *var, const char *value, return git_config_string(&askpass_program, var, value); if (!strcmp(var, "core.excludesfile")) { - free((char *)excludes_file); + free(excludes_file); return git_config_pathname(&excludes_file, var, value); } @@ -2494,7 +2494,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d return 1; } -int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest) +int git_configset_get_pathname(struct config_set *set, const char *key, char **dest) { const char *value; if (!git_configset_get_value(set, key, &value, NULL)) @@ -2639,7 +2639,7 @@ int repo_config_get_maybe_bool(struct repository *repo, } int repo_config_get_pathname(struct repository *repo, - const char *key, const char **dest) + const char *key, char **dest) { int ret; git_config_check_init(repo); @@ -2738,7 +2738,7 @@ int git_config_get_maybe_bool(const char *key, int *dest) return repo_config_get_maybe_bool(the_repository, key, dest); } -int git_config_get_pathname(const char *key, const char **dest) +int git_config_get_pathname(const char *key, char **dest) { return repo_config_get_pathname(the_repository, key, dest); } diff --git a/config.h b/config.h index db8b608064..b3103bba94 100644 --- a/config.h +++ b/config.h @@ -286,7 +286,7 @@ int git_config_string(const char **, const char *, const char *); * Similar to `git_config_string`, but expands `~` or `~user` into the * user's home directory when found at the beginning of the path. */ -int git_config_pathname(const char **, const char *, const char *); +int git_config_pathname(char **, const char *, const char *); int git_config_expiry_date(timestamp_t *, const char *, const char *); int git_config_color(char *, const char *, const char *); @@ -541,7 +541,7 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lon int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); -int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); +int git_configset_get_pathname(struct config_set *cs, const char *key, char **dest); /* Functions for reading a repository's config */ struct repository; @@ -577,7 +577,7 @@ int repo_config_get_bool_or_int(struct repository *repo, int repo_config_get_maybe_bool(struct repository *repo, const char *key, int *dest); int repo_config_get_pathname(struct repository *repo, - const char *key, const char **dest); + const char *key, char **dest); /* * Functions for reading protected config. By definition, protected @@ -687,7 +687,7 @@ int git_config_get_maybe_bool(const char *key, int *dest); * Similar to `git_config_get_string`, but expands `~` or `~user` into * the user's home directory when found at the beginning of the path. */ -int git_config_get_pathname(const char *key, const char **dest); +int git_config_get_pathname(const char *key, char **dest); int git_config_get_index_threads(int *dest); int git_config_get_split_index(void); diff --git a/diff.c b/diff.c index ded9ac70df..902df9286a 100644 --- a/diff.c +++ b/diff.c @@ -58,7 +58,7 @@ static int diff_context_default = 3; static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; -static const char *diff_order_file_cfg; +static char *diff_order_file_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; diff --git a/environment.c b/environment.c index a73ba9c12c..279ea3fd5e 100644 --- a/environment.c +++ b/environment.c @@ -46,8 +46,8 @@ const char *git_commit_encoding; const char *git_log_output_encoding; char *apply_default_whitespace; char *apply_default_ignorewhitespace; -const char *git_attributes_file; -const char *git_hooks_path; +char *git_attributes_file; +char *git_hooks_path; int zlib_compression_level = Z_BEST_SPEED; int pack_compression_level = Z_DEFAULT_COMPRESSION; int fsync_object_files = -1; @@ -60,7 +60,7 @@ size_t delta_base_cache_limit = 96 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; const char *editor_program; const char *askpass_program; -const char *excludes_file; +char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; diff --git a/environment.h b/environment.h index 0b2d457f07..be1b88ad6f 100644 --- a/environment.h +++ b/environment.h @@ -131,8 +131,8 @@ extern int warn_ambiguous_refs; extern int warn_on_object_refname_ambiguity; extern char *apply_default_whitespace; extern char *apply_default_ignorewhitespace; -extern const char *git_attributes_file; -extern const char *git_hooks_path; +extern char *git_attributes_file; +extern char *git_hooks_path; extern int zlib_compression_level; extern int pack_compression_level; extern size_t packed_git_window_size; @@ -229,7 +229,7 @@ extern const char *git_log_output_encoding; extern const char *editor_program; extern const char *askpass_program; -extern const char *excludes_file; +extern char *excludes_file; /* * Should we print an ellipsis after an abbreviated SHA-1 value diff --git a/fetch-pack.c b/fetch-pack.c index 8e8f3bba32..d80e9c92dd 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -1865,13 +1865,13 @@ static int fetch_pack_config_cb(const char *var, const char *value, const char *msg_id; if (strcmp(var, "fetch.fsck.skiplist") == 0) { - const char *path; + char *path ; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&fsck_msg_types, "%cskiplist=%s", fsck_msg_types.len ? ',' : '=', path); - free((char *)path); + free(path); return 0; } diff --git a/fsck.c b/fsck.c index 8ef962199f..7dff41413e 100644 --- a/fsck.c +++ b/fsck.c @@ -1330,13 +1330,13 @@ int git_fsck_config(const char *var, const char *value, const char *msg_id; if (strcmp(var, "fsck.skiplist") == 0) { - const char *path; + char *path; struct strbuf sb = STRBUF_INIT; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&sb, "skiplist=%s", path); - free((char *)path); + free(path); fsck_set_msg_types(options, sb.buf); strbuf_release(&sb); return 0; diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c index a6a9e6bc19..e818583420 100644 --- a/fsmonitor-settings.c +++ b/fsmonitor-settings.c @@ -103,6 +103,7 @@ static struct fsmonitor_settings *alloc_settings(void) static void lookup_fsmonitor_settings(struct repository *r) { const char *const_str; + char *to_free = NULL; int bool_value; if (r->settings.fsmonitor) @@ -129,8 +130,9 @@ static void lookup_fsmonitor_settings(struct repository *r) break; case -1: /* config value set to an arbitrary string */ - if (repo_config_get_pathname(r, "core.fsmonitor", &const_str)) + if (repo_config_get_pathname(r, "core.fsmonitor", &to_free)) return; /* should not happen */ + const_str = to_free; break; default: /* should not happen */ @@ -141,6 +143,7 @@ static void lookup_fsmonitor_settings(struct repository *r) fsm_settings__set_hook(r, const_str); else fsm_settings__set_disabled(r); + free(to_free); } enum fsmonitor_mode fsm_settings__get_mode(struct repository *r) diff --git a/gpg-interface.c b/gpg-interface.c index 1ff94266d2..2b50ed0fa0 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -27,7 +27,9 @@ static void gpg_interface_lazy_init(void) } static char *configured_signing_key; -static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file; +static const char *ssh_default_key_command; +static char *ssh_allowed_signers; +static char *ssh_revocation_file; static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { diff --git a/http.c b/http.c index db2e2f1d39..fa3ea87451 100644 --- a/http.c +++ b/http.c @@ -64,7 +64,7 @@ static char *ssl_key_type; static char *ssl_capath; static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY -static const char *ssl_pinnedkey; +static char *ssl_pinnedkey; #endif static char *ssl_cainfo; static long curl_low_speed_limit = -1; @@ -108,7 +108,7 @@ static struct { static struct credential proxy_auth = CREDENTIAL_INIT; static const char *curl_proxyuserpwd; -static const char *curl_cookie_file; +static char *curl_cookie_file; static int curl_save_cookies; struct credential http_auth = CREDENTIAL_INIT; static int http_proactive_auth; @@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value, if (!strcmp("http.sslversion", var)) return git_config_string(&ssl_version, var, value); if (!strcmp("http.sslcert", var)) - return git_config_pathname((const char **)&ssl_cert, var, value); + return git_config_pathname(&ssl_cert, var, value); if (!strcmp("http.sslcerttype", var)) return git_config_string((const char **)&ssl_cert_type, var, value); if (!strcmp("http.sslkey", var)) - return git_config_pathname((const char **)&ssl_key, var, value); + return git_config_pathname(&ssl_key, var, value); if (!strcmp("http.sslkeytype", var)) return git_config_string((const char **)&ssl_key_type, var, value); if (!strcmp("http.sslcapath", var)) - return git_config_pathname((const char **)&ssl_capath, var, value); + return git_config_pathname(&ssl_capath, var, value); if (!strcmp("http.sslcainfo", var)) - return git_config_pathname((const char **)&ssl_cainfo, var, value); + return git_config_pathname(&ssl_cainfo, var, value); if (!strcmp("http.sslcertpasswordprotected", var)) { ssl_cert_password_required = git_config_bool(var, value); return 0; diff --git a/mailmap.c b/mailmap.c index 3d6a5e9400..044466b043 100644 --- a/mailmap.c +++ b/mailmap.c @@ -6,7 +6,7 @@ #include "object-store-ll.h" #include "setup.h" -const char *git_mailmap_file; +char *git_mailmap_file; const char *git_mailmap_blob; struct mailmap_info { diff --git a/mailmap.h b/mailmap.h index 0f8fd2c586..429a760945 100644 --- a/mailmap.h +++ b/mailmap.h @@ -3,7 +3,7 @@ struct string_list; -extern const char *git_mailmap_file; +extern char *git_mailmap_file; extern const char *git_mailmap_blob; int read_mailmap(struct string_list *map); diff --git a/setup.c b/setup.c index 9247cded6a..59ff3a19eb 100644 --- a/setup.c +++ b/setup.c @@ -1177,13 +1177,13 @@ static int safe_directory_cb(const char *key, const char *value, } else if (!strcmp(value, "*")) { data->is_safe = 1; } else { - const char *interpolated = NULL; + char *interpolated = NULL; if (!git_config_pathname(&interpolated, key, value) && !fspathcmp(data->path, interpolated ? interpolated : value)) data->is_safe = 1; - free((char *)interpolated); + free(interpolated); } return 0; @@ -1822,7 +1822,7 @@ static int template_dir_cb(const char *key, const char *value, char *path = NULL; FREE_AND_NULL(data->path); - if (!git_config_pathname((const char **)&path, key, value)) + if (!git_config_pathname(&path, key, value)) data->path = path ? path : xstrdup(value); } From f9c19896749912da7add7ef855ea0543cca91bef Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:20 +0200 Subject: [PATCH 06/46] diff: refactor code to clarify memory ownership of prefixes The source and destination prefixes are tracked in a `const char *` array, but may at times contain allocated strings. The result is that those strings may be leaking because we never free them. Refactor the code to always store allocated strings in those variables, freeing them as required. This requires us to handle the default values a bit different compared to before. But given that there is only a single callsite where we use the variables to `struct diff_options` it's easy to handle the defaults there. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- diff.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/diff.c b/diff.c index 902df9286a..679ef472f4 100644 --- a/diff.c +++ b/diff.c @@ -62,8 +62,8 @@ static char *diff_order_file_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; -static const char *diff_src_prefix = "a/"; -static const char *diff_dst_prefix = "b/"; +static char *diff_src_prefix; +static char *diff_dst_prefix; static int diff_relative; static int diff_stat_name_width; static int diff_stat_graph_width; @@ -411,10 +411,12 @@ int git_diff_ui_config(const char *var, const char *value, return 0; } if (!strcmp(var, "diff.srcprefix")) { - return git_config_string(&diff_src_prefix, var, value); + FREE_AND_NULL(diff_src_prefix); + return git_config_string((const char **) &diff_src_prefix, var, value); } if (!strcmp(var, "diff.dstprefix")) { - return git_config_string(&diff_dst_prefix, var, value); + FREE_AND_NULL(diff_dst_prefix); + return git_config_string((const char **) &diff_dst_prefix, var, value); } if (!strcmp(var, "diff.relative")) { diff_relative = git_config_bool(var, value); @@ -3433,8 +3435,8 @@ void diff_set_noprefix(struct diff_options *options) void diff_set_default_prefix(struct diff_options *options) { - options->a_prefix = diff_src_prefix; - options->b_prefix = diff_dst_prefix; + options->a_prefix = diff_src_prefix ? diff_src_prefix : "a/"; + options->b_prefix = diff_dst_prefix ? diff_dst_prefix : "b/"; } struct userdiff_driver *get_textconv(struct repository *r, @@ -5371,8 +5373,8 @@ static int diff_opt_default_prefix(const struct option *opt, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(optarg); - diff_src_prefix = "a/"; - diff_dst_prefix = "b/"; + FREE_AND_NULL(diff_src_prefix); + FREE_AND_NULL(diff_dst_prefix); diff_set_default_prefix(options); return 0; } From a6cb0cc61033d10eb948057c45dea25c1ab8e151 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:25 +0200 Subject: [PATCH 07/46] convert: refactor code to clarify ownership of check_roundtrip_encoding The `check_roundtrip_encoding` variable is tracked in a `const char *` even though it may contain allocated strings at times. The result is that those strings may be leaking because we never free them. Refactor the code to always store allocated strings in this variable. The default value is handled in `check_roundtrip()` now, which is the only user of the variable. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- config.c | 6 ++++-- convert.c | 24 +++++++++++++----------- convert.h | 2 +- environment.c | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/config.c b/config.c index fb56e11276..f9101045ee 100644 --- a/config.c +++ b/config.c @@ -1564,8 +1564,10 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.checkroundtripencoding")) - return git_config_string(&check_roundtrip_encoding, var, value); + if (!strcmp(var, "core.checkroundtripencoding")) { + FREE_AND_NULL(check_roundtrip_encoding); + return git_config_string((const char **) &check_roundtrip_encoding, var, value); + } if (!strcmp(var, "core.notesref")) { if (!value) diff --git a/convert.c b/convert.c index 35b25eb3cb..03c3c528f9 100644 --- a/convert.c +++ b/convert.c @@ -345,30 +345,32 @@ static int check_roundtrip(const char *enc_name) * space separated encodings (eg. "UTF-16, ASCII, CP1125"). * Search for the given encoding in that string. */ - const char *found = strcasestr(check_roundtrip_encoding, enc_name); + const char *encoding = check_roundtrip_encoding ? + check_roundtrip_encoding : "SHIFT-JIS"; + const char *found = strcasestr(encoding, enc_name); const char *next; int len; if (!found) return 0; next = found + strlen(enc_name); - len = strlen(check_roundtrip_encoding); + len = strlen(encoding); return (found && ( /* - * check that the found encoding is at the - * beginning of check_roundtrip_encoding or - * that it is prefixed with a space or comma + * Check that the found encoding is at the beginning of + * encoding or that it is prefixed with a space or + * comma. */ - found == check_roundtrip_encoding || ( + found == encoding || ( (isspace(found[-1]) || found[-1] == ',') ) ) && ( /* - * check that the found encoding is at the - * end of check_roundtrip_encoding or - * that it is suffixed with a space or comma + * Check that the found encoding is at the end of + * encoding or that it is suffixed with a space + * or comma. */ - next == check_roundtrip_encoding + len || ( - next < check_roundtrip_encoding + len && + next == encoding + len || ( + next < encoding + len && (isspace(next[0]) || next[0] == ',') ) )); diff --git a/convert.h b/convert.h index ab8b4fa68d..d925589444 100644 --- a/convert.h +++ b/convert.h @@ -92,7 +92,7 @@ void convert_attrs(struct index_state *istate, struct conv_attrs *ca, const char *path); extern enum eol core_eol; -extern const char *check_roundtrip_encoding; +extern char *check_roundtrip_encoding; const char *get_cached_convert_stats_ascii(struct index_state *istate, const char *path); const char *get_wt_convert_stats_ascii(const char *path); diff --git a/environment.c b/environment.c index 279ea3fd5e..ab6956559e 100644 --- a/environment.c +++ b/environment.c @@ -64,7 +64,7 @@ char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; -const char *check_roundtrip_encoding = "SHIFT-JIS"; +char *check_roundtrip_encoding; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; From 106a54aecb21fb17956290aee4e9204aa29174e7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:30 +0200 Subject: [PATCH 08/46] builtin/log: stop using globals for log config We're using global variables to store the log configuration. Many of these can be set both via the command line and via the config, and depending on how they are being set, they may contain allocated strings. This leads to hard-to-track memory ownership and memory leaks. Refactor the code to instead use a `struct log_config` that is being allocated on the stack. This allows us to more clearly scope the variables, track memory ownership and ultimately release the memory. This also prepares us for a change to `git_config_string()`, which will be adapted to have a `char **` out parameter instead of `const char **`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/log.c | 265 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 106 deletions(-) diff --git a/builtin/log.c b/builtin/log.c index a2f5845556..f5da29ee2a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -48,22 +48,8 @@ #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 #define FORMAT_PATCH_NAME_MAX_DEFAULT 64 -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode = NULL; - -static int default_abbrev_commit; -static int default_show_root = 1; -static int default_follow; -static int default_show_signature; -static int default_encode_email_headers = 1; -static int decoration_style; -static int decoration_given; -static int use_mailmap_config = 1; static unsigned int force_in_body_from; static int stdout_mboxrd; -static const char *fmt_patch_subject_prefix = "PATCH"; -static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; -static const char *fmt_pretty; static int format_no_prefix; static const char * const builtin_log_usage[] = { @@ -111,6 +97,39 @@ static int parse_decoration_style(const char *value) return -1; } +struct log_config { + int default_abbrev_commit; + int default_show_root; + int default_follow; + int default_show_signature; + int default_encode_email_headers; + int decoration_style; + int decoration_given; + int use_mailmap_config; + char *fmt_patch_subject_prefix; + int fmt_patch_name_max; + char *fmt_pretty; + char *default_date_mode; +}; + +static void log_config_init(struct log_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + cfg->default_show_root = 1; + cfg->default_encode_email_headers = 1; + cfg->use_mailmap_config = 1; + cfg->fmt_patch_subject_prefix = xstrdup("PATCH"); + cfg->fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; + cfg->decoration_style = auto_decoration_style(); +} + +static void log_config_release(struct log_config *cfg) +{ + free(cfg->default_date_mode); + free(cfg->fmt_pretty); + free(cfg->fmt_patch_subject_prefix); +} + static int use_default_decoration_filter = 1; static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; @@ -127,20 +146,22 @@ static int clear_decorations_callback(const struct option *opt UNUSED, return 0; } -static int decorate_callback(const struct option *opt UNUSED, const char *arg, +static int decorate_callback(const struct option *opt, const char *arg, int unset) { - if (unset) - decoration_style = 0; - else if (arg) - decoration_style = parse_decoration_style(arg); - else - decoration_style = DECORATE_SHORT_REFS; + struct log_config *cfg = opt->value; - if (decoration_style < 0) + if (unset) + cfg->decoration_style = 0; + else if (arg) + cfg->decoration_style = parse_decoration_style(arg); + else + cfg->decoration_style = DECORATE_SHORT_REFS; + + if (cfg->decoration_style < 0) die(_("invalid --decorate option: %s"), arg); - decoration_given = 1; + cfg->decoration_given = 1; return 0; } @@ -160,32 +181,26 @@ static int log_line_range_callback(const struct option *option, const char *arg, return 0; } -static void init_log_defaults(void) +static void cmd_log_init_defaults(struct rev_info *rev, + struct log_config *cfg) { - init_diff_ui_defaults(); - - decoration_style = auto_decoration_style(); -} - -static void cmd_log_init_defaults(struct rev_info *rev) -{ - if (fmt_pretty) - get_commit_format(fmt_pretty, rev); - if (default_follow) + if (cfg->fmt_pretty) + get_commit_format(cfg->fmt_pretty, rev); + if (cfg->default_follow) rev->diffopt.flags.default_follow_renames = 1; rev->verbose_header = 1; init_diffstat_widths(&rev->diffopt); rev->diffopt.flags.recursive = 1; rev->diffopt.flags.allow_textconv = 1; - rev->abbrev_commit = default_abbrev_commit; - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - rev->patch_name_max = fmt_patch_name_max; - rev->show_signature = default_show_signature; - rev->encode_email_headers = default_encode_email_headers; + rev->abbrev_commit = cfg->default_abbrev_commit; + rev->show_root_diff = cfg->default_show_root; + rev->subject_prefix = cfg->fmt_patch_subject_prefix; + rev->patch_name_max = cfg->fmt_patch_name_max; + rev->show_signature = cfg->default_show_signature; + rev->encode_email_headers = cfg->default_encode_email_headers; - if (default_date_mode) - parse_date_format(default_date_mode, &rev->date_mode); + if (cfg->default_date_mode) + parse_date_format(cfg->default_date_mode, &rev->date_mode); } static void set_default_decoration_filter(struct decoration_filter *decoration_filter) @@ -233,7 +248,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f } static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) + struct rev_info *rev, struct setup_revision_opt *opt, + struct log_config *cfg) { struct userformat_want w; int quiet = 0, source = 0, mailmap; @@ -258,7 +274,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, N_("pattern"), N_("only decorate refs that match ")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, N_("pattern"), N_("do not decorate refs that match ")), - OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), + OPT_CALLBACK_F(0, "decorate", cfg, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "range:file", N_("trace the evolution of line range , or function : in "), @@ -269,7 +285,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, line_cb.rev = rev; line_cb.prefix = prefix; - mailmap = use_mailmap_config; + mailmap = cfg->use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | @@ -314,8 +330,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, * "log --pretty=raw" is special; ignore UI oriented * configuration variables such as decoration. */ - if (!decoration_given) - decoration_style = 0; + if (!cfg->decoration_given) + cfg->decoration_style = 0; if (!rev->abbrev_commit_given) rev->abbrev_commit = 0; } @@ -326,24 +342,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, * Disable decoration loading if the format will not * show them anyway. */ - decoration_style = 0; - } else if (!decoration_style) { + cfg->decoration_style = 0; + } else if (!cfg->decoration_style) { /* * If we are going to show them, make sure we do load * them here, but taking care not to override a * specific style set by config or --decorate. */ - decoration_style = DECORATE_SHORT_REFS; + cfg->decoration_style = DECORATE_SHORT_REFS; } } - if (decoration_style || rev->simplify_by_decoration) { + if (cfg->decoration_style || rev->simplify_by_decoration) { set_default_decoration_filter(&decoration_filter); - if (decoration_style) + if (cfg->decoration_style) rev->show_decorations = 1; - load_ref_decorations(&decoration_filter, decoration_style); + load_ref_decorations(&decoration_filter, cfg->decoration_style); } if (rev->line_level_traverse) @@ -353,16 +369,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) + struct rev_info *rev, struct setup_revision_opt *opt, + struct log_config *cfg) { - cmd_log_init_defaults(rev); - cmd_log_init_finish(argc, argv, prefix, rev, opt); -} - -static int cmd_log_deinit(int ret, struct rev_info *rev) -{ - release_revisions(rev); - return ret; + cmd_log_init_defaults(rev, cfg); + cmd_log_init_finish(argc, argv, prefix, rev, opt, cfg); } /* @@ -566,30 +577,37 @@ static int cmd_log_walk(struct rev_info *rev) static int git_log_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { + struct log_config *cfg = cb; const char *slot_name; - if (!strcmp(var, "format.pretty")) - return git_config_string(&fmt_pretty, var, value); - if (!strcmp(var, "format.subjectprefix")) - return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.pretty")) { + FREE_AND_NULL(cfg->fmt_pretty); + return git_config_string((const char **) &cfg->fmt_pretty, var, value); + } + if (!strcmp(var, "format.subjectprefix")) { + FREE_AND_NULL(cfg->fmt_patch_subject_prefix); + return git_config_string((const char **) &cfg->fmt_patch_subject_prefix, var, value); + } if (!strcmp(var, "format.filenamemaxlength")) { - fmt_patch_name_max = git_config_int(var, value, ctx->kvi); + cfg->fmt_patch_name_max = git_config_int(var, value, ctx->kvi); return 0; } if (!strcmp(var, "format.encodeemailheaders")) { - default_encode_email_headers = git_config_bool(var, value); + cfg->default_encode_email_headers = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.abbrevcommit")) { - default_abbrev_commit = git_config_bool(var, value); + cfg->default_abbrev_commit = git_config_bool(var, value); return 0; } - if (!strcmp(var, "log.date")) - return git_config_string(&default_date_mode, var, value); + if (!strcmp(var, "log.date")) { + FREE_AND_NULL(cfg->default_date_mode); + return git_config_string((const char **) &cfg->default_date_mode, var, value); + } if (!strcmp(var, "log.decorate")) { - decoration_style = parse_decoration_style(value); - if (decoration_style < 0) - decoration_style = 0; /* maybe warn? */ + cfg->decoration_style = parse_decoration_style(value); + if (cfg->decoration_style < 0) + cfg->decoration_style = 0; /* maybe warn? */ return 0; } if (!strcmp(var, "log.diffmerges")) { @@ -598,21 +616,21 @@ static int git_log_config(const char *var, const char *value, return diff_merges_config(value); } if (!strcmp(var, "log.showroot")) { - default_show_root = git_config_bool(var, value); + cfg->default_show_root = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.follow")) { - default_follow = git_config_bool(var, value); + cfg->default_follow = git_config_bool(var, value); return 0; } if (skip_prefix(var, "color.decorate.", &slot_name)) return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { - use_mailmap_config = git_config_bool(var, value); + cfg->use_mailmap_config = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.showsignature")) { - default_show_signature = git_config_bool(var, value); + cfg->default_show_signature = git_config_bool(var, value); return 0; } @@ -621,11 +639,14 @@ static int git_log_config(const char *var, const char *value, int cmd_whatchanged(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); @@ -635,10 +656,15 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; - cmd_log_init(argc, argv, prefix, &rev, &opt); + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + + ret = cmd_log_walk(&rev); + + release_revisions(&rev); + log_config_release(&cfg); + return ret; } static void show_tagger(const char *buf, struct rev_info *rev) @@ -733,14 +759,16 @@ static void show_setup_revisions_tweak(struct rev_info *rev) int cmd_show(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; unsigned int i; struct setup_revision_opt opt; struct pathspec match_all; int ret = 0; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); if (the_repository->gitdir) { prepare_repo_settings(the_repository); @@ -759,10 +787,14 @@ int cmd_show(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.tweak = show_setup_revisions_tweak; - cmd_log_init(argc, argv, prefix, &rev, &opt); + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); - if (!rev.no_walk) - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + if (!rev.no_walk) { + ret = cmd_log_walk(&rev); + release_revisions(&rev); + log_config_release(&cfg); + return ret; + } rev.diffopt.no_free = 1; for (i = 0; i < rev.pending.nr && !ret; i++) { @@ -832,8 +864,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.diffopt.no_free = 0; diff_free(&rev.diffopt); + release_revisions(&rev); + log_config_release(&cfg); - return cmd_log_deinit(ret, &rev); + return ret; } /* @@ -841,11 +875,14 @@ int cmd_show(int argc, const char **argv, const char *prefix) */ int cmd_log_reflog(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); init_reflog_walk(&rev.reflog_info); @@ -854,14 +891,18 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - cmd_log_init_defaults(&rev); + cmd_log_init_defaults(&rev, &cfg); rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; - cmd_log_init_finish(argc, argv, prefix, &rev, &opt); + cmd_log_init_finish(argc, argv, prefix, &rev, &opt, &cfg); - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + ret = cmd_log_walk(&rev); + + release_revisions(&rev); + log_config_release(&cfg); + return ret; } static void log_setup_revisions_tweak(struct rev_info *rev) @@ -876,11 +917,14 @@ static void log_setup_revisions_tweak(struct rev_info *rev) int cmd_log(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); @@ -890,8 +934,13 @@ int cmd_log(int argc, const char **argv, const char *prefix) opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; opt.tweak = log_setup_revisions_tweak; - cmd_log_init(argc, argv, prefix, &rev, &opt); - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); + + ret = cmd_log_walk(&rev); + + release_revisions(&rev); + log_config_release(&cfg); + return ret; } /* format-patch */ @@ -1884,6 +1933,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, int cmd_format_patch(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct commit *commit; struct commit **list = NULL; struct rev_info rev; @@ -1943,7 +1993,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at instead of 1")), OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"), N_("mark the series as Nth re-roll")), - OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, + OPT_INTEGER(0, "filename-max-length", &cfg.fmt_patch_name_max, N_("max length of output filename")), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add (default 'RFC') before 'PATCH'"), @@ -2017,16 +2067,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; - init_log_defaults(); + log_config_init(&cfg); + init_diff_ui_defaults(); init_display_notes(¬es_opt); - git_config(git_format_config, NULL); + git_config(git_format_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; - rev.encode_email_headers = default_encode_email_headers; + rev.encode_email_headers = cfg.default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; @@ -2037,7 +2088,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; - strbuf_addstr(&sprefix, fmt_patch_subject_prefix); + strbuf_addstr(&sprefix, cfg.fmt_patch_subject_prefix); if (format_no_prefix) diff_set_noprefix(&rev.diffopt); @@ -2059,8 +2110,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from; /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ - if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) - fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); + if (cfg.fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) + cfg.fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); if (cover_from_description_arg) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); @@ -2156,7 +2207,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; rev.zero_commit = zero_commit; - rev.patch_name_max = fmt_patch_name_max; + rev.patch_name_max = cfg.fmt_patch_name_max; if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary = 1; @@ -2450,7 +2501,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (rev.ref_message_ids) string_list_clear(rev.ref_message_ids, 0); free(rev.ref_message_ids); - return cmd_log_deinit(0, &rev); + release_revisions(&rev); + log_config_release(&cfg); + return 0; } static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) From 83024d98f78684fce3d4d0598e6b2b147a4b2ffa Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:34 +0200 Subject: [PATCH 09/46] builtin/log: stop using globals for format config This commit does the exact same as the preceding commit, only for the format configuration instead of the log configuration. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/log.c | 467 ++++++++++++++++++++++++++++---------------------- 1 file changed, 265 insertions(+), 202 deletions(-) diff --git a/builtin/log.c b/builtin/log.c index f5da29ee2a..890bf0c425 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -945,36 +945,6 @@ int cmd_log(int argc, const char **argv, const char *prefix) /* format-patch */ -static const char *fmt_patch_suffix = ".patch"; -static int numbered = 0; -static int auto_number = 1; - -static char *default_attach = NULL; - -static struct string_list extra_hdr = STRING_LIST_INIT_NODUP; -static struct string_list extra_to = STRING_LIST_INIT_NODUP; -static struct string_list extra_cc = STRING_LIST_INIT_NODUP; - -static void add_header(const char *value) -{ - struct string_list_item *item; - int len = strlen(value); - while (len && value[len - 1] == '\n') - len--; - - if (!strncasecmp(value, "to: ", 4)) { - item = string_list_append(&extra_to, value + 4); - len -= 4; - } else if (!strncasecmp(value, "cc: ", 4)) { - item = string_list_append(&extra_cc, value + 4); - len -= 4; - } else { - item = string_list_append(&extra_hdr, value); - } - - item->string[len] = '\0'; -} - enum cover_setting { COVER_UNSET, COVER_OFF, @@ -1001,17 +971,61 @@ enum auto_base_setting { AUTO_BASE_WHEN_ABLE }; -static enum thread_level thread; -static int do_signoff; -static enum auto_base_setting auto_base; -static char *from; -static const char *signature = git_version_string; -static char *signature_file; -static enum cover_setting config_cover_letter; -static const char *config_output_directory; -static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; -static int show_notes; -static struct display_notes_opt notes_opt; +struct format_config { + struct log_config log; + enum thread_level thread; + int do_signoff; + enum auto_base_setting auto_base; + char *base_commit; + char *from; + char *signature; + char *signature_file; + enum cover_setting config_cover_letter; + char *config_output_directory; + enum cover_from_description cover_from_description_mode; + int show_notes; + struct display_notes_opt notes_opt; + int numbered_cmdline_opt; + int numbered; + int auto_number; + char *default_attach; + struct string_list extra_hdr; + struct string_list extra_to; + struct string_list extra_cc; + int keep_subject; + int subject_prefix; + struct strbuf sprefix; + char *fmt_patch_suffix; +}; + +static void format_config_init(struct format_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + log_config_init(&cfg->log); + cfg->cover_from_description_mode = COVER_FROM_MESSAGE; + cfg->auto_number = 1; + string_list_init_dup(&cfg->extra_hdr); + string_list_init_dup(&cfg->extra_to); + string_list_init_dup(&cfg->extra_cc); + strbuf_init(&cfg->sprefix, 0); + cfg->fmt_patch_suffix = xstrdup(".patch"); +} + +static void format_config_release(struct format_config *cfg) +{ + log_config_release(&cfg->log); + free(cfg->base_commit); + free(cfg->from); + free(cfg->signature); + free(cfg->signature_file); + free(cfg->config_output_directory); + free(cfg->default_attach); + string_list_clear(&cfg->extra_hdr, 0); + string_list_clear(&cfg->extra_to, 0); + string_list_clear(&cfg->extra_cc, 0); + strbuf_release(&cfg->sprefix); + free(cfg->fmt_patch_suffix); +} static enum cover_from_description parse_cover_from_description(const char *arg) { @@ -1029,27 +1043,51 @@ static enum cover_from_description parse_cover_from_description(const char *arg) die(_("%s: invalid cover from description mode"), arg); } +static void add_header(struct format_config *cfg, const char *value) +{ + struct string_list_item *item; + int len = strlen(value); + while (len && value[len - 1] == '\n') + len--; + + if (!strncasecmp(value, "to: ", 4)) { + item = string_list_append(&cfg->extra_to, value + 4); + len -= 4; + } else if (!strncasecmp(value, "cc: ", 4)) { + item = string_list_append(&cfg->extra_cc, value + 4); + len -= 4; + } else { + item = string_list_append(&cfg->extra_hdr, value); + } + + item->string[len] = '\0'; +} + static int git_format_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { + struct format_config *cfg = cb; + if (!strcmp(var, "format.headers")) { if (!value) die(_("format.headers without value")); - add_header(value); + add_header(cfg, value); return 0; } - if (!strcmp(var, "format.suffix")) - return git_config_string(&fmt_patch_suffix, var, value); + if (!strcmp(var, "format.suffix")) { + FREE_AND_NULL(cfg->fmt_patch_suffix); + return git_config_string((const char **) &cfg->fmt_patch_suffix, var, value); + } if (!strcmp(var, "format.to")) { if (!value) return config_error_nonbool(var); - string_list_append(&extra_to, value); + string_list_append(&cfg->extra_to, value); return 0; } if (!strcmp(var, "format.cc")) { if (!value) return config_error_nonbool(var); - string_list_append(&extra_cc, value); + string_list_append(&cfg->extra_cc, value); return 0; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") || @@ -1058,69 +1096,76 @@ static int git_format_config(const char *var, const char *value, } if (!strcmp(var, "format.numbered")) { if (value && !strcasecmp(value, "auto")) { - auto_number = 1; + cfg->auto_number = 1; return 0; } - numbered = git_config_bool(var, value); - auto_number = auto_number && numbered; + cfg->numbered = git_config_bool(var, value); + cfg->auto_number = cfg->auto_number && cfg->numbered; return 0; } if (!strcmp(var, "format.attach")) { - if (value && *value) - default_attach = xstrdup(value); - else if (value && !*value) - FREE_AND_NULL(default_attach); - else - default_attach = xstrdup(git_version_string); + if (value && *value) { + FREE_AND_NULL(cfg->default_attach); + cfg->default_attach = xstrdup(value); + } else if (value && !*value) { + FREE_AND_NULL(cfg->default_attach); + } else { + FREE_AND_NULL(cfg->default_attach); + cfg->default_attach = xstrdup(git_version_string); + } return 0; } if (!strcmp(var, "format.thread")) { if (value && !strcasecmp(value, "deep")) { - thread = THREAD_DEEP; + cfg->thread = THREAD_DEEP; return 0; } if (value && !strcasecmp(value, "shallow")) { - thread = THREAD_SHALLOW; + cfg->thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; + cfg->thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { - do_signoff = git_config_bool(var, value); + cfg->do_signoff = git_config_bool(var, value); return 0; } - if (!strcmp(var, "format.signature")) - return git_config_string(&signature, var, value); - if (!strcmp(var, "format.signaturefile")) - return git_config_pathname(&signature_file, var, value); + if (!strcmp(var, "format.signature")) { + FREE_AND_NULL(cfg->signature); + return git_config_string((const char **) &cfg->signature, var, value); + } + if (!strcmp(var, "format.signaturefile")) { + FREE_AND_NULL(cfg->signature_file); + return git_config_pathname(&cfg->signature_file, var, value); + } if (!strcmp(var, "format.coverletter")) { if (value && !strcasecmp(value, "auto")) { - config_cover_letter = COVER_AUTO; + cfg->config_cover_letter = COVER_AUTO; return 0; } - config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; + cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; return 0; } - if (!strcmp(var, "format.outputdirectory")) - return git_config_string(&config_output_directory, var, value); + if (!strcmp(var, "format.outputdirectory")) { + FREE_AND_NULL(cfg->config_output_directory); + return git_config_string((const char **) &cfg->config_output_directory, var, value); + } if (!strcmp(var, "format.useautobase")) { if (value && !strcasecmp(value, "whenAble")) { - auto_base = AUTO_BASE_WHEN_ABLE; + cfg->auto_base = AUTO_BASE_WHEN_ABLE; return 0; } - auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; + cfg->auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; return 0; } if (!strcmp(var, "format.from")) { int b = git_parse_maybe_bool(value); - free(from); + FREE_AND_NULL(cfg->from); if (b < 0) - from = xstrdup(value); + cfg->from = xstrdup(value); else if (b) - from = xstrdup(git_committer_info(IDENT_NO_DATE)); - else - from = NULL; + cfg->from = xstrdup(git_committer_info(IDENT_NO_DATE)); return 0; } if (!strcmp(var, "format.forceinbodyfrom")) { @@ -1130,15 +1175,15 @@ static int git_format_config(const char *var, const char *value, if (!strcmp(var, "format.notes")) { int b = git_parse_maybe_bool(value); if (b < 0) - enable_ref_display_notes(¬es_opt, &show_notes, value); + enable_ref_display_notes(&cfg->notes_opt, &cfg->show_notes, value); else if (b) - enable_default_display_notes(¬es_opt, &show_notes); + enable_default_display_notes(&cfg->notes_opt, &cfg->show_notes); else - disable_display_notes(¬es_opt, &show_notes); + disable_display_notes(&cfg->notes_opt, &cfg->show_notes); return 0; } if (!strcmp(var, "format.coverfromdescription")) { - cover_from_description_mode = parse_cover_from_description(value); + cfg->cover_from_description_mode = parse_cover_from_description(value); return 0; } if (!strcmp(var, "format.mboxrd")) { @@ -1159,7 +1204,7 @@ static int git_format_config(const char *var, const char *value, if (!strcmp(var, "diff.noprefix")) return 0; - return git_log_config(var, value, ctx, cb); + return git_log_config(var, value, ctx, &cfg->log); } static const char *output_directory = NULL; @@ -1247,7 +1292,7 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id = strbuf_detach(&buf, NULL); } -static void print_signature(FILE *file) +static void print_signature(const char *signature, FILE *file) { if (!signature || !*signature) return; @@ -1317,14 +1362,15 @@ static void prepare_cover_text(struct pretty_print_context *pp, const char *branch_name, struct strbuf *sb, const char *encoding, - int need_8bit_cte) + int need_8bit_cte, + const struct format_config *cfg) { const char *subject = "*** SUBJECT HERE ***"; const char *body = "*** BLURB HERE ***"; struct strbuf description_sb = STRBUF_INIT; struct strbuf subject_sb = STRBUF_INIT; - if (cover_from_description_mode == COVER_FROM_NONE) + if (cfg->cover_from_description_mode == COVER_FROM_NONE) goto do_pp; if (description_file && *description_file) @@ -1334,13 +1380,13 @@ static void prepare_cover_text(struct pretty_print_context *pp, if (!description_sb.len) goto do_pp; - if (cover_from_description_mode == COVER_FROM_SUBJECT || - cover_from_description_mode == COVER_FROM_AUTO) + if (cfg->cover_from_description_mode == COVER_FROM_SUBJECT || + cfg->cover_from_description_mode == COVER_FROM_AUTO) body = format_subject(&subject_sb, description_sb.buf, " "); - if (cover_from_description_mode == COVER_FROM_MESSAGE || - (cover_from_description_mode == COVER_FROM_AUTO && - subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + if (cfg->cover_from_description_mode == COVER_FROM_MESSAGE || + (cfg->cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) body = description_sb.buf; else subject = subject_sb.buf; @@ -1377,7 +1423,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, int nr, struct commit **list, const char *description_file, const char *branch_name, - int quiet) + int quiet, + const struct format_config *cfg) { const char *committer; struct shortlog log; @@ -1416,7 +1463,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.encode_email_headers = rev->encode_email_headers; pp_user_info(&pp, NULL, &sb, committer, encoding); prepare_cover_text(&pp, description_file, branch_name, &sb, - encoding, need_8bit_cte); + encoding, need_8bit_cte, cfg); fprintf(rev->diffopt.file, "%s\n", sb.buf); free(pp.after_subject); @@ -1517,29 +1564,30 @@ static const char * const builtin_format_patch_usage[] = { NULL }; -static int keep_subject = 0; +struct keep_callback_data { + struct format_config *cfg; + struct rev_info *revs; +}; static int keep_callback(const struct option *opt, const char *arg, int unset) { + struct keep_callback_data *data = opt->value; BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - ((struct rev_info *)opt->value)->total = -1; - keep_subject = 1; + data->revs->total = -1; + data->cfg->keep_subject = 1; return 0; } -static int subject_prefix = 0; - static int subject_prefix_callback(const struct option *opt, const char *arg, int unset) { - struct strbuf *sprefix; + struct format_config *cfg = opt->value; BUG_ON_OPT_NEG(unset); - sprefix = opt->value; - subject_prefix = 1; - strbuf_reset(sprefix); - strbuf_addstr(sprefix, arg); + cfg->subject_prefix = 1; + strbuf_reset(&cfg->sprefix); + strbuf_addstr(&cfg->sprefix, arg); return 0; } @@ -1556,15 +1604,14 @@ static int rfc_callback(const struct option *opt, const char *arg, return 0; } -static int numbered_cmdline_opt = 0; - static int numbered_callback(const struct option *opt, const char *arg, int unset) { + struct format_config *cfg = opt->value; BUG_ON_OPT_ARG(arg); - *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; + cfg->numbered = cfg->numbered_cmdline_opt = unset ? 0 : 1; if (unset) - auto_number = 0; + cfg->auto_number = 0; return 0; } @@ -1588,13 +1635,14 @@ static int output_directory_callback(const struct option *opt, const char *arg, static int thread_callback(const struct option *opt, const char *arg, int unset) { - enum thread_level *thread = (enum thread_level *)opt->value; + struct format_config *cfg = opt->value; + if (unset) - *thread = THREAD_UNSET; + cfg->thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) - *thread = THREAD_SHALLOW; + cfg->thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) - *thread = THREAD_DEEP; + cfg->thread = THREAD_DEEP; /* * Please update _git_formatpatch() in git-completion.bash * when you add new options. @@ -1630,15 +1678,17 @@ static int inline_callback(const struct option *opt, const char *arg, int unset) return 0; } -static int header_callback(const struct option *opt UNUSED, const char *arg, +static int header_callback(const struct option *opt, const char *arg, int unset) { + struct format_config *cfg = opt->value; + if (unset) { - string_list_clear(&extra_hdr, 0); - string_list_clear(&extra_to, 0); - string_list_clear(&extra_cc, 0); + string_list_clear(&cfg->extra_hdr, 0); + string_list_clear(&cfg->extra_to, 0); + string_list_clear(&cfg->extra_cc, 0); } else { - add_header(arg); + add_header(cfg, arg); } return 0; } @@ -1660,17 +1710,17 @@ static int from_callback(const struct option *opt, const char *arg, int unset) static int base_callback(const struct option *opt, const char *arg, int unset) { - const char **base_commit = opt->value; + struct format_config *cfg = opt->value; if (unset) { - auto_base = AUTO_BASE_NEVER; - *base_commit = NULL; + cfg->auto_base = AUTO_BASE_NEVER; + FREE_AND_NULL(cfg->base_commit); } else if (!strcmp(arg, "auto")) { - auto_base = AUTO_BASE_ALWAYS; - *base_commit = NULL; + cfg->auto_base = AUTO_BASE_ALWAYS; + FREE_AND_NULL(cfg->base_commit); } else { - auto_base = AUTO_BASE_NEVER; - *base_commit = arg; + cfg->auto_base = AUTO_BASE_NEVER; + cfg->base_commit = xstrdup(arg); } return 0; } @@ -1681,7 +1731,7 @@ struct base_tree_info { struct object_id *patch_id; }; -static struct commit *get_base_commit(const char *base_commit, +static struct commit *get_base_commit(const struct format_config *cfg, struct commit **list, int total) { @@ -1689,9 +1739,9 @@ static struct commit *get_base_commit(const char *base_commit, struct commit **rev; int i = 0, rev_nr = 0, auto_select, die_on_failure, ret; - switch (auto_base) { + switch (cfg->auto_base) { case AUTO_BASE_NEVER: - if (base_commit) { + if (cfg->base_commit) { auto_select = 0; die_on_failure = 1; } else { @@ -1701,11 +1751,11 @@ static struct commit *get_base_commit(const char *base_commit, break; case AUTO_BASE_ALWAYS: case AUTO_BASE_WHEN_ABLE: - if (base_commit) { + if (cfg->base_commit) { BUG("requested automatic base selection but a commit was provided"); } else { auto_select = 1; - die_on_failure = auto_base == AUTO_BASE_ALWAYS; + die_on_failure = cfg->auto_base == AUTO_BASE_ALWAYS; } break; default: @@ -1713,9 +1763,9 @@ static struct commit *get_base_commit(const char *base_commit, } if (!auto_select) { - base = lookup_commit_reference_by_name(base_commit); + base = lookup_commit_reference_by_name(cfg->base_commit); if (!base) - die(_("unknown commit %s"), base_commit); + die(_("unknown commit %s"), cfg->base_commit); } else { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); @@ -1933,7 +1983,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, int cmd_format_patch(int argc, const char **argv, const char *prefix) { - struct log_config cfg; + struct format_config cfg; struct commit *commit; struct commit **list = NULL; struct rev_info rev; @@ -1958,7 +2008,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *cover_from_description_arg = NULL; char *description_file = NULL; char *branch_name = NULL; - char *base_commit = NULL; struct base_tree_info bases; struct commit *base; int show_progress = 0; @@ -1969,18 +2018,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf rdiff1 = STRBUF_INIT; struct strbuf rdiff2 = STRBUF_INIT; struct strbuf rdiff_title = STRBUF_INIT; - struct strbuf sprefix = STRBUF_INIT; const char *rfc = NULL; int creation_factor = -1; + const char *signature = git_version_string; + const char *signature_file_arg = NULL; + struct keep_callback_data keep_callback_data = { + .cfg = &cfg, + .revs = &rev, + }; + const char *fmt_patch_suffix = NULL; const struct option builtin_format_patch_options[] = { - OPT_CALLBACK_F('n', "numbered", &numbered, NULL, + OPT_CALLBACK_F('n', "numbered", &cfg, NULL, N_("use [PATCH n/m] even with a single patch"), PARSE_OPT_NOARG, numbered_callback), - OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL, + OPT_CALLBACK_F('N', "no-numbered", &cfg, NULL, N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), - OPT_BOOL('s', "signoff", &do_signoff, N_("add a Signed-off-by trailer")), + OPT_BOOL('s', "signoff", &cfg.do_signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, @@ -1993,7 +2048,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at instead of 1")), OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"), N_("mark the series as Nth re-roll")), - OPT_INTEGER(0, "filename-max-length", &cfg.fmt_patch_name_max, + OPT_INTEGER(0, "filename-max-length", &cfg.log.fmt_patch_name_max, N_("max length of output filename")), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add (default 'RFC') before 'PATCH'"), @@ -2003,13 +2058,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("generate parts of a cover letter based on a branch's description")), OPT_FILENAME(0, "description-file", &description_file, N_("use branch description from file")), - OPT_CALLBACK_F(0, "subject-prefix", &sprefix, N_("prefix"), + OPT_CALLBACK_F(0, "subject-prefix", &cfg, N_("prefix"), N_("use [] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback), OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in "), PARSE_OPT_NONEG, output_directory_callback), - OPT_CALLBACK_F('k', "keep-subject", &rev, NULL, + OPT_CALLBACK_F('k', "keep-subject", &keep_callback_data, NULL, N_("don't strip/add [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff, @@ -2022,11 +2077,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show patch format instead of default (patch + stat)"), 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Messaging")), - OPT_CALLBACK(0, "add-header", NULL, N_("header"), + OPT_CALLBACK(0, "add-header", &cfg, N_("header"), N_("add email header"), header_callback), - OPT_STRING_LIST(0, "to", &extra_to, N_("email"), N_("add To: header")), - OPT_STRING_LIST(0, "cc", &extra_cc, N_("email"), N_("add Cc: header")), - OPT_CALLBACK_F(0, "from", &from, N_("ident"), + OPT_STRING_LIST(0, "to", &cfg.extra_to, N_("email"), N_("add To: header")), + OPT_STRING_LIST(0, "cc", &cfg.extra_cc, N_("email"), N_("add Cc: header")), + OPT_CALLBACK_F(0, "from", &cfg.from, N_("ident"), N_("set From address to (or committer ident if absent)"), PARSE_OPT_OPTARG, from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), @@ -2038,15 +2093,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, inline_callback), - OPT_CALLBACK_F(0, "thread", &thread, N_("style"), + OPT_CALLBACK_F(0, "thread", &cfg, N_("style"), N_("enable message threading, styles: shallow, deep"), PARSE_OPT_OPTARG, thread_callback), OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), - OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"), + OPT_CALLBACK_F(0, "base", &cfg, N_("base-commit"), N_("add prerequisite tree info to the patch series"), 0, base_callback), - OPT_FILENAME(0, "signature-file", &signature_file, + OPT_FILENAME(0, "signature-file", &signature_file_arg, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_BOOL(0, "progress", &show_progress, @@ -2063,21 +2118,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_END() }; - extra_hdr.strdup_strings = 1; - extra_to.strdup_strings = 1; - extra_cc.strdup_strings = 1; - - log_config_init(&cfg); + format_config_init(&cfg); init_diff_ui_defaults(); - init_display_notes(¬es_opt); + init_display_notes(&cfg.notes_opt); git_config(git_format_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); - rev.show_notes = show_notes; - memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); + rev.show_notes = cfg.show_notes; + memcpy(&rev.notes_opt, &cfg.notes_opt, sizeof(cfg.notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; - rev.encode_email_headers = cfg.default_encode_email_headers; + rev.encode_email_headers = cfg.log.default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; @@ -2088,12 +2139,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; - strbuf_addstr(&sprefix, cfg.fmt_patch_subject_prefix); + strbuf_addstr(&cfg.sprefix, cfg.log.fmt_patch_subject_prefix); if (format_no_prefix) diff_set_noprefix(&rev.diffopt); - if (default_attach) { - rev.mime_boundary = default_attach; + if (cfg.default_attach) { + rev.mime_boundary = cfg.default_attach; rev.no_inline = 1; } @@ -2109,60 +2160,63 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from; + if (!fmt_patch_suffix) + fmt_patch_suffix = cfg.fmt_patch_suffix; + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ - if (cfg.fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) - cfg.fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); + if (cfg.log.fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) + cfg.log.fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); if (cover_from_description_arg) - cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + cfg.cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); if (rfc && rfc[0]) { - subject_prefix = 1; + cfg.subject_prefix = 1; if (rfc[0] == '-') - strbuf_addf(&sprefix, " %s", rfc + 1); + strbuf_addf(&cfg.sprefix, " %s", rfc + 1); else - strbuf_insertf(&sprefix, 0, "%s ", rfc); + strbuf_insertf(&cfg.sprefix, 0, "%s ", rfc); } if (reroll_count) { - strbuf_addf(&sprefix, " v%s", reroll_count); + strbuf_addf(&cfg.sprefix, " v%s", reroll_count); rev.reroll_count = reroll_count; } - rev.subject_prefix = sprefix.buf; + rev.subject_prefix = cfg.sprefix.buf; - for (i = 0; i < extra_hdr.nr; i++) { - strbuf_addstr(&buf, extra_hdr.items[i].string); + for (i = 0; i < cfg.extra_hdr.nr; i++) { + strbuf_addstr(&buf, cfg.extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); } - if (extra_to.nr) + if (cfg.extra_to.nr) strbuf_addstr(&buf, "To: "); - for (i = 0; i < extra_to.nr; i++) { + for (i = 0; i < cfg.extra_to.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_to.items[i].string); - if (i + 1 < extra_to.nr) + strbuf_addstr(&buf, cfg.extra_to.items[i].string); + if (i + 1 < cfg.extra_to.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } - if (extra_cc.nr) + if (cfg.extra_cc.nr) strbuf_addstr(&buf, "Cc: "); - for (i = 0; i < extra_cc.nr; i++) { + for (i = 0; i < cfg.extra_cc.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_cc.items[i].string); - if (i + 1 < extra_cc.nr) + strbuf_addstr(&buf, cfg.extra_cc.items[i].string); + if (i + 1 < cfg.extra_cc.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } rev.extra_headers = to_free = strbuf_detach(&buf, NULL); - if (from) { - if (split_ident_line(&rev.from_ident, from, strlen(from))) - die(_("invalid ident line: %s"), from); + if (cfg.from) { + if (split_ident_line(&rev.from_ident, cfg.from, strlen(cfg.from))) + die(_("invalid ident line: %s"), cfg.from); } if (start_number < 0) @@ -2173,14 +2227,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * and it would conflict with --keep-subject (-k) from the * command line, reset "numbered". */ - if (numbered && keep_subject && !numbered_cmdline_opt) - numbered = 0; + if (cfg.numbered && cfg.keep_subject && !cfg.numbered_cmdline_opt) + cfg.numbered = 0; - if (numbered && keep_subject) + if (cfg.numbered && cfg.keep_subject) die(_("options '%s' and '%s' cannot be used together"), "-n", "-k"); - if (keep_subject && subject_prefix) + if (cfg.keep_subject && cfg.subject_prefix) die(_("options '%s' and '%s' cannot be used together"), "--subject-prefix/--rfc", "-k"); - rev.preserve_subject = keep_subject; + rev.preserve_subject = cfg.keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) @@ -2207,7 +2261,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; rev.zero_commit = zero_commit; - rev.patch_name_max = cfg.fmt_patch_name_max; + rev.patch_name_max = cfg.log.fmt_patch_name_max; if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary = 1; @@ -2228,7 +2282,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int saved; if (!output_directory) - output_directory = config_output_directory; + output_directory = cfg.config_output_directory; output_directory = set_outdir(prefix, output_directory); if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) @@ -2326,14 +2380,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) goto done; total = nr; if (cover_letter == -1) { - if (config_cover_letter == COVER_AUTO) + if (cfg.config_cover_letter == COVER_AUTO) cover_letter = (total > 1); else - cover_letter = (config_cover_letter == COVER_ON); + cover_letter = (cfg.config_cover_letter == COVER_ON); } - if (!keep_subject && auto_number && (total > 1 || cover_letter)) - numbered = 1; - if (numbered) + if (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter)) + cfg.numbered = 1; + if (cfg.numbered) rev.total = total + start_number - 1; if (idiff_prev.nr) { @@ -2365,27 +2419,40 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) _("Range-diff against v%d:")); } + /* + * The order of precedence is: + * + * 1. The `--signature` and `--no-signature` options. + * 2. The `--signature-file` option. + * 3. The `format.signature` config. + * 4. The `format.signatureFile` config. + * 5. Default `git_version_string`. + */ if (!signature) { ; /* --no-signature inhibits all signatures */ } else if (signature && signature != git_version_string) { ; /* non-default signature already set */ - } else if (signature_file) { + } else if (signature_file_arg || (cfg.signature_file && !cfg.signature)) { struct strbuf buf = STRBUF_INIT; + const char *signature_file = signature_file_arg ? + signature_file_arg : cfg.signature_file; if (strbuf_read_file(&buf, signature_file, 128) < 0) die_errno(_("unable to read signature file '%s'"), signature_file); signature = strbuf_detach(&buf, NULL); + } else if (cfg.signature) { + signature = cfg.signature; } memset(&bases, 0, sizeof(bases)); - base = get_base_commit(base_commit, list, nr); + base = get_base_commit(&cfg, list, nr); if (base) { reset_revision_walk(); clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); } - if (in_reply_to || thread || cover_letter) { + if (in_reply_to || cfg.thread || cover_letter) { rev.ref_message_ids = xmalloc(sizeof(*rev.ref_message_ids)); string_list_init_dup(rev.ref_message_ids); } @@ -2396,19 +2463,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { - if (thread) + if (cfg.thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, !!output_directory, - origin, nr, list, description_file, branch_name, quiet); + origin, nr, list, description_file, branch_name, quiet, &cfg); print_bases(&bases, rev.diffopt.file); - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); total++; start_number--; /* interdiff/range-diff in cover-letter; omit from patches */ rev.idiff_oid1 = NULL; rev.rdiff1 = NULL; } - rev.add_signoff = do_signoff; + rev.add_signoff = cfg.do_signoff; if (show_progress) progress = start_delayed_progress(_("Generating patches"), total); @@ -2418,7 +2485,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) commit = list[nr]; rev.nr = total - nr + (start_number - 1); /* Make the second and subsequent mails replies to the first */ - if (thread) { + if (cfg.thread) { /* Have we already had a message ID? */ if (rev.message_id) { /* @@ -2442,7 +2509,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * letter is a reply to the * --in-reply-to, if specified. */ - if (thread == THREAD_SHALLOW + if (cfg.thread == THREAD_SHALLOW && rev.ref_message_ids->nr > 0 && (!cover_letter || rev.nr > 1)) free(rev.message_id); @@ -2475,7 +2542,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) mime_boundary_leader, rev.mime_boundary); else - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); } if (output_directory) fclose(rev.diffopt.file); @@ -2483,9 +2550,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) stop_progress(&progress); free(list); free(branch_name); - string_list_clear(&extra_to, 0); - string_list_clear(&extra_cc, 0); - string_list_clear(&extra_hdr, 0); if (ignore_if_in_upstream) free_patch_ids(&ids); @@ -2495,14 +2559,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) strbuf_release(&rdiff1); strbuf_release(&rdiff2); strbuf_release(&rdiff_title); - strbuf_release(&sprefix); free(to_free); free(rev.message_id); if (rev.ref_message_ids) string_list_clear(rev.ref_message_ids, 0); free(rev.ref_message_ids); release_revisions(&rev); - log_config_release(&cfg); + format_config_release(&cfg); return 0; } From 1b261c20ed28ad26ddbcd3dff94a248ac6866ac8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:39 +0200 Subject: [PATCH 10/46] config: clarify memory ownership in `git_config_string()` The out parameter of `git_config_string()` is a `const char **` even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be `char **`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- alias.c | 2 +- attr.c | 2 +- attr.h | 2 +- builtin/commit.c | 2 +- builtin/log.c | 12 ++++++------ builtin/merge.c | 4 ++-- builtin/rebase.c | 2 +- builtin/receive-pack.c | 2 +- builtin/repack.c | 8 ++++---- config.c | 6 +++--- config.h | 2 +- convert.c | 6 +++--- delta-islands.c | 2 +- diff.c | 8 ++++---- environment.c | 8 ++++---- environment.h | 8 ++++---- gpg-interface.c | 4 ++-- http.c | 24 ++++++++++++------------ imap-send.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- merge-ll.c | 6 +++--- pager.c | 2 +- pretty.c | 14 +++++++++----- promisor-remote.h | 2 +- remote.c | 20 ++++++++++---------- remote.h | 8 ++++---- sequencer.c | 2 +- upload-pack.c | 2 +- userdiff.h | 12 ++++++------ 30 files changed, 96 insertions(+), 92 deletions(-) diff --git a/alias.c b/alias.c index 5a238f2e30..269892c356 100644 --- a/alias.c +++ b/alias.c @@ -22,7 +22,7 @@ static int config_alias_cb(const char *key, const char *value, if (data->alias) { if (!strcasecmp(p, data->alias)) - return git_config_string((const char **)&data->v, + return git_config_string(&data->v, key, value); } else if (data->list) { string_list_append(data->list, p); diff --git a/attr.c b/attr.c index 33473bdce0..5607db2bbe 100644 --- a/attr.c +++ b/attr.c @@ -25,7 +25,7 @@ #include "tree-walk.h" #include "object-name.h" -const char *git_attr_tree; +char *git_attr_tree; const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; diff --git a/attr.h b/attr.h index 127998ae01..e7cc318b0c 100644 --- a/attr.h +++ b/attr.h @@ -236,6 +236,6 @@ const char *git_attr_global_file(void); /* Return whether the system gitattributes file is enabled and should be used. */ int git_attr_system_is_enabled(void); -extern const char *git_attr_tree; +extern char *git_attr_tree; #endif /* ATTR_H */ diff --git a/builtin/commit.c b/builtin/commit.c index 1cc88e92bf..f53e7e86ff 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -133,7 +133,7 @@ static struct strvec trailer_args = STRVEC_INIT; * is specified explicitly. */ static enum commit_msg_cleanup_mode cleanup_mode; -static const char *cleanup_arg; +static char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; diff --git a/builtin/log.c b/builtin/log.c index 890bf0c425..517d7982f1 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -582,11 +582,11 @@ static int git_log_config(const char *var, const char *value, if (!strcmp(var, "format.pretty")) { FREE_AND_NULL(cfg->fmt_pretty); - return git_config_string((const char **) &cfg->fmt_pretty, var, value); + return git_config_string(&cfg->fmt_pretty, var, value); } if (!strcmp(var, "format.subjectprefix")) { FREE_AND_NULL(cfg->fmt_patch_subject_prefix); - return git_config_string((const char **) &cfg->fmt_patch_subject_prefix, var, value); + return git_config_string(&cfg->fmt_patch_subject_prefix, var, value); } if (!strcmp(var, "format.filenamemaxlength")) { cfg->fmt_patch_name_max = git_config_int(var, value, ctx->kvi); @@ -602,7 +602,7 @@ static int git_log_config(const char *var, const char *value, } if (!strcmp(var, "log.date")) { FREE_AND_NULL(cfg->default_date_mode); - return git_config_string((const char **) &cfg->default_date_mode, var, value); + return git_config_string(&cfg->default_date_mode, var, value); } if (!strcmp(var, "log.decorate")) { cfg->decoration_style = parse_decoration_style(value); @@ -1076,7 +1076,7 @@ static int git_format_config(const char *var, const char *value, } if (!strcmp(var, "format.suffix")) { FREE_AND_NULL(cfg->fmt_patch_suffix); - return git_config_string((const char **) &cfg->fmt_patch_suffix, var, value); + return git_config_string(&cfg->fmt_patch_suffix, var, value); } if (!strcmp(var, "format.to")) { if (!value) @@ -1133,7 +1133,7 @@ static int git_format_config(const char *var, const char *value, } if (!strcmp(var, "format.signature")) { FREE_AND_NULL(cfg->signature); - return git_config_string((const char **) &cfg->signature, var, value); + return git_config_string(&cfg->signature, var, value); } if (!strcmp(var, "format.signaturefile")) { FREE_AND_NULL(cfg->signature_file); @@ -1149,7 +1149,7 @@ static int git_format_config(const char *var, const char *value, } if (!strcmp(var, "format.outputdirectory")) { FREE_AND_NULL(cfg->config_output_directory); - return git_config_string((const char **) &cfg->config_output_directory, var, value); + return git_config_string(&cfg->config_output_directory, var, value); } if (!strcmp(var, "format.useautobase")) { if (value && !strcasecmp(value, "whenAble")) { diff --git a/builtin/merge.c b/builtin/merge.c index e4bd65eeba..daed2d4e1e 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -100,7 +100,7 @@ static struct strategy all_strategy[] = { { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, }; -static const char *pull_twohead, *pull_octopus; +static char *pull_twohead, *pull_octopus; enum ff_type { FF_NO, @@ -110,7 +110,7 @@ enum ff_type { static enum ff_type fast_forward = FF_ALLOW; -static const char *cleanup_arg; +static char *cleanup_arg; static enum commit_msg_cleanup_mode cleanup_mode; static int option_parse_message(const struct option *opt, diff --git a/builtin/rebase.c b/builtin/rebase.c index 0466d9414a..14d4f0a5e6 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -83,7 +83,7 @@ static const char *action_names[] = { struct rebase_options { enum rebase_type type; enum empty_type empty; - const char *default_backend; + char *default_backend; const char *state_dir; struct commit *upstream; const char *upstream_name; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 56228ad314..01c1f04ece 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -88,7 +88,7 @@ static struct strbuf push_cert = STRBUF_INIT; static struct object_id push_cert_oid; static struct signature_check sigcheck; static const char *push_cert_nonce; -static const char *cert_nonce_seed; +static char *cert_nonce_seed; static struct strvec hidden_refs = STRVEC_INIT; static const char *NONCE_UNSOLICITED = "UNSOLICITED"; diff --git a/builtin/repack.c b/builtin/repack.c index 43491a4cbf..e40dceaada 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -48,10 +48,10 @@ static const char incremental_bitmap_conflict_error[] = N_( ); struct pack_objects_args { - const char *window; - const char *window_memory; - const char *depth; - const char *threads; + char *window; + char *window_memory; + char *depth; + char *threads; unsigned long max_pack_size; int no_reuse_delta; int no_reuse_object; diff --git a/config.c b/config.c index f9101045ee..da52a7f8a1 100644 --- a/config.c +++ b/config.c @@ -1338,7 +1338,7 @@ int git_config_bool(const char *name, const char *value) return v; } -int git_config_string(const char **dest, const char *var, const char *value) +int git_config_string(char **dest, const char *var, const char *value) { if (!value) return config_error_nonbool(var); @@ -1566,7 +1566,7 @@ static int git_default_core_config(const char *var, const char *value, if (!strcmp(var, "core.checkroundtripencoding")) { FREE_AND_NULL(check_roundtrip_encoding); - return git_config_string((const char **) &check_roundtrip_encoding, var, value); + return git_config_string(&check_roundtrip_encoding, var, value); } if (!strcmp(var, "core.notesref")) { @@ -2418,7 +2418,7 @@ int git_configset_get_string(struct config_set *set, const char *key, char **des { const char *value; if (!git_configset_get_value(set, key, &value, NULL)) - return git_config_string((const char **)dest, key, value); + return git_config_string(dest, key, value); else return 1; } diff --git a/config.h b/config.h index b3103bba94..2d2e22ed64 100644 --- a/config.h +++ b/config.h @@ -280,7 +280,7 @@ int git_config_bool(const char *, const char *); * Allocates and copies the value string into the `dest` parameter; if no * string is given, prints an error message and returns -1. */ -int git_config_string(const char **, const char *, const char *); +int git_config_string(char **, const char *, const char *); /** * Similar to `git_config_string`, but expands `~` or `~user` into the diff --git a/convert.c b/convert.c index 03c3c528f9..f2b9f01354 100644 --- a/convert.c +++ b/convert.c @@ -981,9 +981,9 @@ int async_query_available_blobs(const char *cmd, struct string_list *available_p static struct convert_driver { const char *name; struct convert_driver *next; - const char *smudge; - const char *clean; - const char *process; + char *smudge; + char *clean; + char *process; int required; } *user_convert, **user_convert_tail; diff --git a/delta-islands.c b/delta-islands.c index 4ac3c10551..89d51b72e3 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -313,7 +313,7 @@ struct island_load_data { size_t nr; size_t alloc; }; -static const char *core_island_name; +static char *core_island_name; static void free_config_regexes(struct island_load_data *ild) { diff --git a/diff.c b/diff.c index 679ef472f4..e70301df76 100644 --- a/diff.c +++ b/diff.c @@ -56,8 +56,8 @@ static int diff_color_moved_default; static int diff_color_moved_ws_default; static int diff_context_default = 3; static int diff_interhunk_context_default; -static const char *diff_word_regex_cfg; -static const char *external_diff_cmd_cfg; +static char *diff_word_regex_cfg; +static char *external_diff_cmd_cfg; static char *diff_order_file_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; @@ -412,11 +412,11 @@ int git_diff_ui_config(const char *var, const char *value, } if (!strcmp(var, "diff.srcprefix")) { FREE_AND_NULL(diff_src_prefix); - return git_config_string((const char **) &diff_src_prefix, var, value); + return git_config_string(&diff_src_prefix, var, value); } if (!strcmp(var, "diff.dstprefix")) { FREE_AND_NULL(diff_dst_prefix); - return git_config_string((const char **) &diff_dst_prefix, var, value); + return git_config_string(&diff_dst_prefix, var, value); } if (!strcmp(var, "diff.relative")) { diff_relative = git_config_bool(var, value); diff --git a/environment.c b/environment.c index ab6956559e..701d515135 100644 --- a/environment.c +++ b/environment.c @@ -42,8 +42,8 @@ int is_bare_repository_cfg = -1; /* unspecified */ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int repository_format_precious_objects; -const char *git_commit_encoding; -const char *git_log_output_encoding; +char *git_commit_encoding; +char *git_log_output_encoding; char *apply_default_whitespace; char *apply_default_ignorewhitespace; char *git_attributes_file; @@ -58,8 +58,8 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 96 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; -const char *editor_program; -const char *askpass_program; +char *editor_program; +char *askpass_program; char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; diff --git a/environment.h b/environment.h index be1b88ad6f..e9f01d4d11 100644 --- a/environment.h +++ b/environment.h @@ -224,11 +224,11 @@ int odb_pack_keep(const char *name); const char *get_log_output_encoding(void); const char *get_commit_output_encoding(void); -extern const char *git_commit_encoding; -extern const char *git_log_output_encoding; +extern char *git_commit_encoding; +extern char *git_log_output_encoding; -extern const char *editor_program; -extern const char *askpass_program; +extern char *editor_program; +extern char *askpass_program; extern char *excludes_file; /* diff --git a/gpg-interface.c b/gpg-interface.c index 2b50ed0fa0..5193223714 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -27,14 +27,14 @@ static void gpg_interface_lazy_init(void) } static char *configured_signing_key; -static const char *ssh_default_key_command; +static char *ssh_default_key_command; static char *ssh_allowed_signers; static char *ssh_revocation_file; static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { const char *name; - const char *program; + char *program; const char **verify_args; const char **sigs; int (*verify_signed_buffer)(struct signature_check *sigc, diff --git a/http.c b/http.c index fa3ea87451..67cc47d28f 100644 --- a/http.c +++ b/http.c @@ -38,11 +38,11 @@ char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; static int curl_ssl_try; -static const char *curl_http_version = NULL; +static char *curl_http_version; static char *ssl_cert; static char *ssl_cert_type; -static const char *ssl_cipherlist; -static const char *ssl_version; +static char *ssl_cipherlist; +static char *ssl_version; static struct { const char *name; long ssl_version; @@ -95,7 +95,7 @@ static struct { */ }; #ifdef CURLGSSAPI_DELEGATION_FLAG -static const char *curl_deleg; +static char *curl_deleg; static struct { const char *name; long curl_deleg_param; @@ -383,11 +383,11 @@ static int http_options(const char *var, const char *value, if (!strcmp("http.sslcert", var)) return git_config_pathname(&ssl_cert, var, value); if (!strcmp("http.sslcerttype", var)) - return git_config_string((const char **)&ssl_cert_type, var, value); + return git_config_string(&ssl_cert_type, var, value); if (!strcmp("http.sslkey", var)) return git_config_pathname(&ssl_key, var, value); if (!strcmp("http.sslkeytype", var)) - return git_config_string((const char **)&ssl_key_type, var, value); + return git_config_string(&ssl_key_type, var, value); if (!strcmp("http.sslcapath", var)) return git_config_pathname(&ssl_capath, var, value); if (!strcmp("http.sslcainfo", var)) @@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value, return 0; } if (!strcmp("http.proxy", var)) - return git_config_string((const char **)&curl_http_proxy, var, value); + return git_config_string(&curl_http_proxy, var, value); if (!strcmp("http.proxyauthmethod", var)) - return git_config_string((const char **)&http_proxy_authmethod, var, value); + return git_config_string(&http_proxy_authmethod, var, value); if (!strcmp("http.proxysslcert", var)) - return git_config_string((const char **)&http_proxy_ssl_cert, var, value); + return git_config_string(&http_proxy_ssl_cert, var, value); if (!strcmp("http.proxysslkey", var)) - return git_config_string((const char **)&http_proxy_ssl_key, var, value); + return git_config_string(&http_proxy_ssl_key, var, value); if (!strcmp("http.proxysslcainfo", var)) - return git_config_string((const char **)&http_proxy_ssl_ca_info, var, value); + return git_config_string(&http_proxy_ssl_ca_info, var, value); if (!strcmp("http.proxysslcertpasswordprotected", var)) { proxy_ssl_cert_password_required = git_config_bool(var, value); @@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, } if (!strcmp("http.useragent", var)) - return git_config_string((const char **)&user_agent, var, value); + return git_config_string(&user_agent, var, value); if (!strcmp("http.emptyauth", var)) { if (value && !strcmp("auto", value)) diff --git a/imap-send.c b/imap-send.c index c0130d0e02..a5d1510180 100644 --- a/imap-send.c +++ b/imap-send.c @@ -70,16 +70,16 @@ static char *next_arg(char **); struct imap_server_conf { const char *name; - const char *tunnel; - const char *host; + char *tunnel; + char *host; int port; - const char *folder; - const char *user; - const char *pass; + char *folder; + char *user; + char *pass; int use_ssl; int ssl_verify; int use_html; - const char *auth_method; + char *auth_method; }; static struct imap_server_conf server = { diff --git a/mailmap.c b/mailmap.c index 044466b043..b2efe29b3d 100644 --- a/mailmap.c +++ b/mailmap.c @@ -7,7 +7,7 @@ #include "setup.h" char *git_mailmap_file; -const char *git_mailmap_blob; +char *git_mailmap_blob; struct mailmap_info { char *name; diff --git a/mailmap.h b/mailmap.h index 429a760945..cbda9bc5e0 100644 --- a/mailmap.h +++ b/mailmap.h @@ -4,7 +4,7 @@ struct string_list; extern char *git_mailmap_file; -extern const char *git_mailmap_blob; +extern char *git_mailmap_blob; int read_mailmap(struct string_list *map); void clear_mailmap(struct string_list *map); diff --git a/merge-ll.c b/merge-ll.c index bf1077ae09..e29b15fa4a 100644 --- a/merge-ll.c +++ b/merge-ll.c @@ -27,9 +27,9 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *, struct ll_merge_driver { const char *name; - const char *description; + char *description; ll_merge_fn fn; - const char *recursive; + char *recursive; struct ll_merge_driver *next; char *cmdline; }; @@ -268,7 +268,7 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, * merge.default and merge.driver configuration items */ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; +static char *default_ll_merge; static int read_merge_config(const char *var, const char *value, const struct config_context *ctx UNUSED, diff --git a/pager.c b/pager.c index b8822a9381..e9e121db69 100644 --- a/pager.c +++ b/pager.c @@ -13,7 +13,7 @@ int pager_use_color = 1; #endif static struct child_process pager_process; -static const char *pager_program; +static char *pager_program; /* Is the value coming back from term_columns() just a guess? */ static int term_columns_guessed; diff --git a/pretty.c b/pretty.c index 7ead078998..22a81506b7 100644 --- a/pretty.c +++ b/pretty.c @@ -62,7 +62,7 @@ static int git_pretty_formats_config(const char *var, const char *value, { struct cmt_fmt_map *commit_format = NULL; const char *name; - const char *fmt; + char *fmt; int i; if (!skip_prefix(var, "pretty.", &name)) @@ -93,13 +93,17 @@ static int git_pretty_formats_config(const char *var, const char *value, if (git_config_string(&fmt, var, value)) return -1; - if (skip_prefix(fmt, "format:", &fmt)) + if (skip_prefix(fmt, "format:", &commit_format->user_format)) { commit_format->is_tformat = 0; - else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%')) + } else if (skip_prefix(fmt, "tformat:", &commit_format->user_format)) { commit_format->is_tformat = 1; - else + } else if (strchr(fmt, '%')) { + commit_format->is_tformat = 1; + commit_format->user_format = fmt; + } else { commit_format->is_alias = 1; - commit_format->user_format = fmt; + commit_format->user_format = fmt; + } return 0; } diff --git a/promisor-remote.h b/promisor-remote.h index 2cb9eda9ea..88cb599c39 100644 --- a/promisor-remote.h +++ b/promisor-remote.h @@ -13,7 +13,7 @@ struct object_id; */ struct promisor_remote { struct promisor_remote *next; - const char *partial_clone_filter; + char *partial_clone_filter; const char name[FLEX_ARRAY]; }; diff --git a/remote.c b/remote.c index ec8c158e60..d319f28757 100644 --- a/remote.c +++ b/remote.c @@ -428,29 +428,29 @@ static int handle_config(const char *key, const char *value, else if (!strcmp(subkey, "prunetags")) remote->prune_tags = git_config_bool(key, value); else if (!strcmp(subkey, "url")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; add_url(remote, v); } else if (!strcmp(subkey, "pushurl")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; add_pushurl(remote, v); } else if (!strcmp(subkey, "push")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; refspec_append(&remote->push, v); - free((char *)v); + free(v); } else if (!strcmp(subkey, "fetch")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; refspec_append(&remote->fetch, v); - free((char *)v); + free(v); } else if (!strcmp(subkey, "receivepack")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; if (!remote->receivepack) @@ -458,7 +458,7 @@ static int handle_config(const char *key, const char *value, else error(_("more than one receivepack given, using the first")); } else if (!strcmp(subkey, "uploadpack")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; if (!remote->uploadpack) @@ -471,10 +471,10 @@ static int handle_config(const char *key, const char *value, else if (!strcmp(value, "--tags")) remote->fetch_tags = 2; } else if (!strcmp(subkey, "proxy")) { - return git_config_string((const char **)&remote->http_proxy, + return git_config_string(&remote->http_proxy, key, value); } else if (!strcmp(subkey, "proxyauthmethod")) { - return git_config_string((const char **)&remote->http_proxy_authmethod, + return git_config_string(&remote->http_proxy_authmethod, key, value); } else if (!strcmp(subkey, "vcs")) { return git_config_string(&remote->foreign_vcs, key, value); diff --git a/remote.h b/remote.h index dfd4837e60..e8c6655e42 100644 --- a/remote.h +++ b/remote.h @@ -46,7 +46,7 @@ struct remote_state { struct hashmap branches_hash; struct branch *current_branch; - const char *pushremote_name; + char *pushremote_name; struct rewrites rewrites; struct rewrites rewrites_push; @@ -65,7 +65,7 @@ struct remote { int origin, configured_in_repo; - const char *foreign_vcs; + char *foreign_vcs; /* An array of all of the url_nr URLs configured for the remote */ const char **url; @@ -309,9 +309,9 @@ struct branch { const char *refname; /* The name of the remote listed in the configuration. */ - const char *remote_name; + char *remote_name; - const char *pushremote_name; + char *pushremote_name; /* An array of the "merge" lines in the configuration. */ const char **merge_name; diff --git a/sequencer.c b/sequencer.c index dbd60d79b9..3c81ef9ca5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -306,7 +306,7 @@ static int git_sequencer_config(const char *k, const char *v, } if (!opts->default_strategy && !strcmp(k, "pull.twohead")) { - int ret = git_config_string((const char**)&opts->default_strategy, k, v); + int ret = git_config_string(&opts->default_strategy, k, v); if (ret == 0) { /* * pull.twohead is allowed to be multi-valued; we only diff --git a/upload-pack.c b/upload-pack.c index 8fbd138515..5801eb2639 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -94,7 +94,7 @@ struct upload_pack_data { struct packet_writer writer; - const char *pack_objects_hook; + char *pack_objects_hook; unsigned stateless_rpc : 1; /* v0 only */ unsigned no_done : 1; /* v0 only */ diff --git a/userdiff.h b/userdiff.h index d726804c3e..cc8e5abfef 100644 --- a/userdiff.h +++ b/userdiff.h @@ -7,19 +7,19 @@ struct index_state; struct repository; struct userdiff_funcname { - const char *pattern; + char *pattern; int cflags; }; struct userdiff_driver { const char *name; - const char *external; - const char *algorithm; + char *external; + char *algorithm; int binary; struct userdiff_funcname funcname; - const char *word_regex; - const char *word_regex_multi_byte; - const char *textconv; + char *word_regex; + char *word_regex_multi_byte; + char *textconv; struct notes_cache *textconv_cache; int textconv_want_cache; }; From 49eb597ce08de7fc4837155fa7910dace92b9ae6 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:44 +0200 Subject: [PATCH 11/46] config: plug various memory leaks Now that memory ownership rules around `git_config_string()` and `git_config_pathname()` are clearer, it also got easier to spot that the returned memory needs to be free'd. Plug a subset of those cases and mark now-passing tests as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- alias.c | 4 ++- config.c | 36 +++++++++++++++----- t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t3415-rebase-autosquash.sh | 1 + t/t4041-diff-submodule-option.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4210-log-i18n.sh | 2 ++ t/t6006-rev-list-format.sh | 1 + t/t7005-editor.sh | 1 + t/t7102-reset.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - 13 files changed, 40 insertions(+), 12 deletions(-) diff --git a/alias.c b/alias.c index 269892c356..4daafd9bda 100644 --- a/alias.c +++ b/alias.c @@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value, return 0; if (data->alias) { - if (!strcasecmp(p, data->alias)) + if (!strcasecmp(p, data->alias)) { + FREE_AND_NULL(data->v); return git_config_string(&data->v, key, value); + } } else if (data->list) { string_list_append(data->list, p); } diff --git a/config.c b/config.c index da52a7f8a1..496cd1a61e 100644 --- a/config.c +++ b/config.c @@ -1414,8 +1414,10 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.attributesfile")) + if (!strcmp(var, "core.attributesfile")) { + FREE_AND_NULL(git_attributes_file); return git_config_pathname(&git_attributes_file, var, value); + } if (!strcmp(var, "core.hookspath")) { if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL && @@ -1428,6 +1430,7 @@ static int git_default_core_config(const char *var, const char *value, "again with " "`GIT_CLONE_PROTECTION_ACTIVE=false`"), value); + FREE_AND_NULL(git_hooks_path); return git_config_pathname(&git_hooks_path, var, value); } @@ -1576,8 +1579,10 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.editor")) + if (!strcmp(var, "core.editor")) { + FREE_AND_NULL(editor_program); return git_config_string(&editor_program, var, value); + } if (!strcmp(var, "core.commentchar") || !strcmp(var, "core.commentstring")) { @@ -1595,11 +1600,13 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.askpass")) + if (!strcmp(var, "core.askpass")) { + FREE_AND_NULL(askpass_program); return git_config_string(&askpass_program, var, value); + } if (!strcmp(var, "core.excludesfile")) { - free(excludes_file); + FREE_AND_NULL(excludes_file); return git_config_pathname(&excludes_file, var, value); } @@ -1702,11 +1709,15 @@ static int git_default_sparse_config(const char *var, const char *value) static int git_default_i18n_config(const char *var, const char *value) { - if (!strcmp(var, "i18n.commitencoding")) + if (!strcmp(var, "i18n.commitencoding")) { + FREE_AND_NULL(git_commit_encoding); return git_config_string(&git_commit_encoding, var, value); + } - if (!strcmp(var, "i18n.logoutputencoding")) + if (!strcmp(var, "i18n.logoutputencoding")) { + FREE_AND_NULL(git_log_output_encoding); return git_config_string(&git_log_output_encoding, var, value); + } /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -1779,10 +1790,15 @@ static int git_default_push_config(const char *var, const char *value) static int git_default_mailmap_config(const char *var, const char *value) { - if (!strcmp(var, "mailmap.file")) + if (!strcmp(var, "mailmap.file")) { + FREE_AND_NULL(git_mailmap_file); return git_config_pathname(&git_mailmap_file, var, value); - if (!strcmp(var, "mailmap.blob")) + } + + if (!strcmp(var, "mailmap.blob")) { + FREE_AND_NULL(git_mailmap_blob); return git_config_string(&git_mailmap_blob, var, value); + } /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -1790,8 +1806,10 @@ static int git_default_mailmap_config(const char *var, const char *value) static int git_default_attr_config(const char *var, const char *value) { - if (!strcmp(var, "attr.tree")) + if (!strcmp(var, "attr.tree")) { + FREE_AND_NULL(git_attr_tree); return git_config_string(&git_attr_tree, var, value); + } /* * Add other attribute related config variables here and to diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 40d3c42618..53e5b290b9 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -7,6 +7,7 @@ test_description='Compatibility with $XDG_CONFIG_HOME/git/ files' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' ' diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh index f6dc83e2aa..5c47f9ecc3 100755 --- a/t/t1350-config-hooks-path.sh +++ b/t/t1350-config-hooks-path.sh @@ -2,6 +2,7 @@ test_description='Test the core.hooksPath configuration variable' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up a pre-commit hook in core.hooksPath' ' diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index fcc40d6fe1..22452ff84c 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -5,6 +5,7 @@ test_description='auto squash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 0c1502d4b0..8fc40e75eb 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -12,6 +12,7 @@ This test tries to verify the sanity of the --submodule option of git diff. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index 97c6424cd5..8ce67442d9 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -10,6 +10,7 @@ test_description='Support for diff format verbose submodule difference in git di This test tries to verify the sanity of --submodule=diff option of git diff. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 75216f19ce..7120030b5c 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of é diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 573eb97a0f..f1623b1c06 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -8,6 +8,7 @@ test_description='git rev-list --pretty=format test' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 5fcf281dfb..b9822294fe 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -2,6 +2,7 @@ test_description='GIT_EDITOR, core.editor, and stuff' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh unset EDITOR VISUAL GIT_EDITOR diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 62d9f846ce..2add26d768 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -10,6 +10,7 @@ Documented tests for git reset' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh commit_msg () { diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 185248a4cd..01e1e8a8f7 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -4,7 +4,6 @@ test_description='git svn honors i18n.commitEncoding in config' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh compare_git_head_with () { diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh index b7f756b2b7..22d80b0be2 100755 --- a/t/t9139-git-svn-non-utf8-commitencoding.sh +++ b/t/t9139-git-svn-non-utf8-commitencoding.sh @@ -4,7 +4,6 @@ test_description='git svn refuses to dcommit non-UTF8 messages' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh # ISO-2022-JP can pass for valid UTF-8, so skipping that in this test From 96c1655095ae21040afe9d9c05cf42bb0fc03581 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:49 +0200 Subject: [PATCH 12/46] builtin/credential: clear credential before exit We never release memory associated with `struct credential`. Fix this and mark the corresponding test as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/credential.c | 2 ++ t/t0300-credentials.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/builtin/credential.c b/builtin/credential.c index 5100d441f2..b72e76dd9a 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -39,5 +39,7 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) } else { usage(usage_msg); } + + credential_clear(&c); return 0; } diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 432f029d48..6a76b7fdbd 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='basic credential helper tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-credential.sh From ba9d029445e183d7c7dda75887cee1b5d6fee1d7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:54 +0200 Subject: [PATCH 13/46] commit-reach: fix memory leak in `ahead_behind()` We use a priority queue in `ahead_behind()` to compute the ahead/behind count for commits. We may not iterate through all commits part of that queue though in case all of its entries are stale. Consequently, as we never make the effort to release the remaining commits, we end up leaking bit arrays that we have allocated for each of the contained commits. Plug this leak and mark the corresponding test as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- commit-reach.c | 4 ++++ t/t3203-branch-output.sh | 2 ++ 2 files changed, 6 insertions(+) diff --git a/commit-reach.c b/commit-reach.c index 8f9b008f87..384aee1ab3 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -1106,6 +1106,10 @@ void ahead_behind(struct repository *r, /* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks(r, PARENT2 | STALE); + while (prio_queue_peek(&queue)) { + struct commit *c = prio_queue_get(&queue); + free_bit_array(c); + } clear_bit_arrays(&bit_arrays); clear_prio_queue(&queue); } diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 758963b189..e627f08a17 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git branch display tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh From 3ef52dd1125b6c9223fd03aca146f6b799e014f9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:46:59 +0200 Subject: [PATCH 14/46] submodule: fix leaking memory for submodule entries In `free_one_config()` we never end up freeing the `url` and `ignore` fields and thus leak memory. Fix those leaks and mark now-passing tests as leak free. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- submodule-config.c | 2 ++ t/t1013-read-tree-submodule.sh | 1 + t/t2013-checkout-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t7112-reset-submodule.sh | 1 + 5 files changed, 6 insertions(+) diff --git a/submodule-config.c b/submodule-config.c index 11428b4ada..ec45ea67b9 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -91,6 +91,8 @@ static void free_one_config(struct submodule_entry *entry) free((void *) entry->config->path); free((void *) entry->config->name); free((void *) entry->config->branch); + free((void *) entry->config->url); + free((void *) entry->config->ignore); free((void *) entry->config->update_strategy.command); free(entry->config); } diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index bfc90d4cf2..cf8b94ebed 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -2,6 +2,7 @@ test_description='read-tree can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index b2bdd1fcb4..3c1d663d94 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -2,6 +2,7 @@ test_description='checkout can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh index 61771eec83..f04bdc8c78 100755 --- a/t/t3007-ls-files-recurse-submodules.sh +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -6,6 +6,7 @@ This test verifies the recurse-submodules feature correctly lists files from submodules. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup directory structure and submodules' ' diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh index a3e2413bc3..b0d3d93b0b 100755 --- a/t/t7112-reset-submodule.sh +++ b/t/t7112-reset-submodule.sh @@ -2,6 +2,7 @@ test_description='reset can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh From 11ce77b5cc04e2a42b98f0f9f42d367f50f3b1fc Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:47:04 +0200 Subject: [PATCH 15/46] strvec: add functions to replace and remove strings Add two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1). While at it, add a bunch of unit tests that cover both old and new functionality. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Makefile | 1 + strvec.c | 20 +++ strvec.h | 13 ++ t/unit-tests/t-strvec.c | 269 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/test-lib.c | 13 ++ t/unit-tests/test-lib.h | 13 ++ 6 files changed, 329 insertions(+) create mode 100644 t/unit-tests/t-strvec.c diff --git a/Makefile b/Makefile index cf504963c2..d4000fb1d6 100644 --- a/Makefile +++ b/Makefile @@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-strbuf +UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-prio-queue UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) diff --git a/strvec.c b/strvec.c index 178f4f3748..d4073ec9fa 100644 --- a/strvec.c +++ b/strvec.c @@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items) strvec_push(array, *items); } +const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement) +{ + char *to_free; + if (idx >= array->nr) + BUG("index outside of array boundary"); + to_free = (char *) array->v[idx]; + array->v[idx] = xstrdup(replacement); + free(to_free); + return array->v[idx]; +} + +void strvec_remove(struct strvec *array, size_t idx) +{ + if (idx >= array->nr) + BUG("index outside of array boundary"); + free((char *)array->v[idx]); + memmove(array->v + idx, array->v + idx + 1, (array->nr - idx) * sizeof(char *)); + array->nr--; +} + void strvec_pop(struct strvec *array) { if (!array->nr) diff --git a/strvec.h b/strvec.h index 4715d3e51f..6c7e8b7d50 100644 --- a/strvec.h +++ b/strvec.h @@ -64,6 +64,19 @@ void strvec_pushl(struct strvec *, ...); /* Push a null-terminated array of strings onto the end of the array. */ void strvec_pushv(struct strvec *, const char **); +/** + * Replace the value at the given index with a new value. The index must be + * valid. Returns a pointer to the inserted value. + */ +const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement); + +/* + * Remove the value at the given index. The remainder of the array will be + * moved to fill the resulting gap. The provided index must point into the + * array. + */ +void strvec_remove(struct strvec *array, size_t idx); + /** * Remove the final element from the array. If there are no * elements in the array, do nothing. diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c new file mode 100644 index 0000000000..f17fb10d9e --- /dev/null +++ b/t/unit-tests/t-strvec.c @@ -0,0 +1,269 @@ +#include "test-lib.h" +#include "strbuf.h" +#include "strvec.h" + +#define check_strvec(vec, ...) \ + check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__) +static void check_strvec_loc(const char *loc, struct strvec *vec, ...) +{ + va_list ap; + size_t nr = 0; + + va_start(ap, vec); + while (1) { + const char *str = va_arg(ap, const char *); + if (!str) + break; + + if (!check_uint(vec->nr, >, nr) || + !check_uint(vec->alloc, >, nr) || + !check_str(vec->v[nr], str)) { + struct strbuf msg = STRBUF_INIT; + strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr); + test_assert(loc, msg.buf, 0); + strbuf_release(&msg); + return; + } + + nr++; + } + + check_uint(vec->nr, ==, nr); + check_uint(vec->alloc, >=, nr); + check_pointer_eq(vec->v[nr], NULL); +} + +static void t_static_init(void) +{ + struct strvec vec = STRVEC_INIT; + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_dynamic_init(void) +{ + struct strvec vec; + strvec_init(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_clear(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_push(&vec, "foo"); + strvec_clear(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_push(void) +{ + struct strvec vec = STRVEC_INIT; + + strvec_push(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + + strvec_push(&vec, "bar"); + check_strvec(&vec, "foo", "bar", NULL); + + strvec_clear(&vec); +} + +static void t_pushf(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushf(&vec, "foo: %d", 1); + check_strvec(&vec, "foo: 1", NULL); + strvec_clear(&vec); +} + +static void t_pushl(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_pushv(void) +{ + const char *strings[] = { + "foo", "bar", "baz", NULL, + }; + struct strvec vec = STRVEC_INIT; + + strvec_pushv(&vec, strings); + check_strvec(&vec, "foo", "bar", "baz", NULL); + + strvec_clear(&vec); +} + +static void t_replace_at_head(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 0, "replaced"); + check_strvec(&vec, "replaced", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_replace_at_tail(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 2, "replaced"); + check_strvec(&vec, "foo", "bar", "replaced", NULL); + strvec_clear(&vec); +} + +static void t_replace_in_between(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 1, "replaced"); + check_strvec(&vec, "foo", "replaced", "baz", NULL); + strvec_clear(&vec); +} + +static void t_replace_with_substring(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", NULL); + strvec_replace(&vec, 0, vec.v[0] + 1); + check_strvec(&vec, "oo", NULL); + strvec_clear(&vec); +} + +static void t_remove_at_head(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 0); + check_strvec(&vec, "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_remove_at_tail(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 2); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_remove_in_between(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 1); + check_strvec(&vec, "foo", "baz", NULL); + strvec_clear(&vec); +} + +static void t_pop_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pop(&vec); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_pop_non_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_pop(&vec); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_split_empty_string(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, ""); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_split_single_item(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + strvec_clear(&vec); +} + +static void t_split_multiple_items(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo bar baz"); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_split_whitespace_only(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, " \t\n"); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_split_multiple_consecutive_whitespaces(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo\n\t bar"); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_detach(void) +{ + struct strvec vec = STRVEC_INIT; + const char **detached; + + strvec_push(&vec, "foo"); + + detached = strvec_detach(&vec); + check_str(detached[0], "foo"); + check_pointer_eq(detached[1], NULL); + + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + + free((char *) detached[0]); + free(detached); +} + +int cmd_main(int argc, const char **argv) +{ + TEST(t_static_init(), "static initialization"); + TEST(t_dynamic_init(), "dynamic initialization"); + TEST(t_clear(), "clear"); + TEST(t_push(), "push"); + TEST(t_pushf(), "pushf"); + TEST(t_pushl(), "pushl"); + TEST(t_pushv(), "pushv"); + TEST(t_replace_at_head(), "replace at head"); + TEST(t_replace_in_between(), "replace in between"); + TEST(t_replace_at_tail(), "replace at tail"); + TEST(t_replace_with_substring(), "replace with substring"); + TEST(t_remove_at_head(), "remove at head"); + TEST(t_remove_in_between(), "remove in between"); + TEST(t_remove_at_tail(), "remove at tail"); + TEST(t_pop_empty_array(), "pop with empty array"); + TEST(t_pop_non_empty_array(), "pop with non-empty array"); + TEST(t_split_empty_string(), "split empty string"); + TEST(t_split_single_item(), "split single item"); + TEST(t_split_multiple_items(), "split multiple items"); + TEST(t_split_whitespace_only(), "split whitespace only"); + TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces"); + TEST(t_detach(), "detach"); + return test_done(); +} diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c index 66d6980ffb..3c513ce59a 100644 --- a/t/unit-tests/test-lib.c +++ b/t/unit-tests/test-lib.c @@ -318,6 +318,19 @@ int check_bool_loc(const char *loc, const char *check, int ok) union test__tmp test__tmp[2]; +int check_pointer_eq_loc(const char *loc, const char *check, int ok, + const void *a, const void *b) +{ + int ret = test_assert(loc, check, ok); + + if (!ret) { + test_msg(" left: %p", a); + test_msg(" right: %p", b); + } + + return ret; +} + int check_int_loc(const char *loc, const char *check, int ok, intmax_t a, intmax_t b) { diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h index a8f07ae0b7..2de6d715d5 100644 --- a/t/unit-tests/test-lib.h +++ b/t/unit-tests/test-lib.h @@ -75,6 +75,18 @@ int test_assert(const char *location, const char *check, int ok); check_bool_loc(TEST_LOCATION(), #x, x) int check_bool_loc(const char *loc, const char *check, int ok); +/* + * Compare two integers. Prints a message with the two values if the + * comparison fails. NB this is not thread safe. + */ +#define check_pointer_eq(a, b) \ + (test__tmp[0].p = (a), test__tmp[1].p = (b), \ + check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \ + test__tmp[0].p == test__tmp[1].p, \ + test__tmp[0].p, test__tmp[1].p)) +int check_pointer_eq_loc(const char *loc, const char *check, int ok, + const void *a, const void *b); + /* * Compare two integers. Prints a message with the two values if the * comparison fails. NB this is not thread safe. @@ -136,6 +148,7 @@ union test__tmp { intmax_t i; uintmax_t u; char c; + const void *p; }; extern union test__tmp test__tmp[2]; From 3d231f7b8236787252cb336878e3ace75c1df545 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:47:09 +0200 Subject: [PATCH 16/46] builtin/mv: refactor `add_slash()` to always return allocated strings The `add_slash()` function will only conditionally return an allocated string when the passed-in string did not yet have a trailing slash. This makes the memory ownership harder to track than really necessary. It's dubious whether this optimization really buys us all that much. The number of times we execute this function is bounded by the number of arguments to git-mv(1), so in the typical case we may end up saving an allocation or two. Simplify the code to unconditionally return allocated strings. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/mv.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index 74aa9746aa..9f4c75df04 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -76,7 +76,7 @@ static const char **internal_prefix_pathspec(const char *prefix, return result; } -static const char *add_slash(const char *path) +static char *add_slash(const char *path) { size_t len = strlen(path); if (len && path[len - 1] != '/') { @@ -86,7 +86,7 @@ static const char *add_slash(const char *path) with_slash[len] = 0; return with_slash; } - return path; + return xstrdup(path); } #define SUBMODULE_WITH_GITDIR ((const char *)1) @@ -111,7 +111,7 @@ static void prepare_move_submodule(const char *src, int first, static int index_range_of_same_dir(const char *src, int length, int *first_p, int *last_p) { - const char *src_w_slash = add_slash(src); + char *src_w_slash = add_slash(src); int first, last, len_w_slash = length + 1; first = index_name_pos(the_repository->index, src_w_slash, len_w_slash); @@ -124,8 +124,8 @@ static int index_range_of_same_dir(const char *src, int length, if (strncmp(path, src_w_slash, len_w_slash)) break; } - if (src_w_slash != src) - free((char *)src_w_slash); + + free(src_w_slash); *first_p = first; *last_p = last; return last - first; @@ -141,7 +141,7 @@ static int index_range_of_same_dir(const char *src, int length, static int empty_dir_has_sparse_contents(const char *name) { int ret = 0; - const char *with_slash = add_slash(name); + char *with_slash = add_slash(name); int length = strlen(with_slash); int pos = index_name_pos(the_repository->index, with_slash, length); @@ -159,8 +159,7 @@ static int empty_dir_has_sparse_contents(const char *name) } free_return: - if (with_slash != name) - free((char *)with_slash); + free(with_slash); return ret; } @@ -178,7 +177,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - const char *dst_w_slash; + char *dst_w_slash = NULL; const char **src_dir = NULL; int src_dir_nr = 0, src_dir_alloc = 0; struct strbuf a_src_dir = STRBUF_INIT; @@ -243,10 +242,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_mode = SPARSE; } } - if (dst_w_slash != dest_path[0]) { - free((char *)dst_w_slash); - dst_w_slash = NULL; - } /* Checking */ for (i = 0; i < argc; i++) { @@ -265,12 +260,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix) pos = index_name_pos(the_repository->index, src, length); if (pos < 0) { - const char *src_w_slash = add_slash(src); + char *src_w_slash = add_slash(src); if (!path_in_sparse_checkout(src_w_slash, the_repository->index) && empty_dir_has_sparse_contents(src)) { + free(src_w_slash); modes[i] |= SKIP_WORKTREE_DIR; goto dir_check; } + free(src_w_slash); /* only error if existence is expected. */ if (!(modes[i] & SPARSE)) bad = _("bad source"); @@ -310,7 +307,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check: if (S_ISDIR(st.st_mode)) { - int j, dst_len, n; + char *dst_with_slash; + size_t dst_with_slash_len; + int j, n; int first = index_name_pos(the_repository->index, src, length), last; if (first >= 0) { @@ -335,19 +334,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix) REALLOC_ARRAY(modes, n); REALLOC_ARRAY(submodule_gitfile, n); - dst = add_slash(dst); - dst_len = strlen(dst); + dst_with_slash = add_slash(dst); + dst_with_slash_len = strlen(dst_with_slash); for (j = 0; j < last - first; j++) { const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; source[argc + j] = path; destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); + prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); memset(modes + argc + j, 0, sizeof(enum update_mode)); modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; } + + free(dst_with_slash); argc += last - first; goto act_on_entry; } @@ -565,6 +566,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + free(dst_w_slash); string_list_clear(&src_for_dst, 0); string_list_clear(&dirty_paths, 0); UNLEAK(source); From 9fcd9e4e72ff2ef2b96379d8caf03429fea279eb Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:47:13 +0200 Subject: [PATCH 17/46] builtin/mv duplicate string list memory makes the next patch easier, where we will migrate to the paths being owned by a strvec. given that we are talking about command line parameters here it's also not like we have tons of allocations that this would save while at it, fix a memory leak Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/mv.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index 9f4c75df04..12dcc0b13c 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -183,11 +183,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct strbuf a_src_dir = STRBUF_INIT; enum update_mode *modes, dst_mode = 0; struct stat st, dest_st; - struct string_list src_for_dst = STRING_LIST_INIT_NODUP; + struct string_list src_for_dst = STRING_LIST_INIT_DUP; struct lock_file lock_file = LOCK_INIT; struct cache_entry *ce; - struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; - struct string_list dirty_paths = STRING_LIST_INIT_NODUP; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_DUP; + struct string_list dirty_paths = STRING_LIST_INIT_DUP; + int ret; git_config(git_default_config, NULL); @@ -440,8 +441,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (only_match_skip_worktree.nr) { advise_on_updating_sparse_paths(&only_match_skip_worktree); - if (!ignore_errors) - return 1; + if (!ignore_errors) { + ret = 1; + goto out; + } } for (i = 0; i < argc; i++) { @@ -566,12 +569,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + ret = 0; + +out: free(dst_w_slash); string_list_clear(&src_for_dst, 0); string_list_clear(&dirty_paths, 0); + string_list_clear(&only_match_skip_worktree, 0); UNLEAK(source); UNLEAK(dest_path); free(submodule_gitfile); free(modes); - return 0; + return ret; } From 52a7dab439e53d05adb9edc870c92a446e5a9143 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:47:18 +0200 Subject: [PATCH 18/46] builtin/mv: refactor to use `struct strvec` Memory allocation patterns in git-mv(1) are extremely hard to follow: We copy around string pointers into manually-managed arrays, some of which alias each other, but only sometimes, while we also drop some of those strings at other times without ever daring to free them. While this may be my own subjective feeling, it seems like others have given up as the code has multiple calls to `UNLEAK()`. These are not sufficient though, and git-mv(1) is still leaking all over the place even with them. Refactor the code to instead track strings in `struct strvec`. While this has the effect of effectively duplicating some of the strings without an actual need, it is way easier to reason about and fixes all of the aliasing of memory that has been going on. It allows us to get rid of the `UNLEAK()` calls and also fixes leaks that those calls did not paper over. Mark tests which are now leak-free accordingly. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/mv.c | 125 +++++++++++------------ t/t4001-diff-rename.sh | 4 +- t/t4043-diff-rename-binary.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + 8 files changed, 68 insertions(+), 67 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index 12dcc0b13c..e461d29ca1 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -20,6 +20,7 @@ #include "read-cache-ll.h" #include "repository.h" #include "setup.h" +#include "strvec.h" #include "submodule.h" #include "entry.h" @@ -38,42 +39,32 @@ enum update_mode { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_prefix_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static void internal_prefix_pathspec(struct strvec *out, + const char *prefix, + const char **pathspec, + int count, unsigned flags) { - int i; - const char **result; int prefixlen = prefix ? strlen(prefix) : 0; - ALLOC_ARRAY(result, count + 1); /* Create an intermediate copy of the pathspec based on the flags */ - for (i = 0; i < count; i++) { - int length = strlen(pathspec[i]); - int to_copy = length; - char *it; + for (int i = 0; i < count; i++) { + size_t length = strlen(pathspec[i]); + size_t to_copy = length; + const char *maybe_basename; + char *trimmed, *prefixed_path; + while (!(flags & KEEP_TRAILING_SLASH) && to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - it = xmemdupz(pathspec[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else { - result[i] = it; - } - } - result[count] = NULL; + trimmed = xmemdupz(pathspec[i], to_copy); + maybe_basename = (flags & DUP_BASENAME) ? basename(trimmed) : trimmed; + prefixed_path = prefix_path(prefix, prefixlen, maybe_basename); + strvec_push(out, prefixed_path); - /* Prefix the pathspec and free the old intermediate strings */ - for (i = 0; i < count; i++) { - const char *match = prefix_path(prefix, prefixlen, result[i]); - free((char *) result[i]); - result[i] = match; + free(prefixed_path); + free(trimmed); } - - return result; } static char *add_slash(const char *path) @@ -176,7 +167,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; - const char **source, **destination, **dest_path, **submodule_gitfile; + struct strvec sources = STRVEC_INIT; + struct strvec dest_paths = STRVEC_INIT; + struct strvec destinations = STRVEC_INIT; + const char **submodule_gitfile; char *dst_w_slash = NULL; const char **src_dir = NULL; int src_dir_nr = 0, src_dir_alloc = 0; @@ -201,7 +195,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("index file corrupt")); - source = internal_prefix_pathspec(prefix, argv, argc, 0); + internal_prefix_pathspec(&sources, prefix, argv, argc, 0); CALLOC_ARRAY(modes, argc); /* @@ -212,41 +206,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); - dst_w_slash = add_slash(dest_path[0]); + internal_prefix_pathspec(&dest_paths, prefix, argv + argc, 1, flags); + dst_w_slash = add_slash(dest_paths.v[0]); submodule_gitfile = xcalloc(argc, sizeof(char *)); - if (dest_path[0][0] == '\0') + if (dest_paths.v[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); - else if (!lstat(dest_path[0], &st) && - S_ISDIR(st.st_mode)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); + internal_prefix_pathspec(&destinations, dest_paths.v[0], argv, argc, DUP_BASENAME); + else if (!lstat(dest_paths.v[0], &st) && S_ISDIR(st.st_mode)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + } else if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && + empty_dir_has_sparse_contents(dst_w_slash)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + dst_mode = SKIP_WORKTREE_DIR; + } else if (argc != 1) { + die(_("destination '%s' is not a directory"), dest_paths.v[0]); } else { - if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && - empty_dir_has_sparse_contents(dst_w_slash)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); - dst_mode = SKIP_WORKTREE_DIR; - } else if (argc != 1) { - die(_("destination '%s' is not a directory"), dest_path[0]); - } else { - destination = dest_path; - /* - * is a file outside of sparse-checkout - * cone. Insist on cone mode here for backward - * compatibility. We don't want dst_mode to be assigned - * for a file when the repo is using no-cone mode (which - * is deprecated at this point) sparse-checkout. As - * SPARSE here is only considering cone-mode situation. - */ - if (!path_in_cone_mode_sparse_checkout(destination[0], the_repository->index)) - dst_mode = SPARSE; - } + strvec_pushv(&destinations, dest_paths.v); + + /* + * is a file outside of sparse-checkout + * cone. Insist on cone mode here for backward + * compatibility. We don't want dst_mode to be assigned + * for a file when the repo is using no-cone mode (which + * is deprecated at this point) sparse-checkout. As + * SPARSE here is only considering cone-mode situation. + */ + if (!path_in_cone_mode_sparse_checkout(destinations.v[0], the_repository->index)) + dst_mode = SPARSE; } /* Checking */ for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; int length; const char *bad = NULL; int skip_sparse = 0; @@ -330,8 +322,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) src_dir[src_dir_nr++] = src; n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); REALLOC_ARRAY(modes, n); REALLOC_ARRAY(submodule_gitfile, n); @@ -341,12 +331,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) for (j = 0; j < last - first; j++) { const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); + char *prefixed_path = prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); + + strvec_push(&sources, path); + strvec_push(&destinations, prefixed_path); + memset(modes + argc + j, 0, sizeof(enum update_mode)); modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; + + free(prefixed_path); } free(dst_with_slash); @@ -430,8 +424,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry: if (--argc > 0) { int n = argc - i; - MOVE_ARRAY(source + i, source + i + 1, n); - MOVE_ARRAY(destination + i, destination + i + 1, n); + strvec_remove(&sources, i); + strvec_remove(&destinations, i); MOVE_ARRAY(modes + i, modes + i + 1, n); MOVE_ARRAY(submodule_gitfile + i, submodule_gitfile + i + 1, n); @@ -448,7 +442,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; enum update_mode mode = modes[i]; int pos; int sparse_and_dirty = 0; @@ -576,8 +570,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) string_list_clear(&src_for_dst, 0); string_list_clear(&dirty_paths, 0); string_list_clear(&only_match_skip_worktree, 0); - UNLEAK(source); - UNLEAK(dest_path); + strvec_clear(&sources); + strvec_clear(&dest_paths); + strvec_clear(&destinations); free(submodule_gitfile); free(modes); return ret; diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 49c042a38a..cd1931dd55 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -3,9 +3,9 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Test rename detection in diff engine. +test_description='Test rename detection in diff engine.' -' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh index 2a2cf91352..e486493908 100755 --- a/t/t4043-diff-rename-binary.sh +++ b/t/t4043-diff-rename-binary.sh @@ -5,6 +5,7 @@ test_description='Move a binary file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh index 697e86c0ff..f788428540 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -5,6 +5,7 @@ test_description='git apply -p handling.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh index 3de4ef6bd9..27d6efdc9a 100755 --- a/t/t6400-merge-df.sh +++ b/t/t6400-merge-df.sh @@ -7,6 +7,7 @@ test_description='Test merge with directory/file conflicts' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare repository' ' diff --git a/t/t6412-merge-large-rename.sh b/t/t6412-merge-large-rename.sh index ca018d11f5..d0863a8fb5 100755 --- a/t/t6412-merge-large-rename.sh +++ b/t/t6412-merge-large-rename.sh @@ -4,6 +4,7 @@ test_description='merging with large rename matrix' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh count() { diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh index b059475ed0..62f0180325 100755 --- a/t/t6426-merge-skip-unneeded-updates.sh +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -22,6 +22,7 @@ test_description="merge cases" # underscore notation is to differentiate different # files that might be renamed into each other's paths.) +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 0f39ed0d08..cb1c4ceef7 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -2,6 +2,7 @@ test_description="remember regular & dir renames in sequence of merges" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # From ebdbefa4fe9f618347124b37d44e517e0c6a3e4c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 27 May 2024 13:47:23 +0200 Subject: [PATCH 19/46] builtin/mv: fix leaks for submodule gitfile paths Similar to the preceding commit, we have effectively given tracking memory ownership of submodule gitfile paths. Refactor the code to start tracking allocated strings in a separate `struct strvec` such that we can easily plug those leaks. Mark now-passing tests as leak free. Note that ideally, we wouldn't require two separate data structures to track those paths. But we do need to store `NULL` pointers for the gitfile paths such that we can indicate that its corresponding entries in the other arrays do not have such a path at all. And given that `struct strvec`s cannot store `NULL` pointers we cannot use them to store this information. There is another small gotcha that is easy to miss: you may be wondering why we don't want to store `SUBMODULE_WITH_GITDIR` in the strvec. This is because this is a mere sentinel value and not actually a string at all. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/mv.c | 44 +++++++++++++---------- t/t4059-diff-submodule-not-initialized.sh | 1 + t/t7001-mv.sh | 2 ++ t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/builtin/mv.c b/builtin/mv.c index e461d29ca1..81ca910de6 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -82,21 +82,23 @@ static char *add_slash(const char *path) #define SUBMODULE_WITH_GITDIR ((const char *)1) -static void prepare_move_submodule(const char *src, int first, - const char **submodule_gitfile) +static const char *submodule_gitfile_path(const char *src, int first) { struct strbuf submodule_dotgit = STRBUF_INIT; + const char *path; + if (!S_ISGITLINK(the_repository->index->cache[first]->ce_mode)) die(_("Directory %s is in index and no submodule?"), src); if (!is_staging_gitmodules_ok(the_repository->index)) die(_("Please stage your changes to .gitmodules or stash them to proceed")); + strbuf_addf(&submodule_dotgit, "%s/.git", src); - *submodule_gitfile = read_gitfile(submodule_dotgit.buf); - if (*submodule_gitfile) - *submodule_gitfile = xstrdup(*submodule_gitfile); - else - *submodule_gitfile = SUBMODULE_WITH_GITDIR; + + path = read_gitfile(submodule_dotgit.buf); strbuf_release(&submodule_dotgit); + if (path) + return path; + return SUBMODULE_WITH_GITDIR; } static int index_range_of_same_dir(const char *src, int length, @@ -170,7 +172,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct strvec sources = STRVEC_INIT; struct strvec dest_paths = STRVEC_INIT; struct strvec destinations = STRVEC_INIT; - const char **submodule_gitfile; + struct strvec submodule_gitfiles_to_free = STRVEC_INIT; + const char **submodule_gitfiles; char *dst_w_slash = NULL; const char **src_dir = NULL; int src_dir_nr = 0, src_dir_alloc = 0; @@ -208,7 +211,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = 0; internal_prefix_pathspec(&dest_paths, prefix, argv + argc, 1, flags); dst_w_slash = add_slash(dest_paths.v[0]); - submodule_gitfile = xcalloc(argc, sizeof(char *)); + submodule_gitfiles = xcalloc(argc, sizeof(char *)); if (dest_paths.v[0][0] == '\0') /* special case: "." was normalized to "" */ @@ -306,8 +309,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int first = index_name_pos(the_repository->index, src, length), last; if (first >= 0) { - prepare_move_submodule(src, first, - submodule_gitfile + i); + const char *path = submodule_gitfile_path(src, first); + if (path != SUBMODULE_WITH_GITDIR) + path = strvec_push(&submodule_gitfiles_to_free, path); + submodule_gitfiles[i] = path; goto act_on_entry; } else if (index_range_of_same_dir(src, length, &first, &last) < 1) { @@ -323,7 +328,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) n = argc + last - first; REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); + REALLOC_ARRAY(submodule_gitfiles, n); dst_with_slash = add_slash(dst); dst_with_slash_len = strlen(dst_with_slash); @@ -338,7 +343,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) memset(modes + argc + j, 0, sizeof(enum update_mode)); modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; + submodule_gitfiles[argc + j] = NULL; free(prefixed_path); } @@ -427,8 +432,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) strvec_remove(&sources, i); strvec_remove(&destinations, i); MOVE_ARRAY(modes + i, modes + i + 1, n); - MOVE_ARRAY(submodule_gitfile + i, - submodule_gitfile + i + 1, n); + MOVE_ARRAY(submodule_gitfiles + i, + submodule_gitfiles + i + 1, n); i--; } } @@ -462,12 +467,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) continue; die_errno(_("renaming '%s' failed"), src); } - if (submodule_gitfile[i]) { + if (submodule_gitfiles[i]) { if (!update_path_in_gitmodules(src, dst)) gitmodules_modified = 1; - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + if (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR) connect_work_tree_and_git_dir(dst, - submodule_gitfile[i], + submodule_gitfiles[i], 1); } @@ -573,7 +578,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) strvec_clear(&sources); strvec_clear(&dest_paths); strvec_clear(&destinations); - free(submodule_gitfile); + strvec_clear(&submodule_gitfiles_to_free); + free(submodule_gitfiles); free(modes); return ret; } diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index d489230df8..668f526303 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -9,6 +9,7 @@ This test tries to verify that add_submodule_odb works when the submodule was initialized previously but the checkout has since been removed. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 879a6dce60..86258f9f43 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git mv in subdirs' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-data.sh diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh index 5e3051da8b..dbbb3853dc 100755 --- a/t/t7417-submodule-path-url.sh +++ b/t/t7417-submodule-path-url.sh @@ -4,6 +4,7 @@ test_description='check handling of .gitmodule path with dash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh index ce64d8b137..479c8fdde1 100755 --- a/t/t7421-submodule-summary-add.sh +++ b/t/t7421-submodule-summary-add.sh @@ -10,6 +10,7 @@ while making sure to add submodules using `git submodule add` instead of `git add` as done in t7401. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' From b567004b4b43f9b0d88aa1f0b15698eae8f15836 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:37:39 +0200 Subject: [PATCH 20/46] global: improve const correctness when assigning string constants We're about to enable `-Wwrite-strings`, which changes the type of string constants to `const char[]`. Fix various sites where we assign such constants to non-const variables. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/bisect.c | 3 ++- builtin/blame.c | 2 +- builtin/bugreport.c | 2 +- builtin/check-ignore.c | 4 +-- builtin/clone.c | 6 ++--- builtin/commit.c | 6 ++--- builtin/diagnose.c | 2 +- builtin/log.c | 2 +- builtin/mailsplit.c | 4 +-- builtin/pull.c | 52 ++++++++++++++++++------------------ builtin/receive-pack.c | 4 +-- builtin/revert.c | 2 +- compat/regex/regcomp.c | 2 +- diff.c | 4 +-- diffcore-rename.c | 6 ++--- fmt-merge-msg.c | 2 +- fsck.c | 2 +- fsck.h | 2 +- gpg-interface.c | 2 +- http-backend.c | 2 +- imap-send.c | 6 ++--- pretty.c | 2 +- refs.c | 2 +- refs.h | 2 +- reftable/basics.c | 15 +++++------ reftable/basics.h | 4 +-- reftable/basics_test.c | 4 +-- reftable/record.c | 6 ++--- reftable/stack.c | 10 ++++--- reftable/stack_test.c | 8 +++--- run-command.c | 2 +- t/helper/test-hashmap.c | 3 ++- t/helper/test-json-writer.c | 10 +++---- t/helper/test-regex.c | 4 +-- t/helper/test-rot13-filter.c | 5 ++-- t/unit-tests/t-strbuf.c | 10 ++++--- trailer.c | 2 +- wt-status.c | 2 +- 38 files changed, 106 insertions(+), 102 deletions(-) diff --git a/builtin/bisect.c b/builtin/bisect.c index a58432b9d9..dabce9b542 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -262,7 +262,8 @@ static int bisect_reset(const char *commit) return bisect_clean_state(); } -static void log_commit(FILE *fp, char *fmt, const char *state, +static void log_commit(FILE *fp, + const char *fmt, const char *state, struct commit *commit) { struct pretty_print_context pp = {0}; diff --git a/builtin/blame.c b/builtin/blame.c index 838cd476be..98c7629b6a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what, { struct ident_split ident; size_t len, maillen, namelen; - char *tmp, *endp; + const char *tmp, *endp; const char *namebuf, *mailbuf; tmp = strstr(inbuf, what); diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 25f860a0d9..b3cc77af53 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) struct tm tm; enum diagnose_mode diagnose = DIAGNOSE_NONE; char *option_output = NULL; - char *option_suffix = "%Y-%m-%d-%H%M"; + const char *option_suffix = "%Y-%m-%d-%H%M"; const char *user_relative_path = NULL; char *prefixed_filename; size_t output_path_len; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 6c43430ec4..2bda6a1d46 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = { static void output_pattern(const char *path, struct path_pattern *pattern) { - char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : ""; - char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : ""; + const char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : ""; + const char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : ""; if (!nul_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); diff --git a/builtin/clone.c b/builtin/clone.c index 23993b905b..92ab7d7165 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -71,7 +71,7 @@ static char *option_branch = NULL; static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; static const char *ref_format; -static char *option_upload_pack = "git-upload-pack"; +static const char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; static int option_sparse_checkout; @@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = { static const char *get_repo_path_1(struct strbuf *path, int *is_bundle) { - static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; - static char *bundle_suffix[] = { ".bundle", "" }; + static const char *suffix[] = { "/.git", "", ".git/.git", ".git" }; + static const char *bundle_suffix[] = { ".bundle", "" }; size_t baselen = path->len; struct stat st; int i; diff --git a/builtin/commit.c b/builtin/commit.c index f53e7e86ff..75c741173e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -113,7 +113,7 @@ static char *template_file; * the commit message and/or authorship. */ static const char *author_message, *author_message_buffer; -static char *edit_message, *use_message; +static const char *edit_message, *use_message; static char *fixup_message, *fixup_commit, *squash_message; static const char *fixup_prefix; static int all, also, interactive, patch_interactive, only, amend, signoff; @@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ static int no_post_rewrite, allow_empty_message, pathspec_file_nul; -static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; -static char *sign_commit, *pathspec_from_file; +static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; +static const char *sign_commit, *pathspec_from_file; static struct strvec trailer_args = STRVEC_INIT; /* diff --git a/builtin/diagnose.c b/builtin/diagnose.c index 4f22eb2b55..4857a4395b 100644 --- a/builtin/diagnose.c +++ b/builtin/diagnose.c @@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix) struct tm tm; enum diagnose_mode mode = DIAGNOSE_STATS; char *option_output = NULL; - char *option_suffix = "%Y-%m-%d-%H%M"; + const char *option_suffix = "%Y-%m-%d-%H%M"; char *prefixed_filename; const struct option diagnose_options[] = { diff --git a/builtin/log.c b/builtin/log.c index 78a247d8a9..b8846a9458 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) o2->flags = flags2; } -static void gen_message_id(struct rev_info *info, char *base) +static void gen_message_id(struct rev_info *info, const char *base) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s.%"PRItime".git.%s", base, diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 3af9ddb8ae..fe6dbc5d05 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path) DIR *dir; struct dirent *dent; char *name = NULL; - char *subs[] = { "cur", "new", NULL }; - char **sub; + const char *subs[] = { "cur", "new", NULL }; + const char **sub; int ret = -1; for (sub = subs; *sub; ++sub) { diff --git a/builtin/pull.c b/builtin/pull.c index d622202bce..2d0429f14f 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -71,48 +71,48 @@ static const char * const pull_usage[] = { /* Shared options */ static int opt_verbosity; -static char *opt_progress; +static const char *opt_progress; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; /* Options passed to git-merge or git-rebase */ static enum rebase_type opt_rebase = -1; -static char *opt_diffstat; -static char *opt_log; -static char *opt_signoff; -static char *opt_squash; -static char *opt_commit; -static char *opt_edit; -static char *cleanup_arg; -static char *opt_ff; -static char *opt_verify_signatures; -static char *opt_verify; +static const char *opt_diffstat; +static const char *opt_log; +static const char *opt_signoff; +static const char *opt_squash; +static const char *opt_commit; +static const char *opt_edit; +static const char *cleanup_arg; +static const char *opt_ff; +static const char *opt_verify_signatures; +static const char *opt_verify; static int opt_autostash = -1; static int config_autostash; static int check_trust_level = 1; static struct strvec opt_strategies = STRVEC_INIT; static struct strvec opt_strategy_opts = STRVEC_INIT; -static char *opt_gpg_sign; +static const char *opt_gpg_sign; static int opt_allow_unrelated_histories; /* Options passed to git-fetch */ -static char *opt_all; -static char *opt_append; -static char *opt_upload_pack; +static const char *opt_all; +static const char *opt_append; +static const char *opt_upload_pack; static int opt_force; -static char *opt_tags; -static char *opt_prune; -static char *max_children; +static const char *opt_tags; +static const char *opt_prune; +static const char *max_children; static int opt_dry_run; -static char *opt_keep; -static char *opt_depth; -static char *opt_unshallow; -static char *opt_update_shallow; -static char *opt_refmap; -static char *opt_ipv4; -static char *opt_ipv6; +static const char *opt_keep; +static const char *opt_depth; +static const char *opt_unshallow; +static const char *opt_update_shallow; +static const char *opt_refmap; +static const char *opt_ipv4; +static const char *opt_ipv6; static int opt_show_forced_updates = -1; -static char *set_upstream; +static const char *set_upstream; static struct strvec opt_fetch = STRVEC_INIT; static struct option pull_options[] = { diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 01c1f04ece..c8d12ee0a7 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands, return code; } -static char *refuse_unconfigured_deny_msg = +static const char *refuse_unconfigured_deny_msg = N_("By default, updating the current branch in a non-bare repository\n" "is denied, because it will make the index and work tree inconsistent\n" "with what you pushed, and will require 'git reset --hard' to match\n" @@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void) rp_error("%s", _(refuse_unconfigured_deny_msg)); } -static char *refuse_unconfigured_deny_delete_current_msg = +static const char *refuse_unconfigured_deny_delete_current_msg = N_("By default, deleting the current branch is denied, because the next\n" "'git clone' won't result in any file checked out, causing confusion.\n" "\n" diff --git a/builtin/revert.c b/builtin/revert.c index 53935d2c68..7bf2b4e11d 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix, /* Check for incompatible command line arguments */ if (cmd) { - char *this_operation; + const char *this_operation; if (cmd == 'q') this_operation = "--quit"; else if (cmd == 'c') diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c index 2bc0f1187a..6c5d455e92 100644 --- a/compat/regex/regcomp.c +++ b/compat/regex/regcomp.c @@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len) { unsigned int table_size; #ifndef _LIBC - char *codeset_name; + const char *codeset_name; #endif memset (dfa, '\0', sizeof (re_dfa_t)); diff --git a/diff.c b/diff.c index e70301df76..ffd867ef6c 100644 --- a/diff.c +++ b/diff.c @@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a, return; } -static char *get_compact_summary(const struct diff_filepair *p, int is_renamed) +static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed) { if (!is_renamed) { if (p->status == DIFF_STATUS_ADDED) { @@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate, static int diff_populate_gitlink(struct diff_filespec *s, int size_only) { struct strbuf buf = STRBUF_INIT; - char *dirty = ""; + const char *dirty = ""; /* Are we looking at the work tree? */ if (s->dirty_submodule) diff --git a/diffcore-rename.c b/diffcore-rename.c index 5a6e2bcac7..0e1adb87df 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts) return highest_destination_dir; } -static char *UNKNOWN_DIR = "/"; /* placeholder -- short, illegal directory */ +static const char *UNKNOWN_DIR = "/"; /* placeholder -- short, illegal directory */ static int dir_rename_already_determinable(struct strintmap *counts) { @@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts) } static void increment_count(struct dir_rename_info *info, - char *old_dir, - char *new_dir) + const char *old_dir, + const char *new_dir) { struct strintmap *counts; struct strmap_entry *e; diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 7d144b803a..5af63ab5ab 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out, const char *current_branch) { int i = 0; - char *sep = ""; + const char *sep = ""; strbuf_addstr(out, "Merge "); for (i = 0; i < srcs.nr; i++) { diff --git a/fsck.c b/fsck.c index 7dff41413e..61cd48aa25 100644 --- a/fsck.c +++ b/fsck.c @@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size, } int fsck_buffer(const struct object_id *oid, enum object_type type, - void *data, unsigned long size, + const void *data, unsigned long size, struct fsck_options *options) { if (type == OBJ_BLOB) diff --git a/fsck.h b/fsck.h index 17fa2dda5d..4f0c4e6479 100644 --- a/fsck.h +++ b/fsck.h @@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size, * struct. */ int fsck_buffer(const struct object_id *oid, enum object_type, - void *data, unsigned long size, + const void *data, unsigned long size, struct fsck_options *options); /* diff --git a/gpg-interface.c b/gpg-interface.c index 5193223714..71a9382a61 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value, void *cb UNUSED) { struct gpg_format *fmt = NULL; - char *fmtname = NULL; + const char *fmtname = NULL; char *trust; int ret; diff --git a/http-backend.c b/http-backend.c index 5b65287ac9..5b4dca65ed 100644 --- a/http-backend.c +++ b/http-backend.c @@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c) int cmd_main(int argc UNUSED, const char **argv UNUSED) { - char *method = getenv("REQUEST_METHOD"); + const char *method = getenv("REQUEST_METHOD"); const char *proto_header; char *dir; struct service_cmd *cmd = NULL; diff --git a/imap-send.c b/imap-send.c index a5d1510180..8b723b34a5 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg) static void wrap_in_html(struct strbuf *msg) { struct strbuf buf = STRBUF_INIT; - static char *content_type = "Content-Type: text/html;\n"; - static char *pre_open = "
\n";
-	static char *pre_close = "
\n"; + static const char *content_type = "Content-Type: text/html;\n"; + static const char *pre_open = "
\n";
+	static const char *pre_close = "
\n"; const char *body = strstr(msg->buf, "\n\n"); if (!body) diff --git a/pretty.c b/pretty.c index 22a81506b7..ec05db5655 100644 --- a/pretty.c +++ b/pretty.c @@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts, static size_t parse_describe_args(const char *start, struct strvec *args) { struct { - char *name; + const char *name; enum { DESCRIBE_ARG_BOOL, DESCRIBE_ARG_INTEGER, diff --git a/refs.c b/refs.c index 8260c27cde..292e8d947e 100644 --- a/refs.c +++ b/refs.c @@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref) { struct ref_namespace_info *info = &ref_namespace[namespace]; if (info->ref_updated) - free(info->ref); + free((char *)info->ref); info->ref = ref; info->ref_updated = 1; } diff --git a/refs.h b/refs.h index 34568ee1fb..923f751d18 100644 --- a/refs.h +++ b/refs.h @@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt); */ struct ref_namespace_info { - char *ref; + const char *ref; enum decoration_type decoration; /* diff --git a/reftable/basics.c b/reftable/basics.c index fea711db7e..0058619ca6 100644 --- a/reftable/basics.c +++ b/reftable/basics.c @@ -67,9 +67,9 @@ void free_names(char **a) reftable_free(a); } -size_t names_length(char **names) +size_t names_length(const char **names) { - char **p = names; + const char **p = names; while (*p) p++; return p - names; @@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp) *namesp = names; } -int names_equal(char **a, char **b) +int names_equal(const char **a, const char **b) { - int i = 0; - for (; a[i] && b[i]; i++) { - if (strcmp(a[i], b[i])) { + size_t i = 0; + for (; a[i] && b[i]; i++) + if (strcmp(a[i], b[i])) return 0; - } - } - return a[i] == b[i]; } diff --git a/reftable/basics.h b/reftable/basics.h index 523ecd5307..c8fec68d4e 100644 --- a/reftable/basics.h +++ b/reftable/basics.h @@ -42,10 +42,10 @@ void free_names(char **a); void parse_names(char *buf, int size, char ***namesp); /* compares two NULL-terminated arrays of strings. */ -int names_equal(char **a, char **b); +int names_equal(const char **a, const char **b); /* returns the array size of a NULL-terminated array of strings. */ -size_t names_length(char **names); +size_t names_length(const char **names); /* Allocation routines; they invoke the functions set through * reftable_set_alloc() */ diff --git a/reftable/basics_test.c b/reftable/basics_test.c index 997c4d9e01..13bc761817 100644 --- a/reftable/basics_test.c +++ b/reftable/basics_test.c @@ -58,8 +58,8 @@ static void test_binsearch(void) static void test_names_length(void) { - char *a[] = { "a", "b", NULL }; - EXPECT(names_length(a) == 2); + const char *names[] = { "a", "b", NULL }; + EXPECT(names_length(names) == 2); } static void test_parse_names_normal(void) diff --git a/reftable/record.c b/reftable/record.c index 5506f3e913..a2cba5ef74 100644 --- a/reftable/record.c +++ b/reftable/record.c @@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in) return start_len - in.len; } -static int encode_string(char *str, struct string_view s) +static int encode_string(const char *str, struct string_view s) { struct string_view start = s; int l = strlen(str); @@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key, return REFTABLE_FORMAT_ERROR; } -static int null_streq(char *a, char *b) +static int null_streq(const char *a, const char *b) { - char *empty = ""; + const char *empty = ""; if (!a) a = empty; diff --git a/reftable/stack.c b/reftable/stack.c index a59ebe038d..09549c51c9 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st, return cur; } -static int reftable_stack_reload_once(struct reftable_stack *st, char **names, +static int reftable_stack_reload_once(struct reftable_stack *st, + const char **names, int reuse_open) { size_t cur_len = !st->merged ? 0 : st->merged->stack_len; @@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names, while (*names) { struct reftable_reader *rd = NULL; - char *name = *names++; + const char *name = *names++; /* this is linear; we assume compaction keeps the number of tables under control so this is not quadratic. */ @@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, goto out; } - err = reftable_stack_reload_once(st, names, reuse_open); + err = reftable_stack_reload_once(st, (const char **) names, reuse_open); if (!err) break; if (err != REFTABLE_NOT_EXIST_ERROR) @@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, err = read_lines(st->list_file, &names_after); if (err < 0) goto out; - if (names_equal(names_after, names)) { + if (names_equal((const char **) names_after, + (const char **) names)) { err = REFTABLE_NOT_EXIST_ERROR; goto out; } diff --git a/reftable/stack_test.c b/reftable/stack_test.c index 7889f818d1..07d89b45da 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -83,7 +83,7 @@ static void test_read_file(void) char out[1024] = "line1\n\nline2\nline3"; int n, err; char **names = NULL; - char *want[] = { "line1", "line2", "line3" }; + const char *want[] = { "line1", "line2", "line3" }; int i = 0; EXPECT(fd > 0); @@ -116,9 +116,9 @@ static void test_parse_names(void) static void test_names_equal(void) { - char *a[] = { "a", "b", "c", NULL }; - char *b[] = { "a", "b", "d", NULL }; - char *c[] = { "a", "b", NULL }; + const char *a[] = { "a", "b", "c", NULL }; + const char *b[] = { "a", "b", "d", NULL }; + const char *c[] = { "a", "b", NULL }; EXPECT(names_equal(a, a)); EXPECT(!names_equal(a, b)); diff --git a/run-command.c b/run-command.c index 1b821042b4..7600531fb6 100644 --- a/run-command.c +++ b/run-command.c @@ -663,7 +663,7 @@ int start_command(struct child_process *cmd) int need_in, need_out, need_err; int fdin[2], fdout[2], fderr[2]; int failed_errno; - char *str; + const char *str; /* * In case of errors we must keep the promise to close FDs diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 0eb0b3d49c..2912899558 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data, } static struct test_entry *alloc_test_entry(unsigned int hash, - char *key, char *value) + const char *key, + const char *value) { size_t klen = strlen(key); size_t vlen = strlen(value); diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c index afe393f597..ed52eb76bf 100644 --- a/t/helper/test-json-writer.c +++ b/t/helper/test-json-writer.c @@ -174,7 +174,7 @@ static void make_arr4(int pretty) jw_end(&arr4); } -static char *expect_nest1 = +static const char *expect_nest1 = "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; static struct json_writer nest1 = JSON_WRITER_INIT; @@ -195,10 +195,10 @@ static void make_nest1(int pretty) jw_release(&arr1); } -static char *expect_inline1 = +static const char *expect_inline1 = "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; -static char *pretty_inline1 = +static const char *pretty_inline1 = ("{\n" " \"obj1\": {\n" " \"a\": \"abc\",\n" @@ -236,10 +236,10 @@ static void make_inline1(int pretty) jw_end(&inline1); } -static char *expect_inline2 = +static const char *expect_inline2 = "[[1,2],[3,4],{\"a\":\"abc\"}]"; -static char *pretty_inline2 = +static const char *pretty_inline2 = ("[\n" " [\n" " 1,\n" diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c index 80042eafc2..366bd70976 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = { static int test_regex_bug(void) { - char *pat = "[^={} \t]+"; - char *str = "={}\nfred"; + const char *pat = "[^={} \t]+"; + const char *str = "={}\nfred"; regex_t r; regmatch_t m[1]; diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c index f8d564c622..7e1d9e0ee4 100644 --- a/t/helper/test-rot13-filter.c +++ b/t/helper/test-rot13-filter.c @@ -136,7 +136,7 @@ static void free_delay_entries(void) strmap_clear(&delay, 0); } -static void add_delay_entry(char *pathname, int count, int requested) +static void add_delay_entry(const char *pathname, int count, int requested) { struct delay_entry *entry = xcalloc(1, sizeof(*entry)); entry->count = count; @@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void) static void command_loop(void) { for (;;) { - char *buf, *output; + char *buf; + const char *output; char *pathname; struct delay_entry *entry; struct strbuf input = STRBUF_INIT; diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c index de434a4441..6027dafef7 100644 --- a/t/unit-tests/t-strbuf.c +++ b/t/unit-tests/t-strbuf.c @@ -2,7 +2,8 @@ #include "strbuf.h" /* wrapper that supplies tests with an empty, initialized strbuf */ -static void setup(void (*f)(struct strbuf*, void*), void *data) +static void setup(void (*f)(struct strbuf*, const void*), + const void *data) { struct strbuf buf = STRBUF_INIT; @@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data) } /* wrapper that supplies tests with a populated, initialized strbuf */ -static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data) +static void setup_populated(void (*f)(struct strbuf*, const void*), + const char *init_str, const void *data) { struct strbuf buf = STRBUF_INIT; @@ -64,7 +66,7 @@ static void t_dynamic_init(void) strbuf_release(&buf); } -static void t_addch(struct strbuf *buf, void *data) +static void t_addch(struct strbuf *buf, const void *data) { const char *p_ch = data; const char ch = *p_ch; @@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data) check_char(buf->buf[buf->len], ==, '\0'); } -static void t_addstr(struct strbuf *buf, void *data) +static void t_addstr(struct strbuf *buf, const void *data) { const char *text = data; size_t len = strlen(text); diff --git a/trailer.c b/trailer.c index 2bcb9ba8f7..72e5136c73 100644 --- a/trailer.c +++ b/trailer.c @@ -63,7 +63,7 @@ struct arg_item { static LIST_HEAD(conf_head); -static char *separators = ":"; +static const char *separators = ":"; static int configured; diff --git a/wt-status.c b/wt-status.c index ff4be071ca..7912545e4e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry( int mode; struct object_id oid; } stages[3]; - char *key; + const char *key; char submodule_token[5]; char unmerged_prefix = 'u'; char eol_char = s->null_termination ? '\0' : '\n'; From c113c5df7911bf7bc6a4542131ac5bf983532a97 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:37:43 +0200 Subject: [PATCH 21/46] global: convert intentionally-leaking config strings to consts There are multiple cases where we intentionally leak config strings: - `struct gpg_format` is used to track programs that can be used for signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The user can override the commands via several config variables. As the array is populated once, only, and the struct memers are never written to or free'd. - `struct ll_merge_driver` is used to track merge drivers. Same as with the GPG format, these drivers are populated once and then reused. Its data is never written to or free'd, either. - `struct userdiff_funcname` and `struct userdiff_driver` can be configured via `diff..*` to add additional drivers. Again, these have a global lifetime and are never written to or free'd. All of these are intentionally kept alive and are never written to. Furthermore, all of these are being assigned both string constants in some places, and allocated strings in other places. This will cause warnings once we enable `-Wwrite-strings`, so let's mark the respective fields as `const char *` and cast away the constness when assigning those values. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- gpg-interface.c | 4 ++-- merge-ll.c | 11 ++++++++--- userdiff.c | 10 +++++----- userdiff.h | 12 ++++++------ 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/gpg-interface.c b/gpg-interface.c index 71a9382a61..5c824aeb25 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { const char *name; - char *program; + const char *program; const char **verify_args; const char **sigs; int (*verify_signed_buffer)(struct signature_check *sigc, @@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value, if (fmtname) { fmt = get_format_by_name(fmtname); - return git_config_string(&fmt->program, var, value); + return git_config_string((char **) &fmt->program, var, value); } return 0; diff --git a/merge-ll.c b/merge-ll.c index e29b15fa4a..180c19df67 100644 --- a/merge-ll.c +++ b/merge-ll.c @@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *, struct ll_merge_driver { const char *name; - char *description; + const char *description; ll_merge_fn fn; char *recursive; struct ll_merge_driver *next; @@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value, ll_user_merge_tail = &(fn->next); } - if (!strcmp("name", key)) - return git_config_string(&fn->description, var, value); + if (!strcmp("name", key)) { + /* + * The description is leaking, but that's okay as we want to + * keep around the merge drivers anyway. + */ + return git_config_string((char **) &fn->description, var, value); + } if (!strcmp("driver", key)) { if (!value) diff --git a/userdiff.c b/userdiff.c index 82bc76b910..371032a413 100644 --- a/userdiff.c +++ b/userdiff.c @@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t static int parse_funcname(struct userdiff_funcname *f, const char *k, const char *v, int cflags) { - if (git_config_string(&f->pattern, k, v) < 0) + if (git_config_string((char **) &f->pattern, k, v) < 0) return -1; f->cflags = cflags; return 0; @@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v) if (!strcmp(type, "binary")) return parse_tristate(&drv->binary, k, v); if (!strcmp(type, "command")) - return git_config_string(&drv->external, k, v); + return git_config_string((char **) &drv->external, k, v); if (!strcmp(type, "textconv")) - return git_config_string(&drv->textconv, k, v); + return git_config_string((char **) &drv->textconv, k, v); if (!strcmp(type, "cachetextconv")) return parse_bool(&drv->textconv_want_cache, k, v); if (!strcmp(type, "wordregex")) - return git_config_string(&drv->word_regex, k, v); + return git_config_string((char **) &drv->word_regex, k, v); if (!strcmp(type, "algorithm")) - return git_config_string(&drv->algorithm, k, v); + return git_config_string((char **) &drv->algorithm, k, v); return 0; } diff --git a/userdiff.h b/userdiff.h index cc8e5abfef..d726804c3e 100644 --- a/userdiff.h +++ b/userdiff.h @@ -7,19 +7,19 @@ struct index_state; struct repository; struct userdiff_funcname { - char *pattern; + const char *pattern; int cflags; }; struct userdiff_driver { const char *name; - char *external; - char *algorithm; + const char *external; + const char *algorithm; int binary; struct userdiff_funcname funcname; - char *word_regex; - char *word_regex_multi_byte; - char *textconv; + const char *word_regex; + const char *word_regex_multi_byte; + const char *textconv; struct notes_cache *textconv_cache; int textconv_want_cache; }; From 23c32511b31f2123fd194ba3a89c758ba3e55143 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:37:48 +0200 Subject: [PATCH 22/46] refs/reftable: stop micro-optimizing refname allocations on copy When copying refs, we execute `write_copy_table()` to write the new table. As the names are given to us via `arg->newname` and `arg->oldname`, respectively, we optimize away some allocations by assigning those fields to the reftable records we are about to write directly, without duplicating them. This requires us to cast the input to `char *` pointers as they are in fact constant strings. Later on, we then unset the refname for all of the records before calling `reftable_log_record_release()` on them. We also do this when assigning the "HEAD" constant, but here we do not cast because its type is `char[]` by default. It's about to be turned into `const char *` though once we enable `-Wwrite-strings` and will thus cause another warning. It's quite dubious whether this micro-optimization really helps. We're about to write to disk anyway, which is going to be way slower than a small handful of allocations. Let's drop the optimization altogther and instead copy arguments to simplify the code and avoid the future warning with `-Wwrite-strings`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- refs/reftable-backend.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 1af86bbdec..e77faa2b9d 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) * old reference. */ refs[0] = old_ref; - refs[0].refname = (char *)arg->newname; + refs[0].refname = xstrdup(arg->newname); refs[0].update_index = creation_ts; if (arg->delete_old) { - refs[1].refname = (char *)arg->oldname; + refs[1].refname = xstrdup(arg->oldname); refs[1].value_type = REFTABLE_REF_DELETION; refs[1].update_index = deletion_ts; } @@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); fill_reftable_log_record(&logs[logs_nr], &committer_ident); - logs[logs_nr].refname = (char *)arg->newname; + logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = deletion_ts; logs[logs_nr].value.update.message = xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); @@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) if (append_head_reflog) { ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr] = logs[logs_nr - 1]; - logs[logs_nr].refname = "HEAD"; + logs[logs_nr].refname = xstrdup("HEAD"); + logs[logs_nr].value.update.name = + xstrdup(logs[logs_nr].value.update.name); + logs[logs_nr].value.update.email = + xstrdup(logs[logs_nr].value.update.email); + logs[logs_nr].value.update.message = + xstrdup(logs[logs_nr].value.update.message); logs_nr++; } } @@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); fill_reftable_log_record(&logs[logs_nr], &committer_ident); - logs[logs_nr].refname = (char *)arg->newname; + logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = creation_ts; logs[logs_nr].value.update.message = xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); @@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) */ ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr] = old_log; - logs[logs_nr].refname = (char *)arg->newname; + logs[logs_nr].refname = xstrdup(arg->newname); logs_nr++; /* @@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) if (arg->delete_old) { ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); - logs[logs_nr].refname = (char *)arg->oldname; + logs[logs_nr].refname = xstrdup(arg->oldname); logs[logs_nr].value_type = REFTABLE_LOG_DELETION; logs[logs_nr].update_index = old_log.update_index; logs_nr++; @@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) reftable_iterator_destroy(&it); string_list_clear(&skip, 0); strbuf_release(&errbuf); - for (i = 0; i < logs_nr; i++) { - if (!strcmp(logs[i].refname, "HEAD")) - continue; - logs[i].refname = NULL; + for (i = 0; i < logs_nr; i++) reftable_log_record_release(&logs[i]); - } free(logs); + for (i = 0; i < ARRAY_SIZE(refs); i++) + reftable_ref_record_release(&refs[i]); reftable_ref_record_release(&old_ref); reftable_log_record_release(&old_log); return ret; From 66f892bb075f19bed784b86c7850a89c9a865aca Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:37:52 +0200 Subject: [PATCH 23/46] reftable: cast away constness when assigning constants to records The reftable records are used in multiple ways throughout the reftable library. In many of those cases they merely act as input to a function without getting modified by it at all. Most importantly, this happens when writing records and when querying for records. We rely on this in our tests and thus assign string constants to those fields, which is about to generate warnings as those fields are of type `char *`. While we could go through the process and instead allocate those strings in all of our tests, this feels quite unnecessary. Instead, add casts to `char *` for all of those strings. As this is part of our tests, this also nicely serves as a demonstration that nothing writes or frees those string constants, which would otherwise lead to segfaults. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- reftable/block_test.c | 2 +- reftable/merged_test.c | 44 +++++++++++++++++------------------ reftable/readwrite_test.c | 32 +++++++++++++------------- reftable/stack_test.c | 48 +++++++++++++++++++-------------------- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/reftable/block_test.c b/reftable/block_test.c index 26a9cfbc83..90aecd5a7c 100644 --- a/reftable/block_test.c +++ b/reftable/block_test.c @@ -42,7 +42,7 @@ static void test_block_read_write(void) block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, header_off, hash_size(GIT_SHA1_FORMAT_ID)); - rec.u.ref.refname = ""; + rec.u.ref.refname = (char *) ""; rec.u.ref.value_type = REFTABLE_REF_DELETION; n = block_writer_add(&bw, &rec); EXPECT(n == REFTABLE_API_ERROR); diff --git a/reftable/merged_test.c b/reftable/merged_test.c index 530fc82d1c..6d1159d12d 100644 --- a/reftable/merged_test.c +++ b/reftable/merged_test.c @@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n) static void test_merged_between(void) { struct reftable_ref_record r1[] = { { - .refname = "b", + .refname = (char *) "b", .update_index = 1, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 1, 2, 3, 0 }, } }; struct reftable_ref_record r2[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 2, .value_type = REFTABLE_REF_DELETION, } }; @@ -165,38 +165,38 @@ static void test_merged(void) { struct reftable_ref_record r1[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 1, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 1 }, }, { - .refname = "b", + .refname = (char *) "b", .update_index = 1, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 1 }, }, { - .refname = "c", + .refname = (char *) "c", .update_index = 1, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 1 }, } }; struct reftable_ref_record r2[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 2, .value_type = REFTABLE_REF_DELETION, } }; struct reftable_ref_record r3[] = { { - .refname = "c", + .refname = (char *) "c", .update_index = 3, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 2 }, }, { - .refname = "d", + .refname = (char *) "d", .update_index = 3, .value_type = REFTABLE_REF_VAL1, .value.val1 = { 1 }, @@ -291,46 +291,46 @@ static void test_merged_logs(void) { struct reftable_log_record r1[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 2, .value_type = REFTABLE_LOG_UPDATE, .value.update = { .old_hash = { 2 }, /* deletion */ - .name = "jane doe", - .email = "jane@invalid", - .message = "message2", + .name = (char *) "jane doe", + .email = (char *) "jane@invalid", + .message = (char *) "message2", } }, { - .refname = "a", + .refname = (char *) "a", .update_index = 1, .value_type = REFTABLE_LOG_UPDATE, .value.update = { .old_hash = { 1 }, .new_hash = { 2 }, - .name = "jane doe", - .email = "jane@invalid", - .message = "message1", + .name = (char *) "jane doe", + .email = (char *) "jane@invalid", + .message = (char *) "message1", } }, }; struct reftable_log_record r2[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 3, .value_type = REFTABLE_LOG_UPDATE, .value.update = { .new_hash = { 3 }, - .name = "jane doe", - .email = "jane@invalid", - .message = "message3", + .name = (char *) "jane doe", + .email = (char *) "jane@invalid", + .message = (char *) "message3", } }, }; struct reftable_log_record r3[] = { { - .refname = "a", + .refname = (char *) "a", .update_index = 2, .value_type = REFTABLE_LOG_DELETION, }, @@ -406,7 +406,7 @@ static void test_default_write_opts(void) reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record rec = { - .refname = "master", + .refname = (char *) "master", .update_index = 1, }; int err; diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c index a6dbd214c5..c55019232b 100644 --- a/reftable/readwrite_test.c +++ b/reftable/readwrite_test.c @@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N, log.update_index = update_index; log.value_type = REFTABLE_LOG_UPDATE; set_test_hash(log.value.update.new_hash, i); - log.value.update.message = "message"; + log.value.update.message = (char *) "message"; n = reftable_writer_add_log(w, &log); EXPECT(n == 0); @@ -118,15 +118,15 @@ static void test_log_buffer_size(void) int err; int i; struct reftable_log_record - log = { .refname = "refs/heads/master", + log = { .refname = (char *) "refs/heads/master", .update_index = 0xa, .value_type = REFTABLE_LOG_UPDATE, .value = { .update = { - .name = "Han-Wen Nienhuys", - .email = "hanwen@google.com", + .name = (char *) "Han-Wen Nienhuys", + .email = (char *) "hanwen@google.com", .tz_offset = 100, .time = 0x5e430672, - .message = "commit: 9\n", + .message = (char *) "commit: 9\n", } } }; struct reftable_writer *w = reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); @@ -156,15 +156,15 @@ static void test_log_overflow(void) }; int err; struct reftable_log_record log = { - .refname = "refs/heads/master", + .refname = (char *) "refs/heads/master", .update_index = 0xa, .value_type = REFTABLE_LOG_UPDATE, .value = { .update = { .old_hash = { 1 }, .new_hash = { 2 }, - .name = "Han-Wen Nienhuys", - .email = "hanwen@google.com", + .name = (char *) "Han-Wen Nienhuys", + .email = (char *) "hanwen@google.com", .tz_offset = 100, .time = 0x5e430672, .message = msg, @@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void) char message[100] = { 0 }; int err, i, n; struct reftable_log_record log = { - .refname = "refname", + .refname = (char *) "refname", .value_type = REFTABLE_LOG_UPDATE, .value = { .update = { .new_hash = { 1 }, .old_hash = { 2 }, - .name = "My Name", - .email = "myname@invalid", + .name = (char *) "My Name", + .email = (char *) "myname@invalid", .message = message, }, }, @@ -728,7 +728,7 @@ static void test_write_empty_key(void) struct reftable_writer *w = reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record ref = { - .refname = "", + .refname = (char *) "", .update_index = 1, .value_type = REFTABLE_REF_DELETION, }; @@ -752,18 +752,18 @@ static void test_write_key_order(void) reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); struct reftable_ref_record refs[2] = { { - .refname = "b", + .refname = (char *) "b", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, .value = { - .symref = "target", + .symref = (char *) "target", }, }, { - .refname = "a", + .refname = (char *) "a", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, .value = { - .symref = "target", + .symref = (char *) "target", }, } }; diff --git a/reftable/stack_test.c b/reftable/stack_test.c index 07d89b45da..4abf92636d 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void) struct reftable_stack *st = NULL; int err; struct reftable_ref_record ref = { - .refname = "HEAD", + .refname = (char *) "HEAD", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; struct reftable_ref_record dest = { NULL }; struct stat stat_result = { 0 }; @@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void) int err; struct reftable_ref_record ref1 = { - .refname = "HEAD", + .refname = (char *) "HEAD", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; struct reftable_ref_record ref2 = { - .refname = "branch2", + .refname = (char *) "branch2", .update_index = 2, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; @@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void) struct reftable_addition *add = NULL; struct reftable_ref_record ref = { - .refname = "HEAD", + .refname = (char *) "HEAD", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; struct reftable_ref_record dest = { NULL }; @@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void) struct reftable_ref_record ref = { .update_index = reftable_stack_next_update_index(st), .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; char name[100]; @@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void) static void test_reftable_stack_auto_compaction_fails_gracefully(void) { struct reftable_ref_record ref = { - .refname = "refs/heads/master", + .refname = (char *) "refs/heads/master", .update_index = 1, .value_type = REFTABLE_REF_VAL1, .value.val1 = {0x01}, @@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void) struct reftable_stack *st = NULL; int err; struct reftable_ref_record ref1 = { - .refname = "name1", + .refname = (char *) "name1", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; struct reftable_ref_record ref2 = { - .refname = "name2", + .refname = (char *) "name2", .update_index = 1, .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; err = reftable_new_stack(&st, dir, cfg); @@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void) struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_log_record input = { - .refname = "branch", + .refname = (char *) "branch", .update_index = 1, .value_type = REFTABLE_LOG_UPDATE, .value = { @@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void) err = reftable_new_stack(&st, dir, cfg); EXPECT_ERR(err); - input.value.update.message = "one\ntwo"; + input.value.update.message = (char *) "one\ntwo"; err = reftable_stack_add(st, &write_test_log, &arg); EXPECT(err == REFTABLE_API_ERROR); - input.value.update.message = "one"; + input.value.update.message = (char *) "one"; err = reftable_stack_add(st, &write_test_log, &arg); EXPECT_ERR(err); @@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void) EXPECT_ERR(err); EXPECT(0 == strcmp(dest.value.update.message, "one\n")); - input.value.update.message = "two\n"; + input.value.update.message = (char *) "two\n"; arg.update_index = 2; err = reftable_stack_add(st, &write_test_log, &arg); EXPECT_ERR(err); @@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void) int err; struct reftable_ref_record ref = { - .refname = "master", + .refname = (char *) "master", .value_type = REFTABLE_REF_SYMREF, - .value.symref = "target", + .value.symref = (char *) "target", .update_index = 1, }; struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID }; @@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void) .refname = name, .update_index = reftable_stack_next_update_index(st), .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; snprintf(name, sizeof(name), "branch%04d", i); @@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void) struct reftable_ref_record ref = { .update_index = reftable_stack_next_update_index(st), .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; /* @@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void) .refname = name, .update_index = reftable_stack_next_update_index(st1), .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; snprintf(name, sizeof(name), "branch%04d", i); @@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void) .refname = name, .update_index = reftable_stack_next_update_index(st1), .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", + .value.symref = (char *) "master", }; snprintf(name, sizeof(name), "branch%04d", i); From 235ac3f81ad1950f2e6b47b30561eb96844e1c85 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:37:57 +0200 Subject: [PATCH 24/46] refspec: remove global tag refspec structure We have a global tag refspec structure that is used by both git-clone(1) and git-fetch(1). Initialization of the structure will break once we enable `-Wwrite-strings`, even though the breakage is harmless. While we could just add casts, the structure isn't really required in the first place as we can simply initialize the structures at the respective callsites. Refactor the code accordingly. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 8 ++++++-- builtin/fetch.c | 11 ++++++++--- refspec.c | 13 ------------- refspec.h | 1 - 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 92ab7d7165..bde1d284a2 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs, struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD")); struct ref *local_refs = head; struct ref **tail = head ? &head->next : &local_refs; + struct refspec_item tag_refspec; + + refspec_item_init(&tag_refspec, TAG_REFSPEC, 0); if (option_single_branch) { struct ref *remote_head = NULL; @@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs, &tail, 0); /* if --branch=tag, pull the requested tag explicitly */ - get_fetch_map(remote_head, tag_refspec, &tail, 0); + get_fetch_map(remote_head, &tag_refspec, &tail, 0); } free_refs(remote_head); } else { @@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs, } if (!option_mirror && !option_single_branch && !option_no_tags) - get_fetch_map(refs, tag_refspec, &tail, 0); + get_fetch_map(refs, &tag_refspec, &tail, 0); + refspec_item_clear(&tag_refspec); return local_refs; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 75255dc600..06b60867f5 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote, } } - if (tags == TAGS_SET) + if (tags == TAGS_SET) { + struct refspec_item tag_refspec; + /* also fetch all tags */ - get_fetch_map(remote_refs, tag_refspec, &tail, 0); - else if (tags == TAGS_DEFAULT && *autotags) + refspec_item_init(&tag_refspec, TAG_REFSPEC, 0); + get_fetch_map(remote_refs, &tag_refspec, &tail, 0); + refspec_item_clear(&tag_refspec); + } else if (tags == TAGS_DEFAULT && *autotags) { find_non_local_tags(remote_refs, NULL, &ref_map, &tail); + } /* Now append any refs to be updated opportunistically: */ *tail = orefs; diff --git a/refspec.c b/refspec.c index d60932f4de..1df5de6c2f 100644 --- a/refspec.c +++ b/refspec.c @@ -7,19 +7,6 @@ #include "refspec.h" #include "strbuf.h" -static struct refspec_item s_tag_refspec = { - .force = 0, - .pattern = 1, - .matching = 0, - .exact_sha1 = 0, - .negative = 0, - .src = "refs/tags/*", - .dst = "refs/tags/*", -}; - -/* See TAG_REFSPEC for the string version */ -const struct refspec_item *tag_refspec = &s_tag_refspec; - /* * Parses the provided refspec 'refspec' and populates the refspec_item 'item'. * Returns 1 if successful and 0 if the refspec is invalid. diff --git a/refspec.h b/refspec.h index 8c0c446993..754be45cee 100644 --- a/refspec.h +++ b/refspec.h @@ -2,7 +2,6 @@ #define REFSPEC_H #define TAG_REFSPEC "refs/tags/*:refs/tags/*" -extern const struct refspec_item *tag_refspec; /** * A struct refspec_item holds the parsed interpretation of a refspec. If it From 81654d27bf9e2f3745fb6190cb5b0863f91a644e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:02 +0200 Subject: [PATCH 25/46] builtin/remote: cast away constness in `get_head_names()` In `get_head_names()`, we assign the "refs/heads/*" string constant to `struct refspec_item::{src,dst}`, which are both non-constant pointers. Ideally, we'd refactor the code such that both of these fields were constant. But `struct refspec_item` is used for two different usecases with conflicting requirements: - To query for a source or destination based on the given refspec. The caller either sets `src` or `dst` as the branch that we want to search for, and the respective other field gets populated. The fields should be constant when being used as a query parameter, which is owned by the caller, and non-constant when being used as an out parameter, which is owned by the refspec item. This is is contradictory in itself already. - To store refspec items with their respective source and destination branches, in which case both fields should be owned by the struct. Ideally, we'd split up this interface to clearly separate between querying and storing, which would enable us to clarify lifetimes of the strings. This would be a much bigger undertaking though. Instead, accept the status quo for now and cast away the constness of the source and destination patterns. We know that those are not being written to or freed, so while this is ugly it certainly is fine for now. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/remote.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/remote.c b/builtin/remote.c index d52b1c0e10..b44f580b8c 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat { struct ref *ref, *matches; struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; - struct refspec_item refspec; + struct refspec_item refspec = { + .force = 0, + .pattern = 1, + .src = (char *) "refs/heads/*", + .dst = (char *) "refs/heads/*", + }; - memset(&refspec, 0, sizeof(refspec)); - refspec.force = 0; - refspec.pattern = 1; - refspec.src = refspec.dst = "refs/heads/*"; get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), fetch_map, 1); @@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat free_refs(fetch_map); free_refs(matches); - return 0; } From 86badd4d0a51262c2da1ca45c36612a28aad3b59 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:06 +0200 Subject: [PATCH 26/46] diff: cast string constant in `fill_textconv()` The `fill_textconv()` function is responsible for converting an input file with a textconv driver, which is then passed to the caller. Weirdly though, the function also handles the case where there is no textconv driver at all. In that case, it will return either the contents of the populated filespec, or an empty string if the filespec is invalid. These two cases have differing memory ownership semantics. When there is a textconv driver, then the result is an allocated string. Otherwise, the result is either a string constant or owned by the filespec struct. All callers are in fact aware of this weirdness and only end up freeing the output buffer when they had a textconv driver. Ideally, we'd split up this interface to only perform the conversion via the textconv driver, and BUG in case the caller didn't provide one. This would make memory ownership semantics much more straight forward. For now though, let's simply cast the empty string constant to `char *` to avoid a warning with `-Wwrite-strings`. This is equivalent to the same cast that we already have in `fill_mmfile()`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff.c b/diff.c index ffd867ef6c..cecda216cf 100644 --- a/diff.c +++ b/diff.c @@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r, if (!driver) { if (!DIFF_FILE_VALID(df)) { - *outbuf = ""; + *outbuf = (char *) ""; return 0; } if (diff_populate_filespec(r, df, NULL)) From 42d2ad55566013535c16b80fc2d445da93cceed3 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:11 +0200 Subject: [PATCH 27/46] line-log: stop assigning string constant to file parent buffer Stop assigning a string constant to the file parent buffer and instead assign an allocated string. While the code is fine in practice, it will break once we compile with `-Wwrite-strings`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- line-log.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/line-log.c b/line-log.c index 8ff6ccb772..bd3e663c24 100644 --- a/line-log.c +++ b/line-log.c @@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev, struct range_set tmp; struct diff_ranges diff; mmfile_t file_parent, file_target; + char *parent_data_to_free = NULL; assert(pair->two->path); while (rg) { @@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev, file_parent.ptr = pair->one->data; file_parent.size = pair->one->size; } else { - file_parent.ptr = ""; + file_parent.ptr = parent_data_to_free = xstrdup(""); file_parent.size = 0; } @@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev, diff_ranges_release(&diff); + free(parent_data_to_free); return ((*diff_out)->parent.nr > 0); } From 394affd46dc9b0c805df6e999837aa297f844fc0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:16 +0200 Subject: [PATCH 28/46] line-log: always allocate the output prefix The returned string by `output_prefix()` is sometimes a string constant and sometimes an allocated string. This has been fine until now because we always leak the allocated strings, and thus we never tried to free the string constant. Fix the code to always return an allocated string and free the returned value at all callsites. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- line-log.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/line-log.c b/line-log.c index bd3e663c24..67c80b39a0 100644 --- a/line-log.c +++ b/line-log.c @@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first, static char *output_prefix(struct diff_options *opt) { - char *prefix = ""; - if (opt->output_prefix) { struct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data); - prefix = sb->buf; + return sb->buf; + } else { + return xstrdup(""); } - - return prefix; } static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range) @@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT); if (!pair || !diff) - return; + goto out; if (pair->one->oid_valid) fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends); @@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang c_context, c_reset, opt->file); } +out: free(p_ends); free(t_ends); + free(prefix); } /* @@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang */ static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range) { - fprintf(rev->diffopt.file, "%s\n", output_prefix(&rev->diffopt)); + char *prefix = output_prefix(&rev->diffopt); + + fprintf(rev->diffopt.file, "%s\n", prefix); + free(prefix); + while (range) { dump_diff_hacky_one(rev, range); range = range->next; From b31607a3e03b2002c468c16bb71fcaf2d1846e09 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:20 +0200 Subject: [PATCH 29/46] entry: refactor how we remove items for delayed checkouts When finalizing a delayed checkout, we sort out several strings from the passed-in string list by first assigning the empty string to those filters and then calling `string_list_remove_empty_items()`. Assigning the empty string will cause compiler warnings though as the string is a `char *` once we enable `-Wwrite-strings`. Refactor the code to use a `NULL` pointer with `filter_string_list()` instead to avoid this warning. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- entry.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/entry.c b/entry.c index b8c257f6f9..f291d8eee6 100644 --- a/entry.c +++ b/entry.c @@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data) return !available; } +static int string_is_not_null(struct string_list_item *item, void *data UNUSED) +{ + return !!item->string; +} + int finish_delayed_checkout(struct checkout *state, int show_progress) { int errs = 0; @@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress) if (!async_query_available_blobs(filter->string, &available_paths)) { /* Filter reported an error */ errs = 1; - filter->string = ""; + filter->string = NULL; continue; } if (available_paths.nr <= 0) { @@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress) * filter from the list (see * "string_list_remove_empty_items" call below). */ - filter->string = ""; + filter->string = NULL; continue; } @@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress) * Do not ask the filter for available blobs, * again, as the filter is likely buggy. */ - filter->string = ""; + filter->string = NULL; continue; } ce = index_file_exists(state->istate, path->string, @@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress) errs = 1; } } - string_list_remove_empty_items(&dco->filters, 0); + + filter_string_list(&dco->filters, 0, string_is_not_null, NULL); } stop_progress(&progress); string_list_clear(&dco->filters, 0); From 32f9929109e2858b81403f2cbb2a80774f822859 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:25 +0200 Subject: [PATCH 30/46] ident: add casts for fallback name and GECOS In `xgetpwuid_self()`, we return a fallback identity when it was not possible to look up the current identity. This fallback identity needs to be internal and must never be written to by the calles as specified by getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked as non-constant though, it will cause a warning to assign constant strings to them once compiling with `-Wwrite-strings`. Add explicit casts to avoid the warning. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- ident.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ident.c b/ident.c index cc7afdbf81..caf41fb2a9 100644 --- a/ident.c +++ b/ident.c @@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus) pw = getpwuid(getuid()); if (!pw) { static struct passwd fallback; - fallback.pw_name = "unknown"; + fallback.pw_name = (char *) "unknown"; #ifndef NO_GECOS_IN_PWENT - fallback.pw_gecos = "Unknown"; + fallback.pw_gecos = (char *) "Unknown"; #endif pw = &fallback; if (is_bogus) From 724b6d1e18606ca2fa14d7cc48c3bf3884363e25 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:30 +0200 Subject: [PATCH 31/46] object-file: mark cached object buffers as const The buffers of cached objects are never modified, but are still stored as a non-constant pointer. This will cause a compiler warning once we enable the `-Wwrite-strings` compiler warning as we assign an empty constant string when initializing the static `empty_tree` cached object. Convert the field to be constant. This requires us to shuffle around the code a bit because we memcpy(3P) into the allocated buffer in `pretend_object_file()`. This is easily fixed though by allocating the buffer into a temporary variable first. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- object-file.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/object-file.c b/object-file.c index 610b1f465c..08c00dcc02 100644 --- a/object-file.c +++ b/object-file.c @@ -277,7 +277,7 @@ int hash_algo_by_length(int len) static struct cached_object { struct object_id oid; enum object_type type; - void *buf; + const void *buf; unsigned long size; } *cached_objects; static int cached_object_nr, cached_object_alloc; @@ -1778,6 +1778,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type, struct object_id *oid) { struct cached_object *co; + char *co_buf; hash_object_file(the_hash_algo, buf, len, type, oid); if (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) || @@ -1787,8 +1788,9 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type, co = &cached_objects[cached_object_nr++]; co->size = len; co->type = type; - co->buf = xmalloc(len); - memcpy(co->buf, buf, len); + co_buf = xmalloc(len); + memcpy(co_buf, buf, len); + co->buf = co_buf; oidcpy(&co->oid, oid); return 0; } From 9f03e4813a5b4e469b3a7f5ad4ada3a9c3f92bfd Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:35 +0200 Subject: [PATCH 32/46] object-file: make `buf` parameter of `index_mem()` a constant The `buf` parameter of `index_mem()` is a non-constant string. This will break once we enable `-Wwrite-strings` because we also pass constants from at least one callsite. Adapt the parameter to be a constant. As we cannot free the buffer without casting now, this also requires us to move the lifetime of the nested buffer around. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- object-file.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/object-file.c b/object-file.c index 08c00dcc02..0b58751f94 100644 --- a/object-file.c +++ b/object-file.c @@ -2484,12 +2484,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED, } static int index_mem(struct index_state *istate, - struct object_id *oid, void *buf, size_t size, + struct object_id *oid, + const void *buf, size_t size, enum object_type type, const char *path, unsigned flags) { + struct strbuf nbuf = STRBUF_INIT; int ret = 0; - int re_allocated = 0; int write_object = flags & HASH_WRITE_OBJECT; if (!type) @@ -2499,11 +2500,10 @@ static int index_mem(struct index_state *istate, * Convert blobs to git internal format */ if ((type == OBJ_BLOB) && path) { - struct strbuf nbuf = STRBUF_INIT; if (convert_to_git(istate, path, buf, size, &nbuf, get_conv_flags(flags))) { - buf = strbuf_detach(&nbuf, &size); - re_allocated = 1; + buf = nbuf.buf; + size = nbuf.len; } } if (flags & HASH_FORMAT_CHECK) { @@ -2520,8 +2520,8 @@ static int index_mem(struct index_state *istate, ret = write_object_file(buf, size, type, oid); else hash_object_file(the_hash_algo, buf, size, type, oid); - if (re_allocated) - free(buf); + + strbuf_release(&nbuf); return ret; } From 9c076c32fbb15a0887a1ed4d2afa7e5a3fd74727 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:39 +0200 Subject: [PATCH 33/46] pretty: add casts for decoration option pointers The `struct decoration_options` have a prefix and suffix field which are both non-constant, but we assign a constant pointer to them. This is safe to do because we pass them to `format_decorations()`, which never modifies these pointers, and then immediately discard the structure. Add explicit casts to avoid compilation warnings with `-Wwrite-strings`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- pretty.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pretty.c b/pretty.c index ec05db5655..1df9d635fb 100644 --- a/pretty.c +++ b/pretty.c @@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ case 'D': { const struct decoration_options opts = { - .prefix = "", - .suffix = "" + .prefix = (char *) "", + .suffix = (char *) "", }; format_decorations(sb, commit, c->auto_color, &opts); From e7b40195ae0082d04ea8c0d1769d90ea700b76f2 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:44 +0200 Subject: [PATCH 34/46] compat/win32: fix const-correctness with string constants Adjust various places in our Win32 compatibility layer where we are not assigning string constants to `const char *` variables. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- compat/basename.c | 16 ++++++++++++++-- compat/mingw.c | 28 ++++++++++++++++------------ compat/winansi.c | 2 +- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/compat/basename.c b/compat/basename.c index 96bd9533b4..c33579ef61 100644 --- a/compat/basename.c +++ b/compat/basename.c @@ -10,7 +10,13 @@ char *gitbasename (char *path) skip_dos_drive_prefix(&path); if (!path || !*path) - return "."; + /* + * basename(3P) is mis-specified because it returns a + * non-constant pointer even though it is specified to return a + * pointer to internal memory at times. The cast is a result of + * that. + */ + return (char *) "."; for (base = path; *path; path++) { if (!is_dir_sep(*path)) @@ -34,7 +40,13 @@ char *gitdirname(char *path) int dos_drive_prefix; if (!p) - return "."; + /* + * dirname(3P) is mis-specified because it returns a + * non-constant pointer even though it is specified to return a + * pointer to internal memory at times. The cast is a result of + * that. + */ + return (char *) "."; if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) goto dot; diff --git a/compat/mingw.c b/compat/mingw.c index 6b06ea540f..d378cd04cb 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid) p->pw_name = user_name; p->pw_gecos = get_extended_user_info(NameDisplay); if (!p->pw_gecos) - p->pw_gecos = "unknown"; + /* + * Data returned by getpwuid(3P) is treated as internal and + * must never be written to or freed. + */ + p->pw_gecos = (char *) "unknown"; p->pw_dir = NULL; initialized = 1; @@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report) strbuf_addf(report, "'%s' is on a file system that does " "not record ownership\n", path); } else if (report) { - LPSTR str1, str2, str3, str4, to_free1 = NULL, - to_free3 = NULL, to_local_free2 = NULL, - to_local_free4 = NULL; + PCSTR str1, str2, str3, str4; + LPSTR to_free1 = NULL, to_free3 = NULL, + to_local_free2 = NULL, to_local_free4 = NULL; - if (user_sid_to_user_name(sid, &str1)) - to_free1 = str1; + if (user_sid_to_user_name(sid, &to_free1)) + str1 = to_free1; else str1 = "(inconvertible)"; - if (ConvertSidToStringSidA(sid, &str2)) - to_local_free2 = str2; + if (ConvertSidToStringSidA(sid, &to_local_free2)) + str2 = to_local_free2; else str2 = "(inconvertible)"; @@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report) str4 = "(invalid)"; } else { if (user_sid_to_user_name(current_user_sid, - &str3)) - to_free3 = str3; + &to_free3)) + str3 = to_free3; else str3 = "(inconvertible)"; if (ConvertSidToStringSidA(current_user_sid, - &str4)) - to_local_free4 = str4; + &to_local_free4)) + str4 = to_local_free4; else str4 = "(inconvertible)"; } diff --git a/compat/winansi.c b/compat/winansi.c index f83610f684..575813bde8 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len) /* convert utf-8 to utf-16 */ int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len); if (wlen < 0) { - wchar_t *err = L"[invalid]"; + const wchar_t *err = L"[invalid]"; WriteConsoleW(console, err, wcslen(err), &dummy, NULL); return; } From 8d3a7ce441422f4b48f14c19a1b3be4ba7bfd30f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:49 +0200 Subject: [PATCH 35/46] http: do not assign string constant to non-const field In `write_accept_language()`, we put all acceptable languages into an array. While all entries in that array are allocated strings, the final entry in that array is a string constant. This is fine because we explicitly skip over the last entry when freeing the array, but will cause warnings once we enable `-Wwrite-strings`. Adapt the code to also allocate the final entry. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- http.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/http.c b/http.c index 67cc47d28f..2dea2d03da 100644 --- a/http.c +++ b/http.c @@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf) /* add '*' */ REALLOC_ARRAY(language_tags, num_langs + 1); - language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */ + language_tags[num_langs++] = xstrdup("*"); /* compute decimal_places */ for (max_q = 1, decimal_places = 0; @@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf) } } - /* free language tags -- last one is a static '*' */ - for (i = 0; i < num_langs - 1; i++) + for (i = 0; i < num_langs; i++) free(language_tags[i]); free(language_tags); } From e463c5e8a0c306b62c25ff5856087ab32403158a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:54 +0200 Subject: [PATCH 36/46] parse-options: cast long name for OPTION_ALIAS We assign the long name for OPTION_ALIAS options to a non-constant value field. We know that the variable will never be written to, but this will cause warnings once we enable `-Wwrite-strings`. Cast away the constness to be prepared for this change. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- parse-options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse-options.h b/parse-options.h index bd62e20268..ae15342390 100644 --- a/parse-options.h +++ b/parse-options.h @@ -355,7 +355,7 @@ struct option { .type = OPTION_ALIAS, \ .short_name = (s), \ .long_name = (l), \ - .value = (source_long_name), \ + .value = (char *)(source_long_name), \ } #define OPT_SUBCOMMAND_F(l, v, fn, f) { \ From 5bd0851d97be732470eab8cb8d7255c7050ef384 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:38:58 +0200 Subject: [PATCH 37/46] send-pack: always allocate receive status In `receive_status()`, we record the reason why ref updates have been rejected by the remote via the `remote_status`. But while we allocate the assigned string when a reason was given, we assign a string constant when no reason was given. This has been working fine so far due to two reasons: - We don't ever free the refs in git-send-pack(1)' - Remotes always give a reason, at least as implemented by Git proper. Adapt the code to always allocate the receive status string and free the refs. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/send-pack.c | 2 ++ send-pack.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 3df9eaad09..17cae6bbbd 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) /* stable plumbing output; do not modify or localize */ fprintf(stderr, "Everything up-to-date\n"); + free_refs(remote_refs); + free_refs(local_refs); return ret; } diff --git a/send-pack.c b/send-pack.c index 37f59d4f66..88e96d000b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs) if (p) hint->remote_status = xstrdup(p); else - hint->remote_status = "failed"; + hint->remote_status = xstrdup("failed"); } else { hint->status = REF_STATUS_OK; hint->remote_status = xstrdup_or_null(p); From a3da6948c3b6c272bf7a381b86df9a60bdf2b052 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:03 +0200 Subject: [PATCH 38/46] remote-curl: avoid assigning string constant to non-const variable When processing remote options, we split the option line into two by searching for a space. If there is one, we replace the space with '\0', otherwise we implicitly assume that the value is "true" and thus assign a string constant. As the return value of strchr(3P) weirdly enough is a `char *` even though it gets a `const char *` as input, the assigned-to variable also is a non-constant. This is fine though because the argument is in fact an allocated string, and thus we are allowed to modify it. But this will break once we enable `-Wwrite-strings`. Refactor the code stop splitting the fields with '\0' altogether. Instead, we can pass the length of the option name to `set_option()` and then use strncmp(3P) instead of strcmp(3P). Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- remote-curl.c | 53 ++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index cae98384da..d0f767df8e 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -58,9 +58,9 @@ struct options { static struct options options; static struct string_list cas_options = STRING_LIST_INIT_DUP; -static int set_option(const char *name, const char *value) +static int set_option(const char *name, size_t namelen, const char *value) { - if (!strcmp(name, "verbosity")) { + if (!strncmp(name, "verbosity", namelen)) { char *end; int v = strtol(value, &end, 10); if (value == end || *end) @@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value) options.verbosity = v; return 0; } - else if (!strcmp(name, "progress")) { + else if (!strncmp(name, "progress", namelen)) { if (!strcmp(value, "true")) options.progress = 1; else if (!strcmp(value, "false")) @@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else if (!strcmp(name, "depth")) { + else if (!strncmp(name, "depth", namelen)) { char *end; unsigned long v = strtoul(value, &end, 10); if (value == end || *end) @@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value) options.depth = v; return 0; } - else if (!strcmp(name, "deepen-since")) { + else if (!strncmp(name, "deepen-since", namelen)) { options.deepen_since = xstrdup(value); return 0; } - else if (!strcmp(name, "deepen-not")) { + else if (!strncmp(name, "deepen-not", namelen)) { string_list_append(&options.deepen_not, value); return 0; } - else if (!strcmp(name, "deepen-relative")) { + else if (!strncmp(name, "deepen-relative", namelen)) { if (!strcmp(value, "true")) options.deepen_relative = 1; else if (!strcmp(value, "false")) @@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else if (!strcmp(name, "followtags")) { + else if (!strncmp(name, "followtags", namelen)) { if (!strcmp(value, "true")) options.followtags = 1; else if (!strcmp(value, "false")) @@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else if (!strcmp(name, "dry-run")) { + else if (!strncmp(name, "dry-run", namelen)) { if (!strcmp(value, "true")) options.dry_run = 1; else if (!strcmp(value, "false")) @@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else if (!strcmp(name, "check-connectivity")) { + else if (!strncmp(name, "check-connectivity", namelen)) { if (!strcmp(value, "true")) options.check_self_contained_and_connected = 1; else if (!strcmp(value, "false")) @@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value) return -1; return 0; } - else if (!strcmp(name, "cas")) { + else if (!strncmp(name, "cas", namelen)) { struct strbuf val = STRBUF_INIT; strbuf_addstr(&val, "--force-with-lease="); if (*value != '"') @@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value) string_list_append(&cas_options, val.buf); strbuf_release(&val); return 0; - } else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) { + } else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) { if (!strcmp(value, "true")) options.force_if_includes = 1; else if (!strcmp(value, "false")) @@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "cloning")) { + } else if (!strncmp(name, "cloning", namelen)) { if (!strcmp(value, "true")) options.cloning = 1; else if (!strcmp(value, "false")) @@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "update-shallow")) { + } else if (!strncmp(name, "update-shallow", namelen)) { if (!strcmp(value, "true")) options.update_shallow = 1; else if (!strcmp(value, "false")) @@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "pushcert")) { + } else if (!strncmp(name, "pushcert", namelen)) { if (!strcmp(value, "true")) options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS; else if (!strcmp(value, "false")) @@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "atomic")) { + } else if (!strncmp(name, "atomic", namelen)) { if (!strcmp(value, "true")) options.atomic = 1; else if (!strcmp(value, "false")) @@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "push-option")) { + } else if (!strncmp(name, "push-option", namelen)) { if (*value != '"') string_list_append(&options.push_options, value); else { @@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value) strbuf_detach(&unquoted, NULL)); } return 0; - } else if (!strcmp(name, "family")) { + } else if (!strncmp(name, "family", namelen)) { if (!strcmp(value, "ipv4")) git_curl_ipresolve = CURL_IPRESOLVE_V4; else if (!strcmp(value, "ipv6")) @@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value) else return -1; return 0; - } else if (!strcmp(name, "from-promisor")) { + } else if (!strncmp(name, "from-promisor", namelen)) { options.from_promisor = 1; return 0; - } else if (!strcmp(name, "refetch")) { + } else if (!strncmp(name, "refetch", namelen)) { options.refetch = 1; return 0; - } else if (!strcmp(name, "filter")) { + } else if (!strncmp(name, "filter", namelen)) { options.filter = xstrdup(value); return 0; - } else if (!strcmp(name, "object-format")) { + } else if (!strncmp(name, "object-format", namelen)) { options.object_format = 1; if (strcmp(value, "true")) die(_("unknown value for object-format: %s"), value); @@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv) parse_push(&buf); } else if (skip_prefix(buf.buf, "option ", &arg)) { - char *value = strchr(arg, ' '); + const char *value = strchrnul(arg, ' '); + size_t arglen = value - arg; int result; - if (value) - *value++ = '\0'; + if (*value) + value++; /* skip over SP */ else value = "true"; - result = set_option(arg, value); + result = set_option(arg, arglen, value); if (!result) printf("ok\n"); else if (result < 0) From 844d190677216c0754287f14d9474a55adf606a4 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:07 +0200 Subject: [PATCH 39/46] revision: always store allocated strings in output encoding The `git_log_output_encoding` variable can be set via the `--encoding=` option. When doing so, we conditionally either assign it to the passed value, or if the value is "none" we assign it the empty string. Depending on which of the both code paths we pick though, the variable may end up being assigned either an allocated string or a string constant. This is somewhat risky and may easily lead to bugs when a different code path may want to reassign a new value to it, freeing the previous value. We already to this when parsing the "i18n.logoutputencoding" config in `git_default_i18n_config()`. But because the config is typically parsed before we parse command line options this has been fine so far. Regardless of that, safeguard the code such that the variable always contains an allocated string. While at it, also free the old value in case there was any to plug a potential memory leak. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- revision.c | 3 ++- t/t3900-i18n-commit.sh | 1 + t/t3901-i18n-patch.sh | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/revision.c b/revision.c index 7ddf0f151a..2ee6886078 100644 --- a/revision.c +++ b/revision.c @@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--invert-grep")) { revs->grep_filter.no_body_match = 1; } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) { + free(git_log_output_encoding); if (strcmp(optarg, "none")) git_log_output_encoding = xstrdup(optarg); else - git_log_output_encoding = ""; + git_log_output_encoding = xstrdup(""); return argcount; } else if (!strcmp(arg, "--reverse")) { revs->reverse ^= 1; diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index f27d09cfd9..db7b403bc1 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -5,6 +5,7 @@ test_description='commit and log output encodings' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh compare_with () { diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 4b37f78829..5f0b9afc3f 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_encoding () { From c77756015e66e5fc601cfb6a368c37ef1f94285a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:12 +0200 Subject: [PATCH 40/46] mailmap: always store allocated strings in mailmap blob Same as with the preceding commit, the `git_mailmap_blob` may sometimes contain an allocated string and sometimes it may contain a string constant. This is risky and can easily lead to bugs in case the variable is getting re-assigned, where the code may then try to free the previous value to avoid memory leaks. Safeguard the code by always storing allocated strings in the variable. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- mailmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mailmap.c b/mailmap.c index b2efe29b3d..3d1e092fef 100644 --- a/mailmap.c +++ b/mailmap.c @@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map) map->cmp = namemap_cmp; if (!git_mailmap_blob && is_bare_repository()) - git_mailmap_blob = "HEAD:.mailmap"; + git_mailmap_blob = xstrdup("HEAD:.mailmap"); if (!startup_info->have_repository || !is_bare_repository()) err |= read_mailmap_file(map, ".mailmap", From cea1ff7f1fb65daaf1059bdc0e5b65e2a2e3b89b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:17 +0200 Subject: [PATCH 41/46] imap-send: drop global `imap_server_conf` variable In "imap-send.c", we have a global `sturct imap_server_conf` variable that keeps track of the configuration of the IMAP server. This variable is being populated mostly via the Git configuration. Refactor the code to allocate the structure on the stack instead of having it globally. This change allows us to track its lifetime more closely. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- imap-send.c | 57 ++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/imap-send.c b/imap-send.c index 8b723b34a5..67a7a6c456 100644 --- a/imap-send.c +++ b/imap-send.c @@ -82,10 +82,6 @@ struct imap_server_conf { char *auth_method; }; -static struct imap_server_conf server = { - .ssl_verify = 1, -}; - struct imap_socket { int fd[2]; SSL *ssl; @@ -110,6 +106,7 @@ struct imap { }; struct imap_store { + const struct imap_server_conf *cfg; /* currently open mailbox */ const char *name; /* foreign! maybe preset? */ int uidvalidity; @@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret) #ifdef NO_OPENSSL static int ssl_socket_connect(struct imap_socket *sock UNUSED, - int use_tls_only UNUSED, - int verify UNUSED) + const struct imap_server_conf *cfg, + int use_tls_only UNUSED) { fprintf(stderr, "SSL requested but SSL support not compiled in\n"); return -1; @@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname) cname, hostname); } -static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify) +static int ssl_socket_connect(struct imap_socket *sock, + const struct imap_server_conf *cfg, + int use_tls_only) { #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) const SSL_METHOD *meth; @@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve if (use_tls_only) SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - if (verify) + if (cfg->ssl_verify) SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); if (!SSL_CTX_set_default_verify_paths(ctx)) { @@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve * OpenSSL does not document this function, but the implementation * returns 1 on success, 0 on failure after calling SSLerr(). */ - ret = SSL_set_tlsext_host_name(sock->ssl, server.host); + ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host); if (ret != 1) - warning("SSL_set_tlsext_host_name(%s) failed.", server.host); + warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host); #endif ret = SSL_connect(sock->ssl); @@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve return -1; } - if (verify) { + if (cfg->ssl_verify) { /* make sure the hostname matches that of the certificate */ cert = SSL_get_peer_certificate(sock->ssl); if (!cert) return error("unable to get peer certificate."); - if (verify_hostname(cert, server.host) < 0) + if (verify_hostname(cert, cfg->host) < 0) return -1; } @@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt) int ret; char *response; - response = cram(prompt, server.user, server.pass); + response = cram(prompt, ctx->cfg->user, ctx->cfg->pass); ret = socket_write(&ctx->imap->buf.sock, response, strlen(response)); if (ret != strlen(response)) @@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c CALLOC_ARRAY(ctx, 1); + ctx->cfg = srvc; ctx->imap = CALLOC_ARRAY(imap, 1); imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1; imap->in_progress_append = &imap->in_progress; @@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c imap->buf.sock.fd[1] = dup(s); if (srvc->use_ssl && - ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) { + ssl_socket_connect(&imap->buf.sock, srvc, 0)) { close(s); goto bail; } @@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c if (!srvc->use_ssl && CAP(STARTTLS)) { if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK) goto bail; - if (ssl_socket_connect(&imap->buf.sock, 1, - srvc->ssl_verify)) + if (ssl_socket_connect(&imap->buf.sock, srvc, 1)) goto bail; /* capabilities may have changed, so get the new capabilities */ if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK) @@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs) static int git_imap_config(const char *var, const char *val, const struct config_context *ctx, void *cb) { + struct imap_server_conf *cfg = cb; if (!strcmp("imap.sslverify", var)) - server.ssl_verify = git_config_bool(var, val); + cfg->ssl_verify = git_config_bool(var, val); else if (!strcmp("imap.preformattedhtml", var)) - server.use_html = git_config_bool(var, val); + cfg->use_html = git_config_bool(var, val); else if (!strcmp("imap.folder", var)) - return git_config_string(&server.folder, var, val); + return git_config_string(&cfg->folder, var, val); else if (!strcmp("imap.user", var)) - return git_config_string(&server.user, var, val); + return git_config_string(&cfg->user, var, val); else if (!strcmp("imap.pass", var)) - return git_config_string(&server.pass, var, val); + return git_config_string(&cfg->pass, var, val); else if (!strcmp("imap.tunnel", var)) - return git_config_string(&server.tunnel, var, val); + return git_config_string(&cfg->tunnel, var, val); else if (!strcmp("imap.authmethod", var)) - return git_config_string(&server.auth_method, var, val); + return git_config_string(&cfg->auth_method, var, val); else if (!strcmp("imap.port", var)) - server.port = git_config_int(var, val, ctx->kvi); + cfg->port = git_config_int(var, val, ctx->kvi); else if (!strcmp("imap.host", var)) { if (!val) { return config_error_nonbool(var); @@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val, val += 5; else if (starts_with(val, "imaps:")) { val += 6; - server.use_ssl = 1; + cfg->use_ssl = 1; } if (starts_with(val, "//")) val += 2; - server.host = xstrdup(val); + cfg->host = xstrdup(val); } } else return git_default_config(var, val, ctx, cb); @@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, int cmd_main(int argc, const char **argv) { + struct imap_server_conf server = { + .ssl_verify = 1, + }; struct strbuf all_msgs = STRBUF_INIT; int total; int nongit_ok; setup_git_directory_gently(&nongit_ok); - git_config(git_imap_config, NULL); + git_config(git_imap_config, &server); argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0); From 6d1f198f346fb218e06e730bac28609aa402a648 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:22 +0200 Subject: [PATCH 42/46] imap-send: fix leaking memory in `imap_server_conf` We never free any of the config strings that we populate into the `struct imap_server_conf`. Fix this by creating a common exit path where we can free resources. While at it, drop the unused member `imap_server_conf::name`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- imap-send.c | 63 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/imap-send.c b/imap-send.c index 67a7a6c456..da3e7ec17e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -69,7 +69,6 @@ static void imap_warn(const char *, ...); static char *next_arg(char **); struct imap_server_conf { - const char *name; char *tunnel; char *host; int port; @@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val, { struct imap_server_conf *cfg = cb; - if (!strcmp("imap.sslverify", var)) + if (!strcmp("imap.sslverify", var)) { cfg->ssl_verify = git_config_bool(var, val); - else if (!strcmp("imap.preformattedhtml", var)) + } else if (!strcmp("imap.preformattedhtml", var)) { cfg->use_html = git_config_bool(var, val); - else if (!strcmp("imap.folder", var)) + } else if (!strcmp("imap.folder", var)) { + FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->folder, var, val); - else if (!strcmp("imap.user", var)) + } else if (!strcmp("imap.user", var)) { + FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->user, var, val); - else if (!strcmp("imap.pass", var)) + } else if (!strcmp("imap.pass", var)) { + FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->pass, var, val); - else if (!strcmp("imap.tunnel", var)) + } else if (!strcmp("imap.tunnel", var)) { + FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->tunnel, var, val); - else if (!strcmp("imap.authmethod", var)) + } else if (!strcmp("imap.authmethod", var)) { + FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->auth_method, var, val); - else if (!strcmp("imap.port", var)) + } else if (!strcmp("imap.port", var)) { cfg->port = git_config_int(var, val, ctx->kvi); - else if (!strcmp("imap.host", var)) { + } else if (!strcmp("imap.host", var)) { if (!val) { return config_error_nonbool(var); } else { @@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val, val += 2; cfg->host = xstrdup(val); } - } else + } else { return git_default_config(var, val, ctx, cb); + } return 0; } @@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv) struct strbuf all_msgs = STRBUF_INIT; int total; int nongit_ok; + int ret; setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, &server); @@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv) if (!server.folder) { fprintf(stderr, "no imap store specified\n"); - return 1; + ret = 1; + goto out; } if (!server.host) { if (!server.tunnel) { fprintf(stderr, "no imap host specified\n"); - return 1; + ret = 1; + goto out; } - server.host = "tunnel"; + server.host = xstrdup("tunnel"); } /* read the messages */ if (strbuf_read(&all_msgs, 0, 0) < 0) { error_errno(_("could not read from stdin")); - return 1; + ret = 1; + goto out; } if (all_msgs.len == 0) { fprintf(stderr, "nothing to send\n"); - return 1; + ret = 1; + goto out; } total = count_messages(&all_msgs); if (!total) { fprintf(stderr, "no messages to send\n"); - return 1; + ret = 1; + goto out; } /* write it to the imap server */ if (server.tunnel) - return append_msgs_to_imap(&server, &all_msgs, total); - + ret = append_msgs_to_imap(&server, &all_msgs, total); #ifdef USE_CURL_FOR_IMAP_SEND - if (use_curl) - return curl_append_msgs_to_imap(&server, &all_msgs, total); + else if (use_curl) + ret = curl_append_msgs_to_imap(&server, &all_msgs, total); #endif + else + ret = append_msgs_to_imap(&server, &all_msgs, total); - return append_msgs_to_imap(&server, &all_msgs, total); +out: + free(server.tunnel); + free(server.host); + free(server.folder); + free(server.user); + free(server.pass); + free(server.auth_method); + return ret; } From 25a47ffac0c50d3bae4058e6327c7bd799240df9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:26 +0200 Subject: [PATCH 43/46] builtin/rebase: do not assign default backend to non-constant field The `struct rebase_options::default_backend` field is a non-constant string, but is being assigned a constant via `REBASE_OPTIONS_INIT`. Fix this by using `xstrdup()` to assign the variable and introduce a new function `rebase_options_release()` that releases memory held by the structure, including the newly-allocated variable. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/rebase.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 14d4f0a5e6..adc990b55e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -135,7 +135,7 @@ struct rebase_options { .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ .keep_empty = 1, \ - .default_backend = "merge", \ + .default_backend = xstrdup("merge"), \ .flags = REBASE_NO_QUIET, \ .git_am_opts = STRVEC_INIT, \ .exec = STRING_LIST_INIT_NODUP, \ @@ -151,6 +151,19 @@ struct rebase_options { .strategy_opts = STRING_LIST_INIT_NODUP,\ } +static void rebase_options_release(struct rebase_options *opts) +{ + free(opts->default_backend); + free(opts->reflog_action); + free(opts->head_name); + strvec_clear(&opts->git_am_opts); + free(opts->gpg_sign_opt); + string_list_clear(&opts->exec, 0); + free(opts->strategy); + string_list_clear(&opts->strategy_opts, 0); + strbuf_release(&opts->git_format_patch_opt); +} + static struct replay_opts get_replay_opts(const struct rebase_options *opts) { struct replay_opts replay = REPLAY_OPTS_INIT; @@ -796,6 +809,7 @@ static int rebase_config(const char *var, const char *value, } if (!strcmp(var, "rebase.backend")) { + FREE_AND_NULL(opts->default_backend); return git_config_string(&opts->default_backend, var, value); } @@ -1833,14 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) cleanup: strbuf_release(&buf); strbuf_release(&revisions); - free(options.reflog_action); - free(options.head_name); - strvec_clear(&options.git_am_opts); - free(options.gpg_sign_opt); - string_list_clear(&options.exec, 0); - free(options.strategy); - string_list_clear(&options.strategy_opts, 0); - strbuf_release(&options.git_format_patch_opt); + rebase_options_release(&options); free(squash_onto_name); free(keep_base_onto_name); return !!ret; From fc066767662ead1b279a20545ff02ab17d835f34 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:31 +0200 Subject: [PATCH 44/46] builtin/rebase: always store allocated string in `options.strategy` The `struct rebase_options::strategy` field is a `char *`, but we do end up assigning string constants to it in two cases: - When being passed a `--strategy=` option via the command line. - When being passed a strategy option via `--strategy-option=`, but not a strategy. This will cause warnings once we enable `-Wwrite-strings`. Ideally, we'd just convert the field to be a `const char *`. But we also assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we have to strdup(3P) into it. Instead, refactor the code to make sure that we only ever assign allocated strings to this field. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/rebase.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index adc990b55e..4506bae768 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1061,6 +1061,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = REBASE_OPTIONS_INIT; const char *branch_name; + const char *strategy_opt = NULL; int ret, flags, total_argc, in_progress = 0; int keep_base = 0; int ok_to_skip_pre_rebase = 0; @@ -1175,7 +1176,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, parse_opt_rebase_merges), OPT_BOOL(0, "fork-point", &options.fork_point, N_("use 'merge-base --fork-point' to refine upstream")), - OPT_STRING('s', "strategy", &options.strategy, + OPT_STRING('s', "strategy", &strategy_opt, N_("strategy"), N_("use the given merge strategy")), OPT_STRING_LIST('X', "strategy-option", &options.strategy_opts, N_("option"), @@ -1484,13 +1485,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } - if (options.strategy_opts.nr && !options.strategy) - options.strategy = "ort"; - - if (options.strategy) { - options.strategy = xstrdup(options.strategy); + if (strategy_opt) + options.strategy = xstrdup(strategy_opt); + else if (options.strategy_opts.nr && !options.strategy) + options.strategy = xstrdup("ort"); + if (options.strategy) imply_merge(&options, "--strategy"); - } if (options.root && !options.onto_name) imply_merge(&options, "--root without --onto"); From 71e01a0ebd50fed1d494e1b05374ec0977437248 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:35 +0200 Subject: [PATCH 45/46] builtin/merge: always store allocated strings in `pull_twohead` The `pull_twohead` configuration may sometimes contain an allocated string, and sometimes it may contain a string constant. Refactor this to instead always store an allocated string such that we can release its resources without risk. While at it, manage the lifetime of other config strings, as well. Note that we explicitly don't free `cleanup_arg` here. This is because the variable may be assigned a string constant via command line options. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/merge.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index daed2d4e1e..fb3eb15b89 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v, return 0; } - if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) + if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) { show_diffstat = git_config_bool(k, v); - else if (!strcmp(k, "merge.verifysignatures")) + } else if (!strcmp(k, "merge.verifysignatures")) { verify_signatures = git_config_bool(k, v); - else if (!strcmp(k, "pull.twohead")) + } else if (!strcmp(k, "pull.twohead")) { + FREE_AND_NULL(pull_twohead); return git_config_string(&pull_twohead, k, v); - else if (!strcmp(k, "pull.octopus")) + } else if (!strcmp(k, "pull.octopus")) { + FREE_AND_NULL(pull_octopus); return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "commit.cleanup")) + } else if (!strcmp(k, "commit.cleanup")) { return git_config_string(&cleanup_arg, k, v); - else if (!strcmp(k, "merge.ff")) { + } else if (!strcmp(k, "merge.ff")) { int boolval = git_parse_maybe_bool(v); if (0 <= boolval) { fast_forward = boolval ? FF_ALLOW : FF_NO; @@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!pull_twohead) { char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); if (default_strategy && !strcmp(default_strategy, "ort")) - pull_twohead = "ort"; + pull_twohead = xstrdup("ort"); } init_diff_ui_defaults(); @@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); free(branch_to_free); + free(pull_twohead); + free(pull_octopus); discard_index(the_repository->index); return ret; } From d66fe0726bfb3fb1e3665f7e64b160440007d98e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 7 Jun 2024 08:39:40 +0200 Subject: [PATCH 46/46] config.mak.dev: enable `-Wwrite-strings` warning Writing to string constants is undefined behaviour and must be avoided in C. Even so, the compiler does not help us with this by default because those constants are not in fact marked as `const`. This makes it rather easy to accidentally assign a constant to a non-const variable or field and then later on try to either free it or write to it. Enable `-Wwrite-strings` to catch such mistakes. With this warning enabled, the type of string constants is changed to `const char[]` and will thus cause compiler warnings when being assigned to non-const fields and variables. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- config.mak.dev | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mak.dev b/config.mak.dev index 981304727c..1ce4c70613 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith DEVELOPER_CFLAGS += -Wstrict-prototypes DEVELOPER_CFLAGS += -Wunused DEVELOPER_CFLAGS += -Wvla +DEVELOPER_CFLAGS += -Wwrite-strings DEVELOPER_CFLAGS += -fno-common ifneq ($(filter clang4,$(COMPILER_FEATURES)),)