{"thread":{"id":"61561","subject":"[PATCH 00/19] Compile with `-Wwrite-strings`","startedAt":"2024-05-29T12:44:09Z","lastAt":"2024-06-08T10:59:31Z","messageCount":205,"participants":["Patrick Steinhardt","Junio C Hamano","Eric Sunshine","Phillip Wood","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"495813","messageId":"cover.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":null,"subject":"[PATCH 00/19] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:01Z","receivedAt":"2024-05-29T12:44:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthere were some recent discussions about compiler warnings and how to\nstay on top of breaking changes in compilers in general [1] and about\nstring constants in particular [2]. This made me look into what kind of\nwarnings we should reasonably enable, which led me to the following\nlist of warnings that may be sensible:\n\n  - `-Wformat-nonliteral` to warn about non-constant strings being\n    passed as format string.\n\n  - `-Wwrite-strings` to warn about string constants being assigned to a\n    non-constant variable.\n\n  - `-Wredundant-decls` to warn about redundant declarations.\n\n  - `-Wconversion` to warn about implicit integer casts when they may\n    alter the value.\n\nThis patch series adapts our code to compile with `-Wwrite-strings`.\nThis option will change the type of string constants from `char []` to\n`const char []` such that it is now invalid to assign it to non-const\nvariables without a cast. The intent is to avoid undefined behaviour\nwhen accedintally writing to such strings and to avoid free'ing such a\nvariable.\n\nThere are quite some cases where we mishandle this. Oftentimes we just\ndidn't bother to free any memory at all, which made it a non-issue in\nthe first place. Other times we had some special logic that prevents\nwriting or freeing such strings. But in most cases it was an accident\nwaiting to happen.\n\nEven though the changes are quite invasive, I think that this is a step\ninto the right direction. Many of the constructs feel quite fragile, and\nmost of those get fixed in this series. Some others I just paper over,\nfor example when assigning to structures with global lifetime where we\nknow that they are never released at all.\n\nI also have a patch series cooking for `-Wredundant-decls`. But while\nthat warning detects some redundant declarations indeed, it creates a\nproblem with `extern char **environ`. There is no header for it and\nprograms are asked to declare it by themselves. But of course, some libc\nimplementations disagree and declare it. I haven't found a nice way to\nwork around this issue, but may send the patches that drop the redundant\ndeclarations nonetheless.\n\nThe other two warnings I haven't yet looked into.\n\nI ran some test jobs on both GitHub [3] and GitLab [4] to verify that\nthe result is sane.\n\nThanks!\n\nPatrick\n\n[1]: <xmqqbk5bim2n.fsf@gitster.g>\n[2]: <20240525043347.GA1895047@coredump.intra.peff.net>\n[3]: https://github.com/git/git/pull/1730\n[4]: https://gitlab.com/gitlab-org/git/-/pipelines/1310156791\n\nPatrick Steinhardt (19):\n  global: improve const correctness when assigning string constants\n  global: assign non-const strings as required\n  global: convert intentionally-leaking config strings to consts\n  compat/win32: fix const-correctness with string constants\n  reftable: improve const correctness when assigning string constants\n  refspec: remove global tag refspec structure\n  http: do not assign string constant to non-const field\n  line-log: always allocate the output prefix\n  object-file: make `buf` parameter of `index_mem()` a constant\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: adapt code to not assign string constants to non-const\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  16 +++--\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |   3 +-\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  15 +++-\n compat/mingw.c               |  25 +++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   7 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |   7 +-\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   9 ++-\n imap-send.c                  | 133 ++++++++++++++++++++---------------\n line-log.c                   |  21 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  17 ++---\n parse-options.h              |   2 +-\n pretty.c                     |   7 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |   5 +-\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics_test.c       |   4 +-\n reftable/block_test.c        |   2 +-\n reftable/merged_test.c       |  45 ++++++------\n reftable/readwrite_test.c    |  47 +++++++------\n reftable/record.c            |   6 +-\n reftable/stack_test.c        |  65 ++++++++---------\n remote-curl.c                |  58 ++++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 65 files changed, 410 insertions(+), 340 deletions(-)\n\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495814","messageId":"25c31e550fcecdda7510b259b271cd45d036f5d7.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 01/19] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:07Z","receivedAt":"2024-05-29T12:44:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/record.c            |  6 ++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 33 files changed, 86 insertions(+), 81 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495815","messageId":"51ee5660a1452797ac0a45819210141c57f3dcb9.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:11Z","receivedAt":"2024-05-29T12:44:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are several cases where we initialize non-const fields with string\nconstants. This is invalid and will cause warnings once we enable the\n`-Wwrite-strings` warning. Adapt those cases to instead use string\narrays.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c        | 3 ++-\n diff.c                  | 3 ++-\n entry.c                 | 7 ++++---\n ident.c                 | 9 +++++++--\n line-log.c              | 3 ++-\n object-file.c           | 3 ++-\n pretty.c                | 5 +++--\n refs/reftable-backend.c | 5 +++--\n 8 files changed, 25 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..0324a5d48d 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -494,11 +494,12 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n \tstruct refspec_item refspec;\n+\tchar refspec_str[] = \"refs/heads/*\";\n \n \tmemset(&refspec, 0, sizeof(refspec));\n \trefspec.force = 0;\n \trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n+\trefspec.src = refspec.dst = refspec_str;\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..1439a5a01d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7231,11 +7231,12 @@ size_t fill_textconv(struct repository *r,\n \t\t     struct diff_filespec *df,\n \t\t     char **outbuf)\n {\n+\tstatic char empty_str[] = \"\";\n \tsize_t size;\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = empty_str;\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..2fc06ac90c 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -175,6 +175,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \tstruct string_list_item *filter, *path;\n \tstruct progress *progress = NULL;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n+\tchar empty_str[] = \"\";\n \n \tif (!state->delayed_checkout)\n \t\treturn errs;\n@@ -189,7 +190,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = empty_str;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +200,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = empty_str;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +226,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = empty_str;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..df7aa42802 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,14 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tstatic char fallback_name[] = \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tstatic char fallback_gcos[] = \"Unknown\";\n+#endif\n+\n+\t\tfallback.pw_name = fallback_name;\n+#ifndef NO_GECOS_IN_PWENT\n+\t\tfallback.pw_gecos = fallback_gcos;\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..d9bf2c8120 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar empty_str[] = \"\";\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = empty_str;\n \t\tfile_parent.size = 0;\n \t}\n \ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..c9e374e57e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -282,12 +282,13 @@ static struct cached_object {\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n \n+static char empty_tree_buf[] = \"\";\n static struct cached_object empty_tree = {\n \t.oid = {\n \t\t.hash = EMPTY_TREE_SHA1_BIN_LITERAL,\n \t},\n \t.type = OBJ_TREE,\n-\t.buf = \"\",\n+\t.buf = empty_tree_buf,\n };\n \n static struct cached_object *find_cached_object(const struct object_id *oid)\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1a0030b32a 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1583,9 +1583,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\treturn 1;\n \tcase 'D':\n \t\t{\n+\t\t\tchar empty_str[] = \"\";\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = empty_str,\n+\t\t\t\t.suffix = empty_str,\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..1908e74dea 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1285,6 +1285,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tstruct strbuf errbuf = STRBUF_INIT;\n \tsize_t logs_nr = 0, logs_alloc = 0, i;\n \tconst char *committer_info;\n+\tchar head[] = \"HEAD\";\n \tint ret;\n \n \tcommitter_info = git_committer_info(0);\n@@ -1387,7 +1388,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = head;\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1463,7 +1464,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n \tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n+\t\tif (logs[i].refname == head)\n \t\t\tcontinue;\n \t\tlogs[i].refname = NULL;\n \t\treftable_log_record_release(&logs[i]);\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495816","messageId":"8f3decbb762916a536ec7a8d319c5903bd8f30c1.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 03/19] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:16Z","receivedAt":"2024-05-29T12:44:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and will never be free'd, it is fine\n    to treat the program as a quasi-constant.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never free'd.\n\nAll of these are intentionally kept alive and never free'd. Let's mark\nthe respective fields as `const char *` and cast away the constness when\nassigning those values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495817","messageId":"ba50e49f8640b5c8a175f740dd035a1e63faa410.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 04/19] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:21Z","receivedAt":"2024-05-29T12:44:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 15 +++++++++++++--\n compat/mingw.c    | 25 +++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 27 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..c3c9d65fac 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -1,6 +1,13 @@\n #include \"../git-compat-util.h\"\n #include \"../strbuf.h\"\n \n+/*\n+ * Both basename(3P) and dirname(3P) are mis-specified because they return a\n+ * non-constant pointer even though it is specified that they may return a\n+ * pointer to internal memory. This variable here is a result of that.\n+ */\n+static char current_directory[] = \".\";\n+\n /* Adapted from libiberty's basename.c.  */\n char *gitbasename (char *path)\n {\n@@ -10,7 +17,7 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\treturn current_directory;\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -33,8 +40,12 @@ char *gitdirname(char *path)\n \tchar *p = path, *slash = NULL, c;\n \tint dos_drive_prefix;\n \n+\t/*\n+\t * Same here, dirname(3P) is broken because it returns a non-constant\n+\t * pointer that may point to internal memory.\n+\t */\n \tif (!p)\n-\t\treturn \".\";\n+\t\treturn current_directory;\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..60f0986f76 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2257,6 +2257,7 @@ struct passwd *getpwuid(int uid)\n {\n \tstatic unsigned initialized;\n \tstatic char user_name[100];\n+\tstatic char unknown[] = \"unknown\";\n \tstatic struct passwd *p;\n \twchar_t buf[100];\n \tDWORD len;\n@@ -2279,7 +2280,7 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\tp->pw_gecos = unknown;\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2801,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2823,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495818","messageId":"dc5d85257eef747f2331638ed718b0f7a5bb1da2.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 05/19] reftable: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:25Z","receivedAt":"2024-05-29T12:44:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are many cases in the reftable tests where we assign string\nconstants to non-const fields. This is harmless because we know that\nthose fields are only used for reading access, but will break once we\nenable `-Wwrite-strings`. Add explicit casts to prepare for this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reftable/basics_test.c    |  4 +--\n reftable/block_test.c     |  2 +-\n reftable/merged_test.c    | 45 +++++++++++++--------------\n reftable/readwrite_test.c | 47 ++++++++++++++--------------\n reftable/stack_test.c     | 65 ++++++++++++++++++++-------------------\n 5 files changed, 82 insertions(+), 81 deletions(-)\n\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..af9209d535 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,8 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tchar *names[] = { (char *)\"a\", (char *)\"b\", NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..34c48c8091 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = (char *)\"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..6ab8ea2858 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n static void test_merged_between(void)\n {\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = (char *)\"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *)\"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -165,38 +165,38 @@ static void test_merged(void)\n {\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *)\"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *)\"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *)\"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *)\"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = (char *)\"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,46 +291,46 @@ static void test_merged_logs(void)\n {\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = (char *)\"jane doe\",\n+\t\t\t\t.email = (char *)\"jane@invalid\",\n+\t\t\t\t.message = (char *)\"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = (char *)\"jane doe\",\n+\t\t\t\t.email = (char *)\"jane@invalid\",\n+\t\t\t\t.message = (char *)\"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = (char *)\"jane doe\",\n+\t\t\t\t.email = (char *)\"jane@invalid\",\n+\t\t\t\t.message = (char *)\"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -404,9 +404,8 @@ static void test_default_write_opts(void)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n-\n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *)\"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..72f66507ba 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = (char *)\"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -117,17 +117,18 @@ static void test_log_buffer_size(void)\n \t};\n \tint err;\n \tint i;\n-\tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n-\t\t\t.update_index = 0xa,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n-\t\t\t\t\t   .tz_offset = 100,\n-\t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n-\t\t\t\t   } } };\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)\"refs/heads/master\",\n+\t\t.update_index = 0xa,\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value.update = {\n+\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n+\t\t\t.email = (char *)\"hanwen@google.com\",\n+\t\t\t.tz_offset = 100,\n+\t\t\t.time = 0x5e430672,\n+\t\t\t.message = (char *)\"commit: 9\\n\",\n+\t\t},\n+\t};\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n@@ -156,15 +157,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *)\"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *)\"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -293,14 +294,14 @@ static void test_log_zlib_corruption(void)\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = (char *)\"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = (char *)\"My Name\",\n+\t\t\t\t.email = (char *)\"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -728,7 +729,7 @@ static void test_write_empty_key(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = (char *)\"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -752,18 +753,18 @@ static void test_write_key_order(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *)\"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *)\"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *)\"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *)\"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..0994f6bcda 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,13 +116,14 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n-\n-\tEXPECT(names_equal(a, a));\n-\tEXPECT(!names_equal(a, b));\n-\tEXPECT(!names_equal(a, c));\n+\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n+\tchar *abc[] = { a, b, c, NULL };\n+\tchar *abd[] = { a, b, d, NULL };\n+\tchar *ab[] = { a, b, NULL };\n+\n+\tEXPECT(names_equal(abc, abc));\n+\tEXPECT(!names_equal(abc, abd));\n+\tEXPECT(!names_equal(abc, ab));\n }\n \n static int write_test_ref(struct reftable_writer *wr, void *arg)\n@@ -156,10 +157,10 @@ static void test_reftable_stack_add_one(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *)\"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -216,16 +217,16 @@ static void test_reftable_stack_uptodate(void)\n \n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *)\"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = (char *)\"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \n \n@@ -264,10 +265,10 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_addition *add = NULL;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *)\"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,7 +314,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *)\"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -356,7 +357,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *)\"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,16 +410,16 @@ static void test_reftable_stack_update_index_check(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = (char *)\"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = (char *)\"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *)\"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -561,7 +562,7 @@ static void test_reftable_stack_log_normalize(void)\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = (char *)\"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +583,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = (char *)\"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = (char *)\"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +595,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = (char *)\"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -697,9 +698,9 @@ static void test_reftable_stack_hash_id(void)\n \tint err;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *)\"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = (char *)\"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -879,7 +880,7 @@ static void test_reftable_stack_auto_compaction(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *)\"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -913,7 +914,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *)\"master\",\n \t\t};\n \n \t\t/*\n@@ -964,7 +965,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *)\"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1014,7 +1015,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *)\"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495819","messageId":"0eaa73c109198b332e348921bc0a0dc49c687f98.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 06/19] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:30Z","receivedAt":"2024-05-29T12:44:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization fo the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495820","messageId":"03b13c449b52ecfc845e8ffb0dd69fe67d50651c.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 07/19] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:35Z","receivedAt":"2024-05-29T12:44:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495821","messageId":"699eeae92c0f58032fc76af68521d8cc60f12031.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 08/19] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:39Z","receivedAt":"2024-05-29T12:44:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex d9bf2c8120..9a298209d0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495822","messageId":"6cbb8444a6dabb883ac3b0e450c0bfbd17b58bea.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 09/19] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:44Z","receivedAt":"2024-05-29T12:44:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex c9e374e57e..46ea00ac46 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2483,12 +2483,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2498,11 +2499,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2519,8 +2519,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495823","messageId":"c07b27bbb44cc62f8dc805a523da66c0260955b6.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 10/19] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:49Z","receivedAt":"2024-05-29T12:44:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495824","messageId":"3cd28ae38c46e09607c06cc06868ad7692082d57.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 11/19] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:54Z","receivedAt":"2024-05-29T12:44:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495825","messageId":"00b4a7dbbcdfd8a0f7b4beb4ecaf272257838460.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:44:59Z","receivedAt":"2024-05-29T12:45:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 58 +++++++++++++++++++++++++++------------------------\n 1 file changed, 31 insertions(+), 27 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..ac185d4f88 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,19 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchr(arg, ' ');\n+\t\t\tsize_t arglen;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n-\t\t\telse\n+\t\t\tif (value) {\n+\t\t\t\targlen = value - arg;\n+\t\t\t\tvalue++;\n+\t\t\t} else {\n+\t\t\t\targlen = strlen(arg);\n \t\t\t\tvalue = \"true\";\n+\t\t\t}\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495826","messageId":"68a7d24e4a715eaf60414373636860215d27e643.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 13/19] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:04Z","receivedAt":"2024-05-29T12:45:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495827","messageId":"0e393fa6a708fc29fe666b75d89ed66cc69ec634.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 14/19] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:09Z","receivedAt":"2024-05-29T12:45:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495828","messageId":"18ba9f7b3b8bc81539cd69389e9b60f953f2ae5c.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 15/19] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:14Z","receivedAt":"2024-05-29T12:45:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495829","messageId":"357d69fa8b538baba23cd110b8d16174234a58dc.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:18Z","receivedAt":"2024-05-29T12:45:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused variables `imap_server_conf::name` and\n`nongit_ok`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 68 +++++++++++++++++++++++++++++++++--------------------\n 1 file changed, 43 insertions(+), 25 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..a6fb33806c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1502,9 +1507,9 @@ int cmd_main(int argc, const char **argv)\n \t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n-\tint nongit_ok;\n+\tint ret;\n \n-\tsetup_git_directory_gently(&nongit_ok);\n+\tsetup_git_directory_gently(NULL);\n \tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n@@ -1529,42 +1534,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495830","messageId":"16d3d28243a0480c929ae3740db92ade238dd325.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:23Z","receivedAt":"2024-05-29T12:45:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When computing the rebase strategy we temporarily assign a string\nconstant to `options.strategy` before we call `xstrdup()` on it.\nFurthermore, the default backend is being assigned a string constant via\n`REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable\n`-Wwrite-strings`.\n\nAdapt the code such that we only store allocated strings in those\nvariables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..b05c3b6be3 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -135,7 +135,6 @@ struct rebase_options {\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n@@ -796,6 +795,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1471,12 +1471,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\telse\n+\t\toptions.strategy = xstrdup_or_null(options.strategy);\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n@@ -1522,7 +1521,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (options.type == REBASE_UNSPECIFIED) {\n-\t\tif (!strcmp(options.default_backend, \"merge\"))\n+\t\tif (!options.default_backend)\n+\t\t\toptions.type = REBASE_MERGE;\n+\t\telse if (!strcmp(options.default_backend, \"merge\"))\n \t\t\toptions.type = REBASE_MERGE;\n \t\telse if (!strcmp(options.default_backend, \"apply\"))\n \t\t\toptions.type = REBASE_APPLY;\n@@ -1833,6 +1834,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n+\tfree(options.default_backend);\n \tfree(options.reflog_action);\n \tfree(options.head_name);\n \tstrvec_clear(&options.git_am_opts);\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495831","messageId":"129482dbaa64b509f103914e8b3d6bdd14f47b62.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 18/19] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:30Z","receivedAt":"2024-05-29T12:45:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495832","messageId":"37e7aaed97df0b902631e76ba82ee7c6473293ad.1716983704.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH 19/19] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:45:35Z","receivedAt":"2024-05-29T12:45:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495833","messageId":"Zlck_1vQo8FokpZI@tanuki","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"Re: [PATCH 00/19] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-29T12:52:15Z","receivedAt":"2024-05-29T12:52:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 02:44:01PM +0200, Patrick Steinhardt wrote:\n> Hi,\n> \n> there were some recent discussions about compiler warnings and how to\n> stay on top of breaking changes in compilers in general [1] and about\n> string constants in particular [2]. This made me look into what kind of\n> warnings we should reasonably enable, which led me to the following\n> list of warnings that may be sensible:\n> \n>   - `-Wformat-nonliteral` to warn about non-constant strings being\n>     passed as format string.\n> \n>   - `-Wwrite-strings` to warn about string constants being assigned to a\n>     non-constant variable.\n> \n>   - `-Wredundant-decls` to warn about redundant declarations.\n> \n>   - `-Wconversion` to warn about implicit integer casts when they may\n>     alter the value.\n> \n> This patch series adapts our code to compile with `-Wwrite-strings`.\n> This option will change the type of string constants from `char []` to\n> `const char []` such that it is now invalid to assign it to non-const\n> variables without a cast. The intent is to avoid undefined behaviour\n> when accedintally writing to such strings and to avoid free'ing such a\n> variable.\n> \n> There are quite some cases where we mishandle this. Oftentimes we just\n> didn't bother to free any memory at all, which made it a non-issue in\n> the first place. Other times we had some special logic that prevents\n> writing or freeing such strings. But in most cases it was an accident\n> waiting to happen.\n> \n> Even though the changes are quite invasive, I think that this is a step\n> into the right direction. Many of the constructs feel quite fragile, and\n> most of those get fixed in this series. Some others I just paper over,\n> for example when assigning to structures with global lifetime where we\n> know that they are never released at all.\n> \n> I also have a patch series cooking for `-Wredundant-decls`. But while\n> that warning detects some redundant declarations indeed, it creates a\n> problem with `extern char **environ`. There is no header for it and\n> programs are asked to declare it by themselves. But of course, some libc\n> implementations disagree and declare it. I haven't found a nice way to\n> work around this issue, but may send the patches that drop the redundant\n> declarations nonetheless.\n> \n> The other two warnings I haven't yet looked into.\n> \n> I ran some test jobs on both GitHub [3] and GitLab [4] to verify that\n> the result is sane.\n> \n> Thanks!\n\nI forgot to say that this is based on top of 3a57aa566a (The eighth\nbatch, 2024-05-28) with ps/leakfixes merged into it at ebdbefa4fe\n(builtin/mv: fix leaks for submodule gitfile paths, 2024-05-27).\n\nPatrick\n"},{"id":"495842","messageId":"xmqqzfs8y2pb.fsf@gitster.g","threadId":"61561","inReplyTo":"25c31e550fcecdda7510b259b271cd45d036f5d7.1716983704.git.ps@pks.im","subject":"Re: [PATCH 01/19] global: improve const correctness when assigning string constants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T16:58:56Z","receivedAt":"2024-05-29T16:58:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We're about to enable `-Wwrite-strings`, which changes the type of\n> string constants to `const char[]`. Fix various sites where we assign\n> such constants to non-const variables.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n\nThis is a noisy change, and it is kind of surprising that ...\n\n> diff --git a/builtin/bisect.c b/builtin/bisect.c\n> index a58432b9d9..dabce9b542 100644\n> --- a/builtin/bisect.c\n> +++ b/builtin/bisect.c\n> @@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n>  \treturn bisect_clean_state();\n>  }\n>  \n> -static void log_commit(FILE *fp, char *fmt, const char *state,\n> +static void log_commit(FILE *fp,\n> +\t\t       const char *fmt, const char *state,\n>  \t\t       struct commit *commit)\n>  {\n>  \tstruct pretty_print_context pp = {0};\n\n... a change like this does not require any other change to the\ncode inside the function (e.g., by making further cascading changes\nto what the function calls).\n\nBut applying this step alone and building would not give us any\nconstness warning/error from the compiler, so the result looks good\nto the compiler, I guess?\n\nThanks.\n\n"},{"id":"495849","messageId":"xmqqplt4y1gk.fsf@gitster.g","threadId":"61561","inReplyTo":"51ee5660a1452797ac0a45819210141c57f3dcb9.1716983704.git.ps@pks.im","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T17:25:47Z","receivedAt":"2024-05-29T17:25:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +\tchar refspec_str[] = \"refs/heads/*\";\n>  \n>  \tmemset(&refspec, 0, sizeof(refspec));\n>  \trefspec.force = 0;\n>  \trefspec.pattern = 1;\n> -\trefspec.src = refspec.dst = \"refs/heads/*\";\n> +\trefspec.src = refspec.dst = refspec_str;\n>  \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n>  \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n>  \t\t\t\t    fetch_map, 1);\n\nI have no objections to changes along this line, but this makes me\nwonder if we somehow have ways to ensure that after everything is\ndone, refspec_str[], empty_str[], and the like in other hunks remain\nto have their initial contents.  Stepping back a bit, without\napplying this series, if we told the compiler to store these\nconstant strings in read-only segment, any attempt to write into\nrefspec.src or refspec.dst string would have been caught at runtime\nas an error.  With the patch, that would no longer work.  The piece\nof memory in refspec_str[] is a fair game to be overwritten by\nanybody.\n\nWhich makes me wonder why these refspec.src and refspec.dst members\nare not \"const char *\" pointers in the first place?  Obviously we do\nnot expect \"refs/heads/*\" to be overwritten after storing the pointer\nto it in these members and making the get_fetch_map() call.\n\n"},{"id":"495850","messageId":"xmqqle3sy1cq.fsf@gitster.g","threadId":"61561","inReplyTo":"8f3decbb762916a536ec7a8d319c5903bd8f30c1.1716983704.git.ps@pks.im","subject":"Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T17:28:05Z","receivedAt":"2024-05-29T17:28:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There are multiple cases where we intentionally leak config strings:\n>\n>   - `struct gpg_format` is used to track programs that can be used for\n>     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n>     user can override the commands via several config variables. As the\n>     array is populated once, only, and will never be free'd, it is fine\n>     to treat the program as a quasi-constant.\n>\n>   - `struct ll_merge_driver` is used to track merge drivers. Same as\n>     with the GPG format, these drivers are populated once and then\n>     reused. Its data is never free'd, either.\n>\n>   - `struct userdiff_funcname` and `struct userdiff_driver` can be\n>     configured via `diff.<driver>.*` to add additional drivers. Again,\n>     these have a global lifetime and are never free'd.\n>\n> All of these are intentionally kept alive and never free'd. Let's mark\n> the respective fields as `const char *` and cast away the constness when\n> assigning those values.\n\nIt is not unclear where the linkage between \"not freed\" and \"must be\nconst\" comes from.  What am I missing?\n\nThanks.\n"},{"id":"495851","messageId":"xmqqed9ky0mk.fsf@gitster.g","threadId":"61561","inReplyTo":"dc5d85257eef747f2331638ed718b0f7a5bb1da2.1716983704.git.ps@pks.im","subject":"Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T17:43:47Z","receivedAt":"2024-05-29T17:43:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --git a/reftable/basics_test.c b/reftable/basics_test.c\n> index 997c4d9e01..af9209d535 100644\n> --- a/reftable/basics_test.c\n> +++ b/reftable/basics_test.c\n> @@ -58,8 +58,8 @@ static void test_binsearch(void)\n>  \n>  static void test_names_length(void)\n>  {\n> -\tchar *a[] = { \"a\", \"b\", NULL };\n> -\tEXPECT(names_length(a) == 2);\n> +\tchar *names[] = { (char *)\"a\", (char *)\"b\", NULL };\n> +\tEXPECT(names_length(names) == 2);\n>  }\n\nI would have preferred to see this kind of rewrite more than\nseparate and clearly writable variables that are initialied with the\nconstant contents e.g. branches[] = \"refs/heads/*\", we saw in\nearlier steps.  Wouldn't that approach, combined with making the\nliteral constants stored in read-only segment to trigger runtime\nfailure when a bug causes the \"unfortunately non-const\" variables\nto be written, give us a better result?\n"},{"id":"495852","messageId":"xmqqa5k8y0fy.fsf@gitster.g","threadId":"61561","inReplyTo":"0eaa73c109198b332e348921bc0a0dc49c687f98.1716983704.git.ps@pks.im","subject":"Re: [PATCH 06/19] refspec: remove global tag refspec structure","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T17:47:45Z","receivedAt":"2024-05-29T17:47:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n> ...\n> -static struct refspec_item s_tag_refspec = {\n> -\t.force = 0,\n> -\t.pattern = 1,\n> -\t.matching = 0,\n> -\t.exact_sha1 = 0,\n> -\t.negative = 0,\n> -\t.src = \"refs/tags/*\",\n> -\t.dst = \"refs/tags/*\",\n> -};\n\nRegardless of the constness issue, replacing these hardcoded\ninitializer values that is an accident waiting to happen with a call\nto refspec_item_init() is very much welcomed.\n\nUnless parse_refspec() is an immensely high cost operation, which it\nisn't.\n"},{"id":"495855","messageId":"xmqqy17swgop.fsf@gitster.g","threadId":"61561","inReplyTo":"03b13c449b52ecfc845e8ffb0dd69fe67d50651c.1716983704.git.ps@pks.im","subject":"Re: [PATCH 07/19] http: do not assign string constant to non-const field","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T19:39:50Z","receivedAt":"2024-05-29T19:39:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In `write_accept_language()`, we put all acceptable languages into an\n> array. While all entries in that array are allocated strings, the final\n> entry in that array is a string constant. This is fine because we\n> explicitly skip over the last entry when freeing the array, but will\n> cause warnings once we enable `-Wwrite-strings`.\n>\n> Adapt the code to also allocate the final entry.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  http.c | 5 ++---\n>  1 file changed, 2 insertions(+), 3 deletions(-)\n>\n> diff --git a/http.c b/http.c\n> index 67cc47d28f..2dea2d03da 100644\n> --- a/http.c\n> +++ b/http.c\n> @@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n>  \n>  \t\t/* add '*' */\n>  \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n> -\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n> +\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n>  \n>  \t\t/* compute decimal_places */\n>  \t\tfor (max_q = 1, decimal_places = 0;\n> @@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n>  \t\t}\n>  \t}\n>  \n> -\t/* free language tags -- last one is a static '*' */\n> -\tfor (i = 0; i < num_langs - 1; i++)\n> +\tfor (i = 0; i < num_langs; i++)\n>  \t\tfree(language_tags[i]);\n>  \tfree(language_tags);\n>  }\n\nMakes sense, especially that this is done only once per process.\n\n"},{"id":"495856","messageId":"xmqqr0dkwg4k.fsf@gitster.g","threadId":"61561","inReplyTo":"699eeae92c0f58032fc76af68521d8cc60f12031.1716983704.git.ps@pks.im","subject":"Re: [PATCH 08/19] line-log: always allocate the output prefix","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T19:51:55Z","receivedAt":"2024-05-29T19:51:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The returned string by `output_prefix()` is sometimes a string constant\n> and sometimes an allocated string. This has been fine until now because\n> we always leak the allocated strings, and thus we never tried to free\n> the string constant.\n>\n> Fix the code to always return an allocated string and free the returned\n> value at all callsites.\n\nYay!\n\nThis leak always has bothered me, as it is not just once per process\ninvocation, but once per each commit that is shown.\n"},{"id":"495857","messageId":"xmqqmso8wfnu.fsf@gitster.g","threadId":"61561","inReplyTo":"6cbb8444a6dabb883ac3b0e450c0bfbd17b58bea.1716983704.git.ps@pks.im","subject":"Re: [PATCH 09/19] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T20:01:57Z","receivedAt":"2024-05-29T20:02:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The `buf` parameter of `index_mem()` is a non-constant string. This will\n> break once we enable `-Wwrite-strings` because we also pass constants\n> from at least one callsite.\n>\n> Adapt the parameter to be a constant. As we cannot free the buffer\n> without casting now, this also requires us to move the lifetime of the\n> nested buffer around.\n\nMakes sense.\n"},{"id":"495858","messageId":"xmqq5xuwweqs.fsf@gitster.g","threadId":"61561","inReplyTo":"00b4a7dbbcdfd8a0f7b4beb4ecaf272257838460.1716983704.git.ps@pks.im","subject":"Re: [PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T20:21:47Z","receivedAt":"2024-05-29T20:21:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>  \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n> +\t\t\tconst char *value = strchr(arg, ' ');\n> +\t\t\tsize_t arglen;\n>  \t\t\tint result;\n>  \n> +\t\t\tif (value) {\n> +\t\t\t\targlen = value - arg;\n> +\t\t\t\tvalue++;\n> +\t\t\t} else {\n> +\t\t\t\targlen = strlen(arg);\n>  \t\t\t\tvalue = \"true\";\n> +\t\t\t}\n\nThere is a micro optimization opportunity here.\n\n\tconst char *value = strchrnul(arg, ' ');\n\tsize_t arglen = value - arg;\n\n\tif (*value)\n\t\tvalue++; /* skip over SP */\n\telse\n\t\tvalue = \"true\";\n\nBut other than that, very cleanly done.\n"},{"id":"495859","messageId":"xmqqzfs8v047.fsf@gitster.g","threadId":"61561","inReplyTo":"68a7d24e4a715eaf60414373636860215d27e643.1716983704.git.ps@pks.im","subject":"Re: [PATCH 13/19] revision: always store allocated strings in output encoding","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T20:23:04Z","receivedAt":"2024-05-29T20:23:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The `git_log_output_encoding` variable can be set via the `--encoding=`\n> option. When doing so, we conditionally either assign it to the passed\n> value, or if the value is \"none\" we assign it the empty string.\n> Depending on which of the both code paths we pick though, the variable\n> may end up being assigned either an allocated string or a string\n> constant.\n>\n> This is somewhat risky and may easily lead to bugs when a different code\n> path may want to reassign a new value to it, freeing the previous value.\n> We already to this when parsing the \"i18n.logoutputencoding\" config in\n> `git_default_i18n_config()`. But because the config is typically parsed\n> before we parse command line options this has been fine so far.\n>\n> Regardless of that, safeguard the code such that the variable always\n> contains an allocated string. While at it, also free the old value in\n> case there was any to plug a potential memory leak.\n\nNice.  Now the thing always has to be freed once we are done.\nConsistency is good.\n"},{"id":"495860","messageId":"xmqqle3suymm.fsf@gitster.g","threadId":"61561","inReplyTo":"357d69fa8b538baba23cd110b8d16174234a58dc.1716983704.git.ps@pks.im","subject":"Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T20:55:13Z","receivedAt":"2024-05-29T20:55:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We never free any of the config strings that we populate into the\n> `struct imap_server_conf`. Fix this by creating a common exit path where\n> we can free resources.\n\nThis is more like the previous step got rid of the anchor that made\nthese strings reachable, so we need to turn around to free them,\nwhich is sort-of Meh, especially given that the leaked pieces of\nmemory are small and very much bounded.\n\nThe main benefit of this change is to allow us prepare on the\nconstness change in the other (read: API this thing uses from\nelsewhere) parts of the system, which is a very worthy goal.\n\n> While at it, drop the unused variables `imap_server_conf::name` and\n> `nongit_ok`.\n\nThe removal of the .name member may be correct, but I suspect the\nchange to nongit_ok is a change in behaviour, and it could even be a\nregression.\n\n> -\tsetup_git_directory_gently(&nongit_ok);\n> +\tsetup_git_directory_gently(NULL);\n\nThe general idea behind &nongit_ok is that\n\n - Usually setup_git_directory_gently() dies if NULL is passed\n   instead of a pointer to &nongit_ok.  Most of the Git command\n   wants to make sure they have a repository to operate on, so this\n   is a reasonable default behaviour.\n\n - Some commands would want to work also without having any\n   repository, possibly with limited capability (e.g., \"git apply\"\n   may want to work as a better \"GNU patch\", but obviously it cannot\n   do \"git apply --binary --3way\" without having the object\n   database).  They tell setup_git_directory_gently() not to die\n   when outside a repository by passing a pointer to &nongit_ok, and\n   instead tell if we are in a repository by storing 0/1 in it.\n\nThe idea is that a command that is willing to work outside a\nrepository can disable selected features based on what it sees in\nnongit_ok.  In the case of \"imap-send\", there is no such features\nthat it needs to special case, perhaps because everything it does is\nsupposed to work outside a repository?\n\nSo the short version of what worries me in this change is that we\nused to be able to operate without having a repository at all, but\nnow we would barf if run outside a repository, no?\n\nThanks.\n"},{"id":"495861","messageId":"xmqqh6eguycr.fsf@gitster.g","threadId":"61561","inReplyTo":"16d3d28243a0480c929ae3740db92ade238dd325.1716983704.git.ps@pks.im","subject":"Re: [PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-29T21:01:08Z","receivedAt":"2024-05-29T21:01:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When computing the rebase strategy we temporarily assign a string\n> constant to `options.strategy` before we call `xstrdup()` on it.\n> Furthermore, the default backend is being assigned a string constant via\n> `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable\n> `-Wwrite-strings`.\n>\n> Adapt the code such that we only store allocated strings in those\n> variables.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/rebase.c | 16 +++++++++-------\n>  1 file changed, 9 insertions(+), 7 deletions(-)\n\nOne gripe I have in this change is that it used to be crystal clear\nwhat the hardcoded default was (i.e. written in the initialization\ndata), but now you have to follow the program flow to see what the\nhardcoded default is.\n\n>  \tif (options.type == REBASE_UNSPECIFIED) {\n> -\t\tif (!strcmp(options.default_backend, \"merge\"))\n> +\t\tif (!options.default_backend)\n> +\t\t\toptions.type = REBASE_MERGE;\n> +\t\telse if (!strcmp(options.default_backend, \"merge\"))\n>  \t\t\toptions.type = REBASE_MERGE;\n>  \t\telse if (!strcmp(options.default_backend, \"apply\"))\n>  \t\t\toptions.type = REBASE_APPLY;\n"},{"id":"495917","messageId":"ZlhjE744pNJiyv-k@tanuki","threadId":"61561","inReplyTo":"xmqqzfs8y2pb.fsf@gitster.g","subject":"Re: [PATCH 01/19] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:29:34Z","receivedAt":"2024-05-30T11:29:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 09:58:56AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > We're about to enable `-Wwrite-strings`, which changes the type of\n> > string constants to `const char[]`. Fix various sites where we assign\n> > such constants to non-const variables.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> \n> This is a noisy change, and it is kind of surprising that ...\n> \n> > diff --git a/builtin/bisect.c b/builtin/bisect.c\n> > index a58432b9d9..dabce9b542 100644\n> > --- a/builtin/bisect.c\n> > +++ b/builtin/bisect.c\n> > @@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n> >  \treturn bisect_clean_state();\n> >  }\n> >  \n> > -static void log_commit(FILE *fp, char *fmt, const char *state,\n> > +static void log_commit(FILE *fp,\n> > +\t\t       const char *fmt, const char *state,\n> >  \t\t       struct commit *commit)\n> >  {\n> >  \tstruct pretty_print_context pp = {0};\n> \n> ... a change like this does not require any other change to the\n> code inside the function (e.g., by making further cascading changes\n> to what the function calls).\n> \n> But applying this step alone and building would not give us any\n> constness warning/error from the compiler, so the result looks good\n> to the compiler, I guess?\n\nYup, any intermediate commit builds with DEVELOPER=1.\n\nPatrick\n"},{"id":"495918","messageId":"ZlhjN7K8JKYz6Opd@tanuki","threadId":"61561","inReplyTo":"xmqqplt4y1gk.fsf@gitster.g","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:29:59Z","receivedAt":"2024-05-30T11:30:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 10:25:47AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > +\tchar refspec_str[] = \"refs/heads/*\";\n> >  \n> >  \tmemset(&refspec, 0, sizeof(refspec));\n> >  \trefspec.force = 0;\n> >  \trefspec.pattern = 1;\n> > -\trefspec.src = refspec.dst = \"refs/heads/*\";\n> > +\trefspec.src = refspec.dst = refspec_str;\n> >  \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n> >  \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n> >  \t\t\t\t    fetch_map, 1);\n> \n> I have no objections to changes along this line, but this makes me\n> wonder if we somehow have ways to ensure that after everything is\n> done, refspec_str[], empty_str[], and the like in other hunks remain\n> to have their initial contents.  Stepping back a bit, without\n> applying this series, if we told the compiler to store these\n> constant strings in read-only segment, any attempt to write into\n> refspec.src or refspec.dst string would have been caught at runtime\n> as an error.  With the patch, that would no longer work.  The piece\n> of memory in refspec_str[] is a fair game to be overwritten by\n> anybody.\n\nTrue. Ideally, we wouldn't have to do these acrobatics here in the first\nplace. That would likely require quite a lot more work and go beyond the\nscope of this patch series.\n\n> Which makes me wonder why these refspec.src and refspec.dst members\n> are not \"const char *\" pointers in the first place?  Obviously we do\n> not expect \"refs/heads/*\" to be overwritten after storing the pointer\n> to it in these members and making the get_fetch_map() call.\n\nWell, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It\ndoes weird stuff where it writes the result into either `src` or `dst`\ndepending on which of these fields is provided by the caller. Which\nmeans that one of the fields would typically be a constant, whereas the\nother one will be allocated.\n\nReally, once you start looking you find all kinds of weird interfaces\nwhere we cannot quite make up our mind whether fields are controlled by\nthe caller or by the callee. This patch series certainly surfaces quite\nsome places that left me scratching my head.\n\nPatrick\n"},{"id":"495919","messageId":"ZlhjRtRwxlnchOaV@tanuki","threadId":"61561","inReplyTo":"xmqqle3sy1cq.fsf@gitster.g","subject":"Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:30:14Z","receivedAt":"2024-05-30T11:30:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 10:28:05AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > There are multiple cases where we intentionally leak config strings:\n> >\n> >   - `struct gpg_format` is used to track programs that can be used for\n> >     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n> >     user can override the commands via several config variables. As the\n> >     array is populated once, only, and will never be free'd, it is fine\n> >     to treat the program as a quasi-constant.\n> >\n> >   - `struct ll_merge_driver` is used to track merge drivers. Same as\n> >     with the GPG format, these drivers are populated once and then\n> >     reused. Its data is never free'd, either.\n> >\n> >   - `struct userdiff_funcname` and `struct userdiff_driver` can be\n> >     configured via `diff.<driver>.*` to add additional drivers. Again,\n> >     these have a global lifetime and are never free'd.\n> >\n> > All of these are intentionally kept alive and never free'd. Let's mark\n> > the respective fields as `const char *` and cast away the constness when\n> > assigning those values.\n> \n> It is not unclear where the linkage between \"not freed\" and \"must be\n> const\" comes from.  What am I missing?\n\nIt comes from `-Wwrite-strings`, which will mark string constants as\n`const char *`. This will cause warnings in all of the above cases\nbecause the fields are being assigned constants, but those fields are\ncurrently `char *`. Will clarify.\n\nPatrick\n"},{"id":"495920","messageId":"ZlhjVIEld7_T0q-M@tanuki","threadId":"61561","inReplyTo":"xmqqed9ky0mk.fsf@gitster.g","subject":"Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:30:28Z","receivedAt":"2024-05-30T11:30:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 10:43:47AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > diff --git a/reftable/basics_test.c b/reftable/basics_test.c\n> > index 997c4d9e01..af9209d535 100644\n> > --- a/reftable/basics_test.c\n> > +++ b/reftable/basics_test.c\n> > @@ -58,8 +58,8 @@ static void test_binsearch(void)\n> >  \n> >  static void test_names_length(void)\n> >  {\n> > -\tchar *a[] = { \"a\", \"b\", NULL };\n> > -\tEXPECT(names_length(a) == 2);\n> > +\tchar *names[] = { (char *)\"a\", (char *)\"b\", NULL };\n> > +\tEXPECT(names_length(names) == 2);\n> >  }\n> \n> I would have preferred to see this kind of rewrite more than\n> separate and clearly writable variables that are initialied with the\n> constant contents e.g. branches[] = \"refs/heads/*\", we saw in\n> earlier steps.  Wouldn't that approach, combined with making the\n> literal constants stored in read-only segment to trigger runtime\n> failure when a bug causes the \"unfortunately non-const\" variables\n> to be written, give us a better result?\n\nDepends on what we mean by \"better\", I guess. But yeah, I was torn\nmyself when writing this commit because there are so many string\nconstants in the reftable tests that we assign to non-constant fields. I\ndidn't find the result particularly easy to read when putting each of\nthe constants into a separate variable.\n\nRevisiting this again though I don't think it's all that bad. I'll adapt\naccordingly.\n\nPatrick\n"},{"id":"495921","messageId":"ZlhjXcTdyCZb8eKa@tanuki","threadId":"61561","inReplyTo":"xmqq5xuwweqs.fsf@gitster.g","subject":"Re: [PATCH 12/19] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:30:37Z","receivedAt":"2024-05-30T11:30:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 01:21:47PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >  \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n> > +\t\t\tconst char *value = strchr(arg, ' ');\n> > +\t\t\tsize_t arglen;\n> >  \t\t\tint result;\n> >  \n> > +\t\t\tif (value) {\n> > +\t\t\t\targlen = value - arg;\n> > +\t\t\t\tvalue++;\n> > +\t\t\t} else {\n> > +\t\t\t\targlen = strlen(arg);\n> >  \t\t\t\tvalue = \"true\";\n> > +\t\t\t}\n> \n> There is a micro optimization opportunity here.\n> \n> \tconst char *value = strchrnul(arg, ' ');\n> \tsize_t arglen = value - arg;\n> \n> \tif (*value)\n> \t\tvalue++; /* skip over SP */\n> \telse\n> \t\tvalue = \"true\";\n> \n> But other than that, very cleanly done.\n\nIndeed, that version looks nicer. Thanks!\n\nPatrick\n"},{"id":"495922","messageId":"ZlhjdPkEEwg7Wog7@tanuki","threadId":"61561","inReplyTo":"xmqqle3suymm.fsf@gitster.g","subject":"Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:31:00Z","receivedAt":"2024-05-30T11:31:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 01:55:13PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > We never free any of the config strings that we populate into the\n> > `struct imap_server_conf`. Fix this by creating a common exit path where\n> > we can free resources.\n> \n> This is more like the previous step got rid of the anchor that made\n> these strings reachable, so we need to turn around to free them,\n> which is sort-of Meh, especially given that the leaked pieces of\n> memory are small and very much bounded.\n> \n> The main benefit of this change is to allow us prepare on the\n> constness change in the other (read: API this thing uses from\n> elsewhere) parts of the system, which is a very worthy goal.\n\nThat's the motivation in this series at least. But I also see it as a\ngood goal by itself to get rid of the global state that we had before\nthe preceding patch. It may not be necessary, but it certainly helps me\npersonally to reason about code better.\n\n> > While at it, drop the unused variables `imap_server_conf::name` and\n> > `nongit_ok`.\n> \n> The removal of the .name member may be correct, but I suspect the\n> change to nongit_ok is a change in behaviour, and it could even be a\n> regression.\n> \n> > -\tsetup_git_directory_gently(&nongit_ok);\n> > +\tsetup_git_directory_gently(NULL);\n> \n> The general idea behind &nongit_ok is that\n> \n>  - Usually setup_git_directory_gently() dies if NULL is passed\n>    instead of a pointer to &nongit_ok.  Most of the Git command\n>    wants to make sure they have a repository to operate on, so this\n>    is a reasonable default behaviour.\n> \n>  - Some commands would want to work also without having any\n>    repository, possibly with limited capability (e.g., \"git apply\"\n>    may want to work as a better \"GNU patch\", but obviously it cannot\n>    do \"git apply --binary --3way\" without having the object\n>    database).  They tell setup_git_directory_gently() not to die\n>    when outside a repository by passing a pointer to &nongit_ok, and\n>    instead tell if we are in a repository by storing 0/1 in it.\n> \n> The idea is that a command that is willing to work outside a\n> repository can disable selected features based on what it sees in\n> nongit_ok.  In the case of \"imap-send\", there is no such features\n> that it needs to special case, perhaps because everything it does is\n> supposed to work outside a repository?\n> \n> So the short version of what worries me in this change is that we\n> used to be able to operate without having a repository at all, but\n> now we would barf if run outside a repository, no?\n\nOh, I wasn't aware that the parameter being `NULL` actually causes a\nchange in behaviour. Which nicely demonstrates that we have some missing\ntest coverage for git-imap-send(1).\n\nIn fact, it's not only \"some\". We don't have any test coverage at all\nfor git-imap-send(1) as far as I can see. Which does make me rest a bit\nuneasy. And I suspect that it wouldn't be trivial to add given that it\nkind of requires something that talks IMAP on the receiving end.\n\nPatrick\n"},{"id":"495923","messageId":"ZlhjfmZ89nO14jUv@tanuki","threadId":"61561","inReplyTo":"xmqqh6eguycr.fsf@gitster.g","subject":"Re: [PATCH 17/19] builtin/rebase: adapt code to not assign string constants to non-const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T11:31:10Z","receivedAt":"2024-05-30T11:31:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 29, 2024 at 02:01:08PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > When computing the rebase strategy we temporarily assign a string\n> > constant to `options.strategy` before we call `xstrdup()` on it.\n> > Furthermore, the default backend is being assigned a string constant via\n> > `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable\n> > `-Wwrite-strings`.\n> >\n> > Adapt the code such that we only store allocated strings in those\n> > variables.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  builtin/rebase.c | 16 +++++++++-------\n> >  1 file changed, 9 insertions(+), 7 deletions(-)\n> \n> One gripe I have in this change is that it used to be crystal clear\n> what the hardcoded default was (i.e. written in the initialization\n> data), but now you have to follow the program flow to see what the\n> hardcoded default is.\n\nTrue. We could adapt the macro to instead `xstrdup()` the default\nbackend. But that feels a bit hidden, so I don't think this is a\npartiuclarly great option.\n\nAn alternative would be to wrap initialization into a function\n`rebase_options_init()` that instead allocates the default backend\nstring. We can then also create a `rebase_options_release()` counter\npart that releases its resources such that this whole thing becomes a\nbit more self contained.\n\nPatrick\n"},{"id":"495935","messageId":"cover.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH v2 00/19] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:30Z","receivedAt":"2024-05-30T12:50:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the second version of my patch series that prepares our code\nbase to compile with `-Wwrite-strings`. This warning will convert the\ntype of string constants from `char []` to `const char[]` so that it\nbecomes harder to for example write to or free such constants by\naccident.\n\nChanges compared to v2:\n\n  - Merged the reftable-specific test into the second patch. Instead of\n    adding casts, we now allocate the required strings on the stack.\n\n  - Apply a micro-optimization to \"remote-curl.c\" that was suggested by\n    Junio.\n\n  - Restore the `nongit_ok` variable in \"imap-send.c\". It's somewhat\n    concerning that we do not have test coverage for git-imap-send(1) at\n    all. But it might be a bit more involved as we do not have any IMAP\n    test infra, to the best of my knowledge.\n\n  - Rework the patch to \"builtin/rebase.c\". It is now split into two\n    patches. The first patch reworks initialization of the rebase\n    options so that the defaults are still self-contained in a single\n    place. And the second patch refactors how we set up the merge\n    strategy.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (19):\n  global: improve const correctness when assigning string constants\n  global: assign non-const strings as required\n  global: convert intentionally-leaking config strings to consts\n  compat/win32: fix const-correctness with string constants\n  refspec: remove global tag refspec structure\n  http: do not assign string constant to non-const field\n  line-log: always allocate the output prefix\n  object-file: make `buf` parameter of `index_mem()` a constant\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: do not assign default backend to non-constant field\n  builtin/rebase: always store allocated string in `options.strategy`\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  81 ++++++++++++----------\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |   3 +-\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  15 +++-\n compat/mingw.c               |  25 +++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   7 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |   7 +-\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   9 ++-\n imap-send.c                  | 130 ++++++++++++++++++++---------------\n line-log.c                   |  21 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  17 ++---\n parse-options.h              |   2 +-\n pretty.c                     |   7 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |   5 +-\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics_test.c       |   5 +-\n reftable/block_test.c        |   4 +-\n reftable/merged_test.c       |  52 +++++++-------\n reftable/readwrite_test.c    |  60 +++++++++-------\n reftable/record.c            |   6 +-\n reftable/stack_test.c        |  87 ++++++++++++-----------\n remote-curl.c                |  53 +++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 65 files changed, 479 insertions(+), 373 deletions(-)\n\nRange-diff against v1:\n 1:  25c31e550f =  1:  25c31e550f global: improve const correctness when assigning string constants\n 5:  dc5d85257e !  2:  3430bcc09b reftable: improve const correctness when assigning string constants\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    reftable: improve const correctness when assigning string constants\n    +    global: assign non-const strings as required\n     \n    -    There are many cases in the reftable tests where we assign string\n    -    constants to non-const fields. This is harmless because we know that\n    -    those fields are only used for reading access, but will break once we\n    -    enable `-Wwrite-strings`. Add explicit casts to prepare for this.\n    +    There are several cases where we initialize non-const fields with string\n    +    constants. This is invalid and will cause warnings once we enable the\n    +    `-Wwrite-strings` warning. Adapt those cases to instead use string\n    +    arrays.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    + ## builtin/remote.c ##\n    +@@ builtin/remote.c: static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n    + \tstruct ref *ref, *matches;\n    + \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n    + \tstruct refspec_item refspec;\n    ++\tchar refspec_str[] = \"refs/heads/*\";\n    + \n    + \tmemset(&refspec, 0, sizeof(refspec));\n    + \trefspec.force = 0;\n    + \trefspec.pattern = 1;\n    +-\trefspec.src = refspec.dst = \"refs/heads/*\";\n    ++\trefspec.src = refspec.dst = refspec_str;\n    + \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n    + \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n    + \t\t\t\t    fetch_map, 1);\n    +\n    + ## diff.c ##\n    +@@ diff.c: size_t fill_textconv(struct repository *r,\n    + \t\t     struct diff_filespec *df,\n    + \t\t     char **outbuf)\n    + {\n    ++\tstatic char empty_str[] = \"\";\n    + \tsize_t size;\n    + \n    + \tif (!driver) {\n    + \t\tif (!DIFF_FILE_VALID(df)) {\n    +-\t\t\t*outbuf = \"\";\n    ++\t\t\t*outbuf = empty_str;\n    + \t\t\treturn 0;\n    + \t\t}\n    + \t\tif (diff_populate_filespec(r, df, NULL))\n    +\n    + ## entry.c ##\n    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    + \tstruct string_list_item *filter, *path;\n    + \tstruct progress *progress = NULL;\n    + \tstruct delayed_checkout *dco = state->delayed_checkout;\n    ++\tchar empty_str[] = \"\";\n    + \n    + \tif (!state->delayed_checkout)\n    + \t\treturn errs;\n    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    + \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n    + \t\t\t\t/* Filter reported an error */\n    + \t\t\t\terrs = 1;\n    +-\t\t\t\tfilter->string = \"\";\n    ++\t\t\t\tfilter->string = empty_str;\n    + \t\t\t\tcontinue;\n    + \t\t\t}\n    + \t\t\tif (available_paths.nr <= 0) {\n    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    + \t\t\t\t * filter from the list (see\n    + \t\t\t\t * \"string_list_remove_empty_items\" call below).\n    + \t\t\t\t */\n    +-\t\t\t\tfilter->string = \"\";\n    ++\t\t\t\tfilter->string = empty_str;\n    + \t\t\t\tcontinue;\n    + \t\t\t}\n    + \n    +@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    + \t\t\t\t\t * Do not ask the filter for available blobs,\n    + \t\t\t\t\t * again, as the filter is likely buggy.\n    + \t\t\t\t\t */\n    +-\t\t\t\t\tfilter->string = \"\";\n    ++\t\t\t\t\tfilter->string = empty_str;\n    + \t\t\t\t\tcontinue;\n    + \t\t\t\t}\n    + \t\t\t\tce = index_file_exists(state->istate, path->string,\n    +\n    + ## ident.c ##\n    +@@ ident.c: static struct passwd *xgetpwuid_self(int *is_bogus)\n    + \tpw = getpwuid(getuid());\n    + \tif (!pw) {\n    + \t\tstatic struct passwd fallback;\n    +-\t\tfallback.pw_name = \"unknown\";\n    ++\t\tstatic char fallback_name[] = \"unknown\";\n    + #ifndef NO_GECOS_IN_PWENT\n    +-\t\tfallback.pw_gecos = \"Unknown\";\n    ++\t\tstatic char fallback_gcos[] = \"Unknown\";\n    ++#endif\n    ++\n    ++\t\tfallback.pw_name = fallback_name;\n    ++#ifndef NO_GECOS_IN_PWENT\n    ++\t\tfallback.pw_gecos = fallback_gcos;\n    + #endif\n    + \t\tpw = &fallback;\n    + \t\tif (is_bogus)\n    +\n    + ## line-log.c ##\n    +@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,\n    + \tstruct range_set tmp;\n    + \tstruct diff_ranges diff;\n    + \tmmfile_t file_parent, file_target;\n    ++\tchar empty_str[] = \"\";\n    + \n    + \tassert(pair->two->path);\n    + \twhile (rg) {\n    +@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,\n    + \t\tfile_parent.ptr = pair->one->data;\n    + \t\tfile_parent.size = pair->one->size;\n    + \t} else {\n    +-\t\tfile_parent.ptr = \"\";\n    ++\t\tfile_parent.ptr = empty_str;\n    + \t\tfile_parent.size = 0;\n    + \t}\n    + \n    +\n    + ## object-file.c ##\n    +@@ object-file.c: static struct cached_object {\n    + } *cached_objects;\n    + static int cached_object_nr, cached_object_alloc;\n    + \n    ++static char empty_tree_buf[] = \"\";\n    + static struct cached_object empty_tree = {\n    + \t.oid = {\n    + \t\t.hash = EMPTY_TREE_SHA1_BIN_LITERAL,\n    + \t},\n    + \t.type = OBJ_TREE,\n    +-\t.buf = \"\",\n    ++\t.buf = empty_tree_buf,\n    + };\n    + \n    + static struct cached_object *find_cached_object(const struct object_id *oid)\n    +\n    + ## pretty.c ##\n    +@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n    + \t\treturn 1;\n    + \tcase 'D':\n    + \t\t{\n    ++\t\t\tchar empty_str[] = \"\";\n    + \t\t\tconst struct decoration_options opts = {\n    +-\t\t\t\t.prefix = \"\",\n    +-\t\t\t\t.suffix = \"\"\n    ++\t\t\t\t.prefix = empty_str,\n    ++\t\t\t\t.suffix = empty_str,\n    + \t\t\t};\n    + \n    + \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n    +\n    + ## refs/reftable-backend.c ##\n    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    + \tstruct strbuf errbuf = STRBUF_INIT;\n    + \tsize_t logs_nr = 0, logs_alloc = 0, i;\n    + \tconst char *committer_info;\n    ++\tchar head[] = \"HEAD\";\n    + \tint ret;\n    + \n    + \tcommitter_info = git_committer_info(0);\n    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    + \t\tif (append_head_reflog) {\n    + \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n    + \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n    +-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n    ++\t\t\tlogs[logs_nr].refname = head;\n    + \t\t\tlogs_nr++;\n    + \t\t}\n    + \t}\n    +@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    + \tstring_list_clear(&skip, 0);\n    + \tstrbuf_release(&errbuf);\n    + \tfor (i = 0; i < logs_nr; i++) {\n    +-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n    ++\t\tif (logs[i].refname == head)\n    + \t\t\tcontinue;\n    + \t\tlogs[i].refname = NULL;\n    + \t\treftable_log_record_release(&logs[i]);\n    +\n      ## reftable/basics_test.c ##\n     @@ reftable/basics_test.c: static void test_binsearch(void)\n      \n    @@ reftable/basics_test.c: static void test_binsearch(void)\n      {\n     -\tchar *a[] = { \"a\", \"b\", NULL };\n     -\tEXPECT(names_length(a) == 2);\n    -+\tchar *names[] = { (char *)\"a\", (char *)\"b\", NULL };\n    ++\tchar a[] = \"a\", b[] = \"b\";\n    ++\tchar *names[] = { a, b, NULL };\n     +\tEXPECT(names_length(names) == 2);\n      }\n      \n      static void test_parse_names_normal(void)\n     \n      ## reftable/block_test.c ##\n    +@@ reftable/block_test.c: license that can be found in the LICENSE file or at\n    + static void test_block_read_write(void)\n    + {\n    + \tconst int header_off = 21; /* random */\n    +-\tchar *names[30];\n    ++\tchar *names[30], empty_str[] = \"\";\n    + \tconst int N = ARRAY_SIZE(names);\n    + \tconst int block_size = 1024;\n    + \tstruct reftable_block block = { NULL };\n     @@ reftable/block_test.c: static void test_block_read_write(void)\n      \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n      \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n      \n     -\trec.u.ref.refname = \"\";\n    -+\trec.u.ref.refname = (char *)\"\";\n    ++\trec.u.ref.refname = empty_str;\n      \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n      \tn = block_writer_add(&bw, &rec);\n      \tEXPECT(n == REFTABLE_API_ERROR);\n     \n      ## reftable/merged_test.c ##\n     @@ reftable/merged_test.c: static void readers_destroy(struct reftable_reader **readers, size_t n)\n    + \n      static void test_merged_between(void)\n      {\n    ++\tchar a[] = \"a\", b[] = \"b\";\n      \tstruct reftable_ref_record r1[] = { {\n     -\t\t.refname = \"b\",\n    -+\t\t.refname = (char *)\"b\",\n    ++\t\t.refname = b,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t.value.val1 = { 1, 2, 3, 0 },\n      \t} };\n      \tstruct reftable_ref_record r2[] = { {\n     -\t\t.refname = \"a\",\n    -+\t\t.refname = (char *)\"a\",\n    ++\t\t.refname = a,\n      \t\t.update_index = 2,\n      \t\t.value_type = REFTABLE_REF_DELETION,\n      \t} };\n    -@@ reftable/merged_test.c: static void test_merged(void)\n    +@@ reftable/merged_test.c: static void test_merged_between(void)\n    + \n    + static void test_merged(void)\n      {\n    ++\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n      \tstruct reftable_ref_record r1[] = {\n      \t\t{\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t\t.value.val1 = { 1 },\n      \t\t},\n      \t\t{\n     -\t\t\t.refname = \"b\",\n    -+\t\t\t.refname = (char *)\"b\",\n    ++\t\t\t.refname = b,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t\t.value.val1 = { 1 },\n      \t\t},\n      \t\t{\n     -\t\t\t.refname = \"c\",\n    -+\t\t\t.refname = (char *)\"c\",\n    ++\t\t\t.refname = c,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t\t.value.val1 = { 1 },\n    @@ reftable/merged_test.c: static void test_merged(void)\n      \t};\n      \tstruct reftable_ref_record r2[] = { {\n     -\t\t.refname = \"a\",\n    -+\t\t.refname = (char *)\"a\",\n    ++\t\t.refname = a,\n      \t\t.update_index = 2,\n      \t\t.value_type = REFTABLE_REF_DELETION,\n      \t} };\n      \tstruct reftable_ref_record r3[] = {\n      \t\t{\n     -\t\t\t.refname = \"c\",\n    -+\t\t\t.refname = (char *)\"c\",\n    ++\t\t\t.refname = c,\n      \t\t\t.update_index = 3,\n      \t\t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t\t.value.val1 = { 2 },\n      \t\t},\n      \t\t{\n     -\t\t\t.refname = \"d\",\n    -+\t\t\t.refname = (char *)\"d\",\n    ++\t\t\t.refname = d,\n      \t\t\t.update_index = 3,\n      \t\t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t\t.value.val1 = { 1 },\n    -@@ reftable/merged_test.c: static void test_merged_logs(void)\n    +@@ reftable/merged_test.c: merged_table_from_log_records(struct reftable_log_record **logs,\n    + \n    + static void test_merged_logs(void)\n      {\n    ++\tchar a[] = \"a\";\n    ++\tchar name[] = \"jane doe\", email[] = \"jane@invalid\";\n    ++\tchar message1[] = \"message1\", message2[] = \"message2\";\n    ++\tchar message3[] = \"message3\";\n      \tstruct reftable_log_record r1[] = {\n      \t\t{\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 2,\n      \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t\t.value.update = {\n    @@ reftable/merged_test.c: static void test_merged_logs(void)\n     -\t\t\t\t.name = \"jane doe\",\n     -\t\t\t\t.email = \"jane@invalid\",\n     -\t\t\t\t.message = \"message2\",\n    -+\t\t\t\t.name = (char *)\"jane doe\",\n    -+\t\t\t\t.email = (char *)\"jane@invalid\",\n    -+\t\t\t\t.message = (char *)\"message2\",\n    ++\t\t\t\t.name = name,\n    ++\t\t\t\t.email = email,\n    ++\t\t\t\t.message = message2,\n      \t\t\t}\n      \t\t},\n      \t\t{\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t\t.value.update = {\n    @@ reftable/merged_test.c: static void test_merged_logs(void)\n     -\t\t\t\t.name = \"jane doe\",\n     -\t\t\t\t.email = \"jane@invalid\",\n     -\t\t\t\t.message = \"message1\",\n    -+\t\t\t\t.name = (char *)\"jane doe\",\n    -+\t\t\t\t.email = (char *)\"jane@invalid\",\n    -+\t\t\t\t.message = (char *)\"message1\",\n    ++\t\t\t\t.name = name,\n    ++\t\t\t\t.email = email,\n    ++\t\t\t\t.message = message1,\n      \t\t\t}\n      \t\t},\n      \t};\n      \tstruct reftable_log_record r2[] = {\n      \t\t{\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 3,\n      \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t\t.value.update = {\n    @@ reftable/merged_test.c: static void test_merged_logs(void)\n     -\t\t\t\t.name = \"jane doe\",\n     -\t\t\t\t.email = \"jane@invalid\",\n     -\t\t\t\t.message = \"message3\",\n    -+\t\t\t\t.name = (char *)\"jane doe\",\n    -+\t\t\t\t.email = (char *)\"jane@invalid\",\n    -+\t\t\t\t.message = (char *)\"message3\",\n    ++\t\t\t\t.name = name,\n    ++\t\t\t\t.email = email,\n    ++\t\t\t\t.message = message3,\n      \t\t\t}\n      \t\t},\n      \t};\n      \tstruct reftable_log_record r3[] = {\n      \t\t{\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 2,\n      \t\t\t.value_type = REFTABLE_LOG_DELETION,\n      \t\t},\n    -@@ reftable/merged_test.c: static void test_default_write_opts(void)\n    +@@ reftable/merged_test.c: static void test_merged_logs(void)\n    + \n    + static void test_default_write_opts(void)\n    + {\n    ++\tchar master[] = \"master\";\n    + \tstruct reftable_write_options opts = { 0 };\n      \tstruct strbuf buf = STRBUF_INIT;\n      \tstruct reftable_writer *w =\n      \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n     -\n      \tstruct reftable_ref_record rec = {\n     -\t\t.refname = \"master\",\n    -+\t\t.refname = (char *)\"master\",\n    ++\t\t.refname = master,\n      \t\t.update_index = 1,\n      \t};\n      \tint err;\n     \n      ## reftable/readwrite_test.c ##\n    +@@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,\n    + \tint i = 0, n;\n    + \tstruct reftable_log_record log = { NULL };\n    + \tconst struct reftable_stats *stats = NULL;\n    ++\tchar message[] = \"message\";\n    + \n    + \tREFTABLE_CALLOC_ARRAY(*names, N + 1);\n    + \n     @@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,\n      \t\tlog.update_index = update_index;\n      \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n      \t\tset_test_hash(log.value.update.new_hash, i);\n     -\t\tlog.value.update.message = \"message\";\n    -+\t\tlog.value.update.message = (char *)\"message\";\n    ++\t\tlog.value.update.message = message;\n      \n      \t\tn = reftable_writer_add_log(w, &log);\n      \t\tEXPECT(n == 0);\n    -@@ reftable/readwrite_test.c: static void test_log_buffer_size(void)\n    +@@ reftable/readwrite_test.c: static void write_table(char ***names, struct strbuf *buf, int N,\n    + \n    + static void test_log_buffer_size(void)\n    + {\n    ++\tchar refname[] = \"refs/heads/master\";\n    ++\tchar name[] = \"Han-Wen Hienhuys\";\n    ++\tchar email[] = \"hanwen@google.com\";\n    ++\tchar message[] = \"commit: 9\\n\";\n    + \tstruct strbuf buf = STRBUF_INIT;\n    + \tstruct reftable_write_options opts = {\n    + \t\t.block_size = 4096,\n      \t};\n      \tint err;\n      \tint i;\n    @@ reftable/readwrite_test.c: static void test_log_buffer_size(void)\n     -\t\t\t\t\t   .message = \"commit: 9\\n\",\n     -\t\t\t\t   } } };\n     +\tstruct reftable_log_record log = {\n    -+\t\t.refname = (char *)\"refs/heads/master\",\n    ++\t\t.refname = refname,\n     +\t\t.update_index = 0xa,\n     +\t\t.value_type = REFTABLE_LOG_UPDATE,\n     +\t\t.value.update = {\n    -+\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n    -+\t\t\t.email = (char *)\"hanwen@google.com\",\n    ++\t\t\t.name = name,\n    ++\t\t\t.email = email,\n     +\t\t\t.tz_offset = 100,\n     +\t\t\t.time = 0x5e430672,\n    -+\t\t\t.message = (char *)\"commit: 9\\n\",\n    ++\t\t\t.message = message,\n     +\t\t},\n     +\t};\n      \tstruct reftable_writer *w =\n      \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n      \n    +@@ reftable/readwrite_test.c: static void test_log_buffer_size(void)\n    + \n    + static void test_log_overflow(void)\n    + {\n    ++\tchar refname[] = \"refs/heads/master\";\n    ++\tchar name[] = \"Han-Wen Hienhuys\";\n    ++\tchar email[] = \"hanwen@google.com\";\n    + \tstruct strbuf buf = STRBUF_INIT;\n    + \tchar msg[256] = { 0 };\n    + \tstruct reftable_write_options opts = {\n     @@ reftable/readwrite_test.c: static void test_log_overflow(void)\n      \t};\n      \tint err;\n      \tstruct reftable_log_record log = {\n     -\t\t.refname = \"refs/heads/master\",\n    -+\t\t.refname = (char *)\"refs/heads/master\",\n    ++\t\t.refname = refname,\n      \t\t.update_index = 0xa,\n      \t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t.value = {\n    @@ reftable/readwrite_test.c: static void test_log_overflow(void)\n      \t\t\t\t.new_hash = { 2 },\n     -\t\t\t\t.name = \"Han-Wen Nienhuys\",\n     -\t\t\t\t.email = \"hanwen@google.com\",\n    -+\t\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n    -+\t\t\t\t.email = (char *)\"hanwen@google.com\",\n    ++\t\t\t\t.name = name,\n    ++\t\t\t\t.email = email,\n      \t\t\t\t.tz_offset = 100,\n      \t\t\t\t.time = 0x5e430672,\n      \t\t\t\t.message = msg,\n     @@ reftable/readwrite_test.c: static void test_log_zlib_corruption(void)\n    + \tstruct reftable_writer *w =\n    + \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n    + \tconst struct reftable_stats *stats = NULL;\n    ++\tchar refname[] = \"refname\";\n    ++\tchar name[] = \"My Name\";\n    ++\tchar email[] = \"myname@invalid\";\n      \tchar message[100] = { 0 };\n      \tint err, i, n;\n      \tstruct reftable_log_record log = {\n     -\t\t.refname = \"refname\",\n    -+\t\t.refname = (char *)\"refname\",\n    ++\t\t.refname = refname,\n      \t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t.value = {\n      \t\t\t.update = {\n    @@ reftable/readwrite_test.c: static void test_log_zlib_corruption(void)\n      \t\t\t\t.old_hash = { 2 },\n     -\t\t\t\t.name = \"My Name\",\n     -\t\t\t\t.email = \"myname@invalid\",\n    -+\t\t\t\t.name = (char *)\"My Name\",\n    -+\t\t\t\t.email = (char *)\"myname@invalid\",\n    ++\t\t\t\t.name = name,\n    ++\t\t\t\t.email = email,\n      \t\t\t\t.message = message,\n      \t\t\t},\n      \t\t},\n     @@ reftable/readwrite_test.c: static void test_write_empty_key(void)\n    + \tstruct strbuf buf = STRBUF_INIT;\n      \tstruct reftable_writer *w =\n      \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n    ++\tchar refname[] = \"\";\n      \tstruct reftable_ref_record ref = {\n     -\t\t.refname = \"\",\n    -+\t\t.refname = (char *)\"\",\n    ++\t\t.refname = refname,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_DELETION,\n      \t};\n     @@ reftable/readwrite_test.c: static void test_write_key_order(void)\n    + \tstruct strbuf buf = STRBUF_INIT;\n    + \tstruct reftable_writer *w =\n      \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n    ++\tchar a[] = \"a\", b[] = \"b\", target[] = \"target\";\n      \tstruct reftable_ref_record refs[2] = {\n      \t\t{\n     -\t\t\t.refname = \"b\",\n    -+\t\t\t.refname = (char *)\"b\",\n    ++\t\t\t.refname = b,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n      \t\t\t.value = {\n     -\t\t\t\t.symref = \"target\",\n    -+\t\t\t\t.symref = (char *)\"target\",\n    ++\t\t\t\t.symref = target,\n      \t\t\t},\n      \t\t}, {\n     -\t\t\t.refname = \"a\",\n    -+\t\t\t.refname = (char *)\"a\",\n    ++\t\t\t.refname = a,\n      \t\t\t.update_index = 1,\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n      \t\t\t.value = {\n     -\t\t\t\t.symref = \"target\",\n    -+\t\t\t\t.symref = (char *)\"target\",\n    ++\t\t\t\t.symref = target,\n      \t\t\t},\n      \t\t}\n      \t};\n    @@ reftable/stack_test.c: static void test_parse_names(void)\n      \n      static int write_test_ref(struct reftable_writer *wr, void *arg)\n     @@ reftable/stack_test.c: static void test_reftable_stack_add_one(void)\n    + \t};\n      \tstruct reftable_stack *st = NULL;\n      \tint err;\n    ++\tchar head[] = \"HEAD\", master[] = \"master\";\n      \tstruct reftable_ref_record ref = {\n     -\t\t.refname = \"HEAD\",\n    -+\t\t.refname = (char *)\"HEAD\",\n    ++\t\t.refname = head,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \tstruct reftable_ref_record dest = { NULL };\n      \tstruct stat stat_result = { 0 };\n     @@ reftable/stack_test.c: static void test_reftable_stack_uptodate(void)\n    + \tchar *dir = get_tmp_dir(__LINE__);\n      \n      \tint err;\n    ++\tchar head[] = \"HEAD\", branch2[] = \"branch2\", master[] = \"master\";\n      \tstruct reftable_ref_record ref1 = {\n     -\t\t.refname = \"HEAD\",\n    -+\t\t.refname = (char *)\"HEAD\",\n    ++\t\t.refname = head,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \tstruct reftable_ref_record ref2 = {\n     -\t\t.refname = \"branch2\",\n    -+\t\t.refname = (char *)\"branch2\",\n    ++\t\t.refname = branch2,\n      \t\t.update_index = 2,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \n      \n     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api(void)\n    + \tstruct reftable_stack *st = NULL;\n    + \tint err;\n      \tstruct reftable_addition *add = NULL;\n    - \n    +-\n    ++\tchar head[] = \"HEAD\", master[] = \"master\";\n      \tstruct reftable_ref_record ref = {\n     -\t\t.refname = \"HEAD\",\n    -+\t\t.refname = (char *)\"HEAD\",\n    ++\t\t.refname = head,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \tstruct reftable_ref_record dest = { NULL };\n      \n     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n    + \tEXPECT_ERR(err);\n    + \n    + \tfor (i = 0; i <= n; i++) {\n    ++\t\tchar master[] = \"master\";\n      \t\tstruct reftable_ref_record ref = {\n      \t\t\t.update_index = reftable_stack_next_update_index(st),\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t\t.value.symref = \"master\",\n    -+\t\t\t.value.symref = (char *)\"master\",\n    ++\t\t\t.value.symref = master,\n      \t\t};\n      \t\tchar name[100];\n      \n     @@ reftable/stack_test.c: static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n    + \n      static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n      {\n    ++\tchar master[] = \"refs/meads/master\";\n      \tstruct reftable_ref_record ref = {\n     -\t\t.refname = \"refs/heads/master\",\n    -+\t\t.refname = (char *)\"refs/heads/master\",\n    ++\t\t.refname = master,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_VAL1,\n      \t\t.value.val1 = {0x01},\n    -@@ reftable/stack_test.c: static void test_reftable_stack_update_index_check(void)\n    +@@ reftable/stack_test.c: static int write_error(struct reftable_writer *wr, void *arg)\n    + static void test_reftable_stack_update_index_check(void)\n    + {\n    + \tchar *dir = get_tmp_dir(__LINE__);\n    +-\n    + \tstruct reftable_write_options cfg = { 0 };\n      \tstruct reftable_stack *st = NULL;\n      \tint err;\n    ++\tchar name1[] = \"name1\", name2[] = \"name2\", master[] = \"master\";\n      \tstruct reftable_ref_record ref1 = {\n     -\t\t.refname = \"name1\",\n    -+\t\t.refname = (char *)\"name1\",\n    ++\t\t.refname = name1,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \tstruct reftable_ref_record ref2 = {\n     -\t\t.refname = \"name2\",\n    -+\t\t.refname = (char *)\"name2\",\n    ++\t\t.refname = name2,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"master\",\n    -+\t\t.value.symref = (char *)\"master\",\n    ++\t\t.value.symref = master,\n      \t};\n      \n      \terr = reftable_new_stack(&st, dir, cfg);\n     @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)\n    + \t};\n      \tstruct reftable_stack *st = NULL;\n      \tchar *dir = get_tmp_dir(__LINE__);\n    ++\tchar branch[] = \"branch\";\n    ++\tchar onetwomessage[] = \"one\\ntwo\";\n    ++\tchar onemessage[] = \"one\";\n    ++\tchar twomessage[] = \"two\\n\";\n      \tstruct reftable_log_record input = {\n     -\t\t.refname = \"branch\",\n    -+\t\t.refname = (char *)\"branch\",\n    ++\t\t.refname = branch,\n      \t\t.update_index = 1,\n      \t\t.value_type = REFTABLE_LOG_UPDATE,\n      \t\t.value = {\n    @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)\n      \tEXPECT_ERR(err);\n      \n     -\tinput.value.update.message = \"one\\ntwo\";\n    -+\tinput.value.update.message = (char *)\"one\\ntwo\";\n    ++\tinput.value.update.message = onetwomessage;\n      \terr = reftable_stack_add(st, &write_test_log, &arg);\n      \tEXPECT(err == REFTABLE_API_ERROR);\n      \n     -\tinput.value.update.message = \"one\";\n    -+\tinput.value.update.message = (char *)\"one\";\n    ++\tinput.value.update.message = onemessage;\n      \terr = reftable_stack_add(st, &write_test_log, &arg);\n      \tEXPECT_ERR(err);\n      \n    @@ reftable/stack_test.c: static void test_reftable_stack_log_normalize(void)\n      \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n      \n     -\tinput.value.update.message = \"two\\n\";\n    -+\tinput.value.update.message = (char *)\"two\\n\";\n    ++\tinput.value.update.message = twomessage;\n      \targ.update_index = 2;\n      \terr = reftable_stack_add(st, &write_test_log, &arg);\n      \tEXPECT_ERR(err);\n    -@@ reftable/stack_test.c: static void test_reftable_stack_hash_id(void)\n    +@@ reftable/stack_test.c: static void test_reftable_stack_tombstone(void)\n    + static void test_reftable_stack_hash_id(void)\n    + {\n    + \tchar *dir = get_tmp_dir(__LINE__);\n    +-\n    + \tstruct reftable_write_options cfg = { 0 };\n    + \tstruct reftable_stack *st = NULL;\n      \tint err;\n    - \n    +-\n    ++\tchar master[] = \"master\", target[] = \"target\";\n      \tstruct reftable_ref_record ref = {\n     -\t\t.refname = \"master\",\n    -+\t\t.refname = (char *)\"master\",\n    ++\t\t.refname = master,\n      \t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t.value.symref = \"target\",\n    -+\t\t.value.symref = (char *)\"target\",\n    ++\t\t.value.symref = target,\n      \t\t.update_index = 1,\n      \t};\n      \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n     @@ reftable/stack_test.c: static void test_reftable_stack_auto_compaction(void)\n    + \tEXPECT_ERR(err);\n    + \n    + \tfor (i = 0; i < N; i++) {\n    +-\t\tchar name[100];\n    ++\t\tchar name[100], master[] = \"master\";\n    + \t\tstruct reftable_ref_record ref = {\n      \t\t\t.refname = name,\n      \t\t\t.update_index = reftable_stack_next_update_index(st),\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t\t.value.symref = \"master\",\n    -+\t\t\t.value.symref = (char *)\"master\",\n    ++\t\t\t.value.symref = master,\n      \t\t};\n      \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n      \n     @@ reftable/stack_test.c: static void test_reftable_stack_add_performs_auto_compaction(void)\n    + \tEXPECT_ERR(err);\n    + \n    + \tfor (i = 0; i <= n; i++) {\n    ++\t\tchar master[] = \"master\";\n      \t\tstruct reftable_ref_record ref = {\n      \t\t\t.update_index = reftable_stack_next_update_index(st),\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t\t.value.symref = \"master\",\n    -+\t\t\t.value.symref = (char *)\"master\",\n    ++\t\t\t.value.symref = master,\n      \t\t};\n      \n      \t\t/*\n     @@ reftable/stack_test.c: static void test_reftable_stack_compaction_concurrent(void)\n    + \tEXPECT_ERR(err);\n    + \n    + \tfor (i = 0; i < N; i++) {\n    +-\t\tchar name[100];\n    ++\t\tchar name[100], master[] = \"master\";\n    + \t\tstruct reftable_ref_record ref = {\n      \t\t\t.refname = name,\n      \t\t\t.update_index = reftable_stack_next_update_index(st1),\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t\t.value.symref = \"master\",\n    -+\t\t\t.value.symref = (char *)\"master\",\n    ++\t\t\t.value.symref = master,\n      \t\t};\n      \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n      \n     @@ reftable/stack_test.c: static void test_reftable_stack_compaction_concurrent_clean(void)\n    + \tEXPECT_ERR(err);\n    + \n    + \tfor (i = 0; i < N; i++) {\n    +-\t\tchar name[100];\n    ++\t\tchar name[100], master[] = \"master\";\n    + \t\tstruct reftable_ref_record ref = {\n      \t\t\t.refname = name,\n      \t\t\t.update_index = reftable_stack_next_update_index(st1),\n      \t\t\t.value_type = REFTABLE_REF_SYMREF,\n     -\t\t\t.value.symref = \"master\",\n    -+\t\t\t.value.symref = (char *)\"master\",\n    ++\t\t\t.value.symref = master,\n      \t\t};\n      \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n      \n 3:  8f3decbb76 !  3:  8b71dfa208 global: convert intentionally-leaking config strings to consts\n    @@ Commit message\n             configured via `diff.<driver>.*` to add additional drivers. Again,\n             these have a global lifetime and are never free'd.\n     \n    -    All of these are intentionally kept alive and never free'd. Let's mark\n    -    the respective fields as `const char *` and cast away the constness when\n    -    assigning those values.\n    +    All of these are intentionally kept alive and never free'd. Furthermore,\n    +    all of these are being assigned both string constants in some places,\n    +    and allocated strings in other places. This will cause warnings once we\n    +    enable `-Wwrite-strings`, so let's mark the respective fields as `const\n    +    char *` and cast away the constness when assigning those values.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n 4:  ba50e49f86 =  4:  961b3357d5 compat/win32: fix const-correctness with string constants\n 6:  0eaa73c109 =  5:  b73a45133b refspec: remove global tag refspec structure\n 7:  03b13c449b =  6:  6da87a0905 http: do not assign string constant to non-const field\n 8:  699eeae92c =  7:  3da7df97a5 line-log: always allocate the output prefix\n 9:  6cbb8444a6 =  8:  e5d14a5173 object-file: make `buf` parameter of `index_mem()` a constant\n10:  c07b27bbb4 =  9:  dd40c7464d parse-options: cast long name for OPTION_ALIAS\n11:  3cd28ae38c = 10:  462502127d send-pack: always allocate receive status\n12:  00b4a7dbbc ! 11:  884fbe1da5 remote-curl: avoid assigning string constant to non-const variable\n    @@ remote-curl.c: int cmd_main(int argc, const char **argv)\n      \n      \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n     -\t\t\tchar *value = strchr(arg, ' ');\n    -+\t\t\tconst char *value = strchr(arg, ' ');\n    -+\t\t\tsize_t arglen;\n    ++\t\t\tconst char *value = strchrnul(arg, ' ');\n    ++\t\t\tsize_t arglen = value - arg;\n      \t\t\tint result;\n      \n     -\t\t\tif (value)\n     -\t\t\t\t*value++ = '\\0';\n    --\t\t\telse\n    -+\t\t\tif (value) {\n    -+\t\t\t\targlen = value - arg;\n    -+\t\t\t\tvalue++;\n    -+\t\t\t} else {\n    -+\t\t\t\targlen = strlen(arg);\n    ++\t\t\tif (*value)\n    ++\t\t\t\tvalue++; /* skip over SP */\n    + \t\t\telse\n      \t\t\t\tvalue = \"true\";\n    -+\t\t\t}\n      \n     -\t\t\tresult = set_option(arg, value);\n     +\t\t\tresult = set_option(arg, arglen, value);\n13:  68a7d24e4a = 12:  502380c2ca revision: always store allocated strings in output encoding\n14:  0e393fa6a7 = 13:  ffacdc3779 mailmap: always store allocated strings in mailmap blob\n15:  18ba9f7b3b = 14:  c0fce9b87e imap-send: drop global `imap_server_conf` variable\n16:  357d69fa8b ! 15:  e0a5b83f0e imap-send: fix leaking memory in `imap_server_conf`\n    @@ Commit message\n         `struct imap_server_conf`. Fix this by creating a common exit path where\n         we can free resources.\n     \n    -    While at it, drop the unused variables `imap_server_conf::name` and\n    -    `nongit_ok`.\n    +    While at it, drop the unused member `imap_server_conf::name`.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ imap-send.c: static int git_imap_config(const char *var, const char *val,\n      \treturn 0;\n      }\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n    - \t};\n      \tstruct strbuf all_msgs = STRBUF_INIT;\n      \tint total;\n    --\tint nongit_ok;\n    + \tint nongit_ok;\n     +\tint ret;\n      \n    --\tsetup_git_directory_gently(&nongit_ok);\n    -+\tsetup_git_directory_gently(NULL);\n    + \tsetup_git_directory_gently(&nongit_ok);\n      \tgit_config(git_imap_config, &server);\n    - \n    - \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \n      \tif (!server.folder) {\n 2:  51ee5660a1 ! 16:  36a7b0a4b0 global: assign non-const strings as required\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    global: assign non-const strings as required\n    +    builtin/rebase: do not assign default backend to non-constant field\n     \n    -    There are several cases where we initialize non-const fields with string\n    -    constants. This is invalid and will cause warnings once we enable the\n    -    `-Wwrite-strings` warning. Adapt those cases to instead use string\n    -    arrays.\n    +    The `struct rebase_options::default_backend` field is a non-constant\n    +    string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\n    +    Refactor the code to initialize and release options via two functions\n    +    `rebase_options_init()` and `rebase_options_release()`. Like this, we\n    +    can easily adapt the former funnction to use `xstrdup()` on the default\n    +    value without hiding it away in a macro.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    - ## builtin/remote.c ##\n    -@@ builtin/remote.c: static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n    - \tstruct ref *ref, *matches;\n    - \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n    - \tstruct refspec_item refspec;\n    -+\tchar refspec_str[] = \"refs/heads/*\";\n    - \n    - \tmemset(&refspec, 0, sizeof(refspec));\n    - \trefspec.force = 0;\n    - \trefspec.pattern = 1;\n    --\trefspec.src = refspec.dst = \"refs/heads/*\";\n    -+\trefspec.src = refspec.dst = refspec_str;\n    - \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n    - \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n    - \t\t\t\t    fetch_map, 1);\n    -\n    - ## diff.c ##\n    -@@ diff.c: size_t fill_textconv(struct repository *r,\n    - \t\t     struct diff_filespec *df,\n    - \t\t     char **outbuf)\n    - {\n    -+\tstatic char empty_str[] = \"\";\n    - \tsize_t size;\n    - \n    - \tif (!driver) {\n    - \t\tif (!DIFF_FILE_VALID(df)) {\n    --\t\t\t*outbuf = \"\";\n    -+\t\t\t*outbuf = empty_str;\n    - \t\t\treturn 0;\n    - \t\t}\n    - \t\tif (diff_populate_filespec(r, df, NULL))\n    -\n    - ## entry.c ##\n    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    - \tstruct string_list_item *filter, *path;\n    - \tstruct progress *progress = NULL;\n    - \tstruct delayed_checkout *dco = state->delayed_checkout;\n    -+\tchar empty_str[] = \"\";\n    - \n    - \tif (!state->delayed_checkout)\n    - \t\treturn errs;\n    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    - \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n    - \t\t\t\t/* Filter reported an error */\n    - \t\t\t\terrs = 1;\n    --\t\t\t\tfilter->string = \"\";\n    -+\t\t\t\tfilter->string = empty_str;\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    - \t\t\tif (available_paths.nr <= 0) {\n    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    - \t\t\t\t * filter from the list (see\n    - \t\t\t\t * \"string_list_remove_empty_items\" call below).\n    - \t\t\t\t */\n    --\t\t\t\tfilter->string = \"\";\n    -+\t\t\t\tfilter->string = empty_str;\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    + ## builtin/rebase.c ##\n    +@@ builtin/rebase.c: struct rebase_options {\n    + \tint config_update_refs;\n    + };\n      \n    -@@ entry.c: int finish_delayed_checkout(struct checkout *state, int show_progress)\n    - \t\t\t\t\t * Do not ask the filter for available blobs,\n    - \t\t\t\t\t * again, as the filter is likely buggy.\n    - \t\t\t\t\t */\n    --\t\t\t\t\tfilter->string = \"\";\n    -+\t\t\t\t\tfilter->string = empty_str;\n    - \t\t\t\t\tcontinue;\n    - \t\t\t\t}\n    - \t\t\t\tce = index_file_exists(state->istate, path->string,\n    -\n    - ## ident.c ##\n    -@@ ident.c: static struct passwd *xgetpwuid_self(int *is_bogus)\n    - \tpw = getpwuid(getuid());\n    - \tif (!pw) {\n    - \t\tstatic struct passwd fallback;\n    --\t\tfallback.pw_name = \"unknown\";\n    -+\t\tstatic char fallback_name[] = \"unknown\";\n    - #ifndef NO_GECOS_IN_PWENT\n    --\t\tfallback.pw_gecos = \"Unknown\";\n    -+\t\tstatic char fallback_gcos[] = \"Unknown\";\n    -+#endif\n    +-#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n    +-\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n    +-\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n    +-\t\t.keep_empty = 1,\t\t\t\\\n    +-\t\t.default_backend = \"merge\",\t  \t\\\n    +-\t\t.flags = REBASE_NO_QUIET, \t\t\\\n    +-\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n    +-\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n    +-\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n    +-\t\t.fork_point = -1,\t\t\t\\\n    +-\t\t.reapply_cherry_picks = -1,             \\\n    +-\t\t.allow_empty_message = 1,               \\\n    +-\t\t.autosquash = -1,                       \\\n    +-\t\t.rebase_merges = -1,                    \\\n    +-\t\t.config_rebase_merges = -1,             \\\n    +-\t\t.update_refs = -1,                      \\\n    +-\t\t.config_update_refs = -1,               \\\n    +-\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n    +-\t}\n    ++static void rebase_options_init(struct rebase_options *opts)\n    ++{\n    ++\tmemset(opts, 0, sizeof(*opts));\n    ++\topts->type = REBASE_UNSPECIFIED;\n    ++\topts->empty = EMPTY_UNSPECIFIED;\n    ++\topts->default_backend = xstrdup(\"merge\");\n    ++\topts->keep_empty = 1;\n    ++\topts->flags = REBASE_NO_QUIET;\n    ++\tstrvec_init(&opts->git_am_opts);\n    ++\tstring_list_init_nodup(&opts->exec);\n    ++\tstrbuf_init(&opts->git_format_patch_opt, 0);\n    ++\topts->fork_point = -1;\n    ++\topts->reapply_cherry_picks = -1;\n    ++\topts->allow_empty_message = 1;\n    ++\topts->autosquash = -1;\n    ++\topts->rebase_merges = -1;\n    ++\topts->config_rebase_merges = -1;\n    ++\topts->update_refs = -1;\n    ++\topts->config_update_refs = -1;\n    ++\tstring_list_init_nodup(&opts->strategy_opts);\n    ++}\n     +\n    -+\t\tfallback.pw_name = fallback_name;\n    -+#ifndef NO_GECOS_IN_PWENT\n    -+\t\tfallback.pw_gecos = fallback_gcos;\n    - #endif\n    - \t\tpw = &fallback;\n    - \t\tif (is_bogus)\n    -\n    - ## line-log.c ##\n    -@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,\n    - \tstruct range_set tmp;\n    - \tstruct diff_ranges diff;\n    - \tmmfile_t file_parent, file_target;\n    -+\tchar empty_str[] = \"\";\n    ++static void rebase_options_release(struct rebase_options *opts)\n    ++{\n    ++\tfree(opts->default_backend);\n    ++\tfree(opts->reflog_action);\n    ++\tfree(opts->head_name);\n    ++\tstrvec_clear(&opts->git_am_opts);\n    ++\tfree(opts->gpg_sign_opt);\n    ++\tstring_list_clear(&opts->exec, 0);\n    ++\tfree(opts->strategy);\n    ++\tstring_list_clear(&opts->strategy_opts, 0);\n    ++\tstrbuf_release(&opts->git_format_patch_opt);\n    ++}\n      \n    - \tassert(pair->two->path);\n    - \twhile (rg) {\n    -@@ line-log.c: static int process_diff_filepair(struct rev_info *rev,\n    - \t\tfile_parent.ptr = pair->one->data;\n    - \t\tfile_parent.size = pair->one->size;\n    - \t} else {\n    --\t\tfile_parent.ptr = \"\";\n    -+\t\tfile_parent.ptr = empty_str;\n    - \t\tfile_parent.size = 0;\n    + static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n    + {\n    +@@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,\n      \t}\n      \n    -\n    - ## object-file.c ##\n    -@@ object-file.c: static struct cached_object {\n    - } *cached_objects;\n    - static int cached_object_nr, cached_object_alloc;\n    - \n    -+static char empty_tree_buf[] = \"\";\n    - static struct cached_object empty_tree = {\n    - \t.oid = {\n    - \t\t.hash = EMPTY_TREE_SHA1_BIN_LITERAL,\n    - \t},\n    - \t.type = OBJ_TREE,\n    --\t.buf = \"\",\n    -+\t.buf = empty_tree_buf,\n    - };\n    + \tif (!strcmp(var, \"rebase.backend\")) {\n    ++\t\tFREE_AND_NULL(opts->default_backend);\n    + \t\treturn git_config_string(&opts->default_backend, var, value);\n    + \t}\n      \n    - static struct cached_object *find_cached_object(const struct object_id *oid)\n    -\n    - ## pretty.c ##\n    -@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n    - \t\treturn 1;\n    - \tcase 'D':\n    - \t\t{\n    -+\t\t\tchar empty_str[] = \"\";\n    - \t\t\tconst struct decoration_options opts = {\n    --\t\t\t\t.prefix = \"\",\n    --\t\t\t\t.suffix = \"\"\n    -+\t\t\t\t.prefix = empty_str,\n    -+\t\t\t\t.suffix = empty_str,\n    - \t\t\t};\n    +@@ builtin/rebase.c: static int check_exec_cmd(const char *cmd)\n      \n    - \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n    -\n    - ## refs/reftable-backend.c ##\n    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    - \tstruct strbuf errbuf = STRBUF_INIT;\n    - \tsize_t logs_nr = 0, logs_alloc = 0, i;\n    - \tconst char *committer_info;\n    -+\tchar head[] = \"HEAD\";\n    - \tint ret;\n    + int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + {\n    +-\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n    ++\tstruct rebase_options options;\n    + \tconst char *branch_name;\n    + \tint ret, flags, total_argc, in_progress = 0;\n    + \tint keep_base = 0;\n    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + \t};\n    + \tint i;\n      \n    - \tcommitter_info = git_committer_info(0);\n    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    - \t\tif (append_head_reflog) {\n    - \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n    - \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n    --\t\t\tlogs[logs_nr].refname = \"HEAD\";\n    -+\t\t\tlogs[logs_nr].refname = head;\n    - \t\t\tlogs_nr++;\n    - \t\t}\n    - \t}\n    -@@ refs/reftable-backend.c: static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n    - \tstring_list_clear(&skip, 0);\n    - \tstrbuf_release(&errbuf);\n    - \tfor (i = 0; i < logs_nr; i++) {\n    --\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n    -+\t\tif (logs[i].refname == head)\n    - \t\t\tcontinue;\n    - \t\tlogs[i].refname = NULL;\n    - \t\treftable_log_record_release(&logs[i]);\n    ++\trebase_options_init(&options);\n    ++\n    + \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n    + \t\tusage_with_options(builtin_rebase_usage,\n    + \t\t\t\t   builtin_rebase_options);\n    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + cleanup:\n    + \tstrbuf_release(&buf);\n    + \tstrbuf_release(&revisions);\n    +-\tfree(options.reflog_action);\n    +-\tfree(options.head_name);\n    +-\tstrvec_clear(&options.git_am_opts);\n    +-\tfree(options.gpg_sign_opt);\n    +-\tstring_list_clear(&options.exec, 0);\n    +-\tfree(options.strategy);\n    +-\tstring_list_clear(&options.strategy_opts, 0);\n    +-\tstrbuf_release(&options.git_format_patch_opt);\n    ++\trebase_options_release(&options);\n    + \tfree(squash_onto_name);\n    + \tfree(keep_base_onto_name);\n    + \treturn !!ret;\n17:  16d3d28243 ! 17:  3552ab9748 builtin/rebase: adapt code to not assign string constants to non-const\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    builtin/rebase: adapt code to not assign string constants to non-const\n    +    builtin/rebase: always store allocated string in `options.strategy`\n     \n    -    When computing the rebase strategy we temporarily assign a string\n    -    constant to `options.strategy` before we call `xstrdup()` on it.\n    -    Furthermore, the default backend is being assigned a string constant via\n    -    `REBASE_OPTIONS_INIT`. Both of these will cause warnings once we enable\n    -    `-Wwrite-strings`.\n    +    The `struct rebase_options::strategy` field is a `char *`, but we do end\n    +    up assigning string constants to it in two cases:\n     \n    -    Adapt the code such that we only store allocated strings in those\n    -    variables.\n    +      - When being passed a `--strategy=` option via the command line.\n    +\n    +      - When being passed a strategy option via `--strategy-option=`, but\n    +        not a strategy.\n    +\n    +    This will cause warnings once we enable `-Wwrite-strings`.\n    +\n    +    Ideally, we'd just convert the field to be a `const char *`. But we also\n    +    assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\n    +    have to strdup(3P) into it.\n    +\n    +    Instead, refactor the code to make sure that we only ever assign\n    +    allocated strings to this field.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## builtin/rebase.c ##\n    -@@ builtin/rebase.c: struct rebase_options {\n    - \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n    - \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n    - \t\t.keep_empty = 1,\t\t\t\\\n    --\t\t.default_backend = \"merge\",\t  \t\\\n    - \t\t.flags = REBASE_NO_QUIET, \t\t\\\n    - \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n    - \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n    -@@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,\n    - \t}\n    - \n    - \tif (!strcmp(var, \"rebase.backend\")) {\n    -+\t\tFREE_AND_NULL(opts->default_backend);\n    - \t\treturn git_config_string(&opts->default_backend, var, value);\n    - \t}\n    - \n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + {\n    + \tstruct rebase_options options;\n    + \tconst char *branch_name;\n    ++\tconst char *strategy_opt = NULL;\n    + \tint ret, flags, total_argc, in_progress = 0;\n    + \tint keep_base = 0;\n    + \tint ok_to_skip_pre_rebase = 0;\n    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n    + \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n    + \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n    +-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n    ++\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n    + \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n    + \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n    + \t\t\t\tN_(\"option\"),\n    +@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    + \t\t}\n      \t}\n      \n    - \tif (options.strategy_opts.nr && !options.strategy)\n    +-\tif (options.strategy_opts.nr && !options.strategy)\n     -\t\toptions.strategy = \"ort\";\n     -\n     -\tif (options.strategy) {\n     -\t\toptions.strategy = xstrdup(options.strategy);\n    ++\tif (strategy_opt)\n    ++\t\toptions.strategy = xstrdup(strategy_opt);\n    ++\telse if (options.strategy_opts.nr && !options.strategy)\n     +\t\toptions.strategy = xstrdup(\"ort\");\n    -+\telse\n    -+\t\toptions.strategy = xstrdup_or_null(options.strategy);\n     +\tif (options.strategy)\n      \t\timply_merge(&options, \"--strategy\");\n     -\t}\n      \n      \tif (options.root && !options.onto_name)\n      \t\timply_merge(&options, \"--root without --onto\");\n    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \t}\n    - \n    - \tif (options.type == REBASE_UNSPECIFIED) {\n    --\t\tif (!strcmp(options.default_backend, \"merge\"))\n    -+\t\tif (!options.default_backend)\n    -+\t\t\toptions.type = REBASE_MERGE;\n    -+\t\telse if (!strcmp(options.default_backend, \"merge\"))\n    - \t\t\toptions.type = REBASE_MERGE;\n    - \t\telse if (!strcmp(options.default_backend, \"apply\"))\n    - \t\t\toptions.type = REBASE_APPLY;\n    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - cleanup:\n    - \tstrbuf_release(&buf);\n    - \tstrbuf_release(&revisions);\n    -+\tfree(options.default_backend);\n    - \tfree(options.reflog_action);\n    - \tfree(options.head_name);\n    - \tstrvec_clear(&options.git_am_opts);\n18:  129482dbaa = 18:  bf854b3979 builtin/merge: always store allocated strings in `pull_twohead`\n19:  37e7aaed97 = 19:  9b9d57ae84 config.mak.dev: enable `-Wwrite-strings` warning\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495936","messageId":"25c31e550fcecdda7510b259b271cd45d036f5d7.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 01/19] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:35Z","receivedAt":"2024-05-30T12:50:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/record.c            |  6 ++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 33 files changed, 86 insertions(+), 81 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495937","messageId":"3430bcc09bbb4a9fe86ba40b0b3a0d4864c92e4f.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:40Z","receivedAt":"2024-05-30T12:50:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are several cases where we initialize non-const fields with string\nconstants. This is invalid and will cause warnings once we enable the\n`-Wwrite-strings` warning. Adapt those cases to instead use string\narrays.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c          |  3 +-\n diff.c                    |  3 +-\n entry.c                   |  7 ++--\n ident.c                   |  9 +++-\n line-log.c                |  3 +-\n object-file.c             |  3 +-\n pretty.c                  |  5 ++-\n refs/reftable-backend.c   |  5 ++-\n reftable/basics_test.c    |  5 ++-\n reftable/block_test.c     |  4 +-\n reftable/merged_test.c    | 52 ++++++++++++-----------\n reftable/readwrite_test.c | 60 ++++++++++++++++-----------\n reftable/stack_test.c     | 87 +++++++++++++++++++++------------------\n 13 files changed, 144 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..0324a5d48d 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -494,11 +494,12 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n \tstruct refspec_item refspec;\n+\tchar refspec_str[] = \"refs/heads/*\";\n \n \tmemset(&refspec, 0, sizeof(refspec));\n \trefspec.force = 0;\n \trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n+\trefspec.src = refspec.dst = refspec_str;\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..1439a5a01d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7231,11 +7231,12 @@ size_t fill_textconv(struct repository *r,\n \t\t     struct diff_filespec *df,\n \t\t     char **outbuf)\n {\n+\tstatic char empty_str[] = \"\";\n \tsize_t size;\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = empty_str;\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..2fc06ac90c 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -175,6 +175,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \tstruct string_list_item *filter, *path;\n \tstruct progress *progress = NULL;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n+\tchar empty_str[] = \"\";\n \n \tif (!state->delayed_checkout)\n \t\treturn errs;\n@@ -189,7 +190,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = empty_str;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +200,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = empty_str;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +226,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = empty_str;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..df7aa42802 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,14 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tstatic char fallback_name[] = \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tstatic char fallback_gcos[] = \"Unknown\";\n+#endif\n+\n+\t\tfallback.pw_name = fallback_name;\n+#ifndef NO_GECOS_IN_PWENT\n+\t\tfallback.pw_gecos = fallback_gcos;\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..d9bf2c8120 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar empty_str[] = \"\";\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = empty_str;\n \t\tfile_parent.size = 0;\n \t}\n \ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..c9e374e57e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -282,12 +282,13 @@ static struct cached_object {\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n \n+static char empty_tree_buf[] = \"\";\n static struct cached_object empty_tree = {\n \t.oid = {\n \t\t.hash = EMPTY_TREE_SHA1_BIN_LITERAL,\n \t},\n \t.type = OBJ_TREE,\n-\t.buf = \"\",\n+\t.buf = empty_tree_buf,\n };\n \n static struct cached_object *find_cached_object(const struct object_id *oid)\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1a0030b32a 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1583,9 +1583,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\treturn 1;\n \tcase 'D':\n \t\t{\n+\t\t\tchar empty_str[] = \"\";\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = empty_str,\n+\t\t\t\t.suffix = empty_str,\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..1908e74dea 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1285,6 +1285,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tstruct strbuf errbuf = STRBUF_INIT;\n \tsize_t logs_nr = 0, logs_alloc = 0, i;\n \tconst char *committer_info;\n+\tchar head[] = \"HEAD\";\n \tint ret;\n \n \tcommitter_info = git_committer_info(0);\n@@ -1387,7 +1388,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = head;\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1463,7 +1464,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n \tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n+\t\tif (logs[i].refname == head)\n \t\t\tcontinue;\n \t\tlogs[i].refname = NULL;\n \t\treftable_log_record_release(&logs[i]);\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..23fab22eb1 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,9 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tchar a[] = \"a\", b[] = \"b\";\n+\tchar *names[] = { a, b, NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..d5967b214d 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -19,7 +19,7 @@ license that can be found in the LICENSE file or at\n static void test_block_read_write(void)\n {\n \tconst int header_off = 21; /* random */\n-\tchar *names[30];\n+\tchar *names[30], empty_str[] = \"\";\n \tconst int N = ARRAY_SIZE(names);\n \tconst int block_size = 1024;\n \tstruct reftable_block block = { NULL };\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = empty_str;\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..fd5a065e42 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -123,14 +123,15 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n \n static void test_merged_between(void)\n {\n+\tchar a[] = \"a\", b[] = \"b\";\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = b,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = a,\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -163,40 +164,41 @@ static void test_merged_between(void)\n \n static void test_merged(void)\n {\n+\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = b,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = c,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = a,\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = c,\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = d,\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -289,48 +291,52 @@ merged_table_from_log_records(struct reftable_log_record **logs,\n \n static void test_merged_logs(void)\n {\n+\tchar a[] = \"a\";\n+\tchar name[] = \"jane doe\", email[] = \"jane@invalid\";\n+\tchar message1[] = \"message1\", message2[] = \"message2\";\n+\tchar message3[] = \"message3\";\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = name,\n+\t\t\t\t.email = email,\n+\t\t\t\t.message = message2,\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = name,\n+\t\t\t\t.email = email,\n+\t\t\t\t.message = message1,\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = name,\n+\t\t\t\t.email = email,\n+\t\t\t\t.message = message3,\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -400,13 +406,13 @@ static void test_merged_logs(void)\n \n static void test_default_write_opts(void)\n {\n+\tchar master[] = \"master\";\n \tstruct reftable_write_options opts = { 0 };\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n-\n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = master,\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..064d693111 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -56,6 +56,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \tint i = 0, n;\n \tstruct reftable_log_record log = { NULL };\n \tconst struct reftable_stats *stats = NULL;\n+\tchar message[] = \"message\";\n \n \tREFTABLE_CALLOC_ARRAY(*names, N + 1);\n \n@@ -86,7 +87,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = message;\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -111,23 +112,28 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \n static void test_log_buffer_size(void)\n {\n+\tchar refname[] = \"refs/heads/master\";\n+\tchar name[] = \"Han-Wen Hienhuys\";\n+\tchar email[] = \"hanwen@google.com\";\n+\tchar message[] = \"commit: 9\\n\";\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_write_options opts = {\n \t\t.block_size = 4096,\n \t};\n \tint err;\n \tint i;\n-\tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n-\t\t\t.update_index = 0xa,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n-\t\t\t\t\t   .tz_offset = 100,\n-\t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n-\t\t\t\t   } } };\n+\tstruct reftable_log_record log = {\n+\t\t.refname = refname,\n+\t\t.update_index = 0xa,\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value.update = {\n+\t\t\t.name = name,\n+\t\t\t.email = email,\n+\t\t\t.tz_offset = 100,\n+\t\t\t.time = 0x5e430672,\n+\t\t\t.message = message,\n+\t\t},\n+\t};\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n@@ -149,6 +155,9 @@ static void test_log_buffer_size(void)\n \n static void test_log_overflow(void)\n {\n+\tchar refname[] = \"refs/heads/master\";\n+\tchar name[] = \"Han-Wen Hienhuys\";\n+\tchar email[] = \"hanwen@google.com\";\n \tstruct strbuf buf = STRBUF_INIT;\n \tchar msg[256] = { 0 };\n \tstruct reftable_write_options opts = {\n@@ -156,15 +165,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = refname,\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = name,\n+\t\t\t\t.email = email,\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -290,17 +299,20 @@ static void test_log_zlib_corruption(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tconst struct reftable_stats *stats = NULL;\n+\tchar refname[] = \"refname\";\n+\tchar name[] = \"My Name\";\n+\tchar email[] = \"myname@invalid\";\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = refname,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = name,\n+\t\t\t\t.email = email,\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -727,8 +739,9 @@ static void test_write_empty_key(void)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n+\tchar refname[] = \"\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = refname,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -750,20 +763,21 @@ static void test_write_key_order(void)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n+\tchar a[] = \"a\", b[] = \"b\", target[] = \"target\";\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = b,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = target,\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = a,\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = target,\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..c6d88e6ea8 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,13 +116,14 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n-\n-\tEXPECT(names_equal(a, a));\n-\tEXPECT(!names_equal(a, b));\n-\tEXPECT(!names_equal(a, c));\n+\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n+\tchar *abc[] = { a, b, c, NULL };\n+\tchar *abd[] = { a, b, d, NULL };\n+\tchar *ab[] = { a, b, NULL };\n+\n+\tEXPECT(names_equal(abc, abc));\n+\tEXPECT(!names_equal(abc, abd));\n+\tEXPECT(!names_equal(abc, ab));\n }\n \n static int write_test_ref(struct reftable_writer *wr, void *arg)\n@@ -155,11 +156,12 @@ static void test_reftable_stack_add_one(void)\n \t};\n \tstruct reftable_stack *st = NULL;\n \tint err;\n+\tchar head[] = \"HEAD\", master[] = \"master\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = head,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -215,17 +217,18 @@ static void test_reftable_stack_uptodate(void)\n \tchar *dir = get_tmp_dir(__LINE__);\n \n \tint err;\n+\tchar head[] = \"HEAD\", branch2[] = \"branch2\", master[] = \"master\";\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = head,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = branch2,\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \n \n@@ -262,12 +265,12 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_addition *add = NULL;\n-\n+\tchar head[] = \"HEAD\", master[] = \"master\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = head,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -310,10 +313,11 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i <= n; i++) {\n+\t\tchar master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = master,\n \t\t};\n \t\tchar name[100];\n \n@@ -355,8 +359,9 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n+\tchar master[] = \"refs/meads/master\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = master,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -404,21 +409,21 @@ static int write_error(struct reftable_writer *wr, void *arg)\n static void test_reftable_stack_update_index_check(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\n \tstruct reftable_write_options cfg = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tint err;\n+\tchar name1[] = \"name1\", name2[] = \"name2\", master[] = \"master\";\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = name1,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = name2,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = master,\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -560,8 +565,12 @@ static void test_reftable_stack_log_normalize(void)\n \t};\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n+\tchar branch[] = \"branch\";\n+\tchar onetwomessage[] = \"one\\ntwo\";\n+\tchar onemessage[] = \"one\";\n+\tchar twomessage[] = \"two\\n\";\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = branch,\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +591,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = onetwomessage;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = onemessage;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +603,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = twomessage;\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -691,15 +700,14 @@ static void test_reftable_stack_tombstone(void)\n static void test_reftable_stack_hash_id(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n-\n \tstruct reftable_write_options cfg = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tint err;\n-\n+\tchar master[] = \"master\", target[] = \"target\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = master,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = target,\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -874,12 +882,12 @@ static void test_reftable_stack_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100];\n+\t\tchar name[100], master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = master,\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -910,10 +918,11 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i <= n; i++) {\n+\t\tchar master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = master,\n \t\t};\n \n \t\t/*\n@@ -959,12 +968,12 @@ static void test_reftable_stack_compaction_concurrent(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100];\n+\t\tchar name[100], master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = master,\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1009,12 +1018,12 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100];\n+\t\tchar name[100], master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = master,\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495938","messageId":"8b71dfa208c14600e4f9164017acda10627ba70a.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 03/19] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:44Z","receivedAt":"2024-05-30T12:50:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and will never be free'd, it is fine\n    to treat the program as a quasi-constant.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never free'd.\n\nAll of these are intentionally kept alive and never free'd. Furthermore,\nall of these are being assigned both string constants in some places,\nand allocated strings in other places. This will cause warnings once we\nenable `-Wwrite-strings`, so let's mark the respective fields as `const\nchar *` and cast away the constness when assigning those values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495939","messageId":"961b3357d57296cb236c3832e1823ed44320a44c.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 04/19] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:49Z","receivedAt":"2024-05-30T12:50:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 15 +++++++++++++--\n compat/mingw.c    | 25 +++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 27 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..c3c9d65fac 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -1,6 +1,13 @@\n #include \"../git-compat-util.h\"\n #include \"../strbuf.h\"\n \n+/*\n+ * Both basename(3P) and dirname(3P) are mis-specified because they return a\n+ * non-constant pointer even though it is specified that they may return a\n+ * pointer to internal memory. This variable here is a result of that.\n+ */\n+static char current_directory[] = \".\";\n+\n /* Adapted from libiberty's basename.c.  */\n char *gitbasename (char *path)\n {\n@@ -10,7 +17,7 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\treturn current_directory;\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -33,8 +40,12 @@ char *gitdirname(char *path)\n \tchar *p = path, *slash = NULL, c;\n \tint dos_drive_prefix;\n \n+\t/*\n+\t * Same here, dirname(3P) is broken because it returns a non-constant\n+\t * pointer that may point to internal memory.\n+\t */\n \tif (!p)\n-\t\treturn \".\";\n+\t\treturn current_directory;\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..60f0986f76 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2257,6 +2257,7 @@ struct passwd *getpwuid(int uid)\n {\n \tstatic unsigned initialized;\n \tstatic char user_name[100];\n+\tstatic char unknown[] = \"unknown\";\n \tstatic struct passwd *p;\n \twchar_t buf[100];\n \tDWORD len;\n@@ -2279,7 +2280,7 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\tp->pw_gecos = unknown;\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2801,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2823,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495940","messageId":"b73a45133b3984a810c6c5e46924e3eb517d0fb4.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 05/19] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:54Z","receivedAt":"2024-05-30T12:50:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization fo the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495941","messageId":"6da87a09056f9394387c61882586010976d07bf5.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 06/19] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:50:59Z","receivedAt":"2024-05-30T12:51:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495942","messageId":"3da7df97a5f80d3ca2a4e2a597c11f6105b6a152.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 07/19] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:03Z","receivedAt":"2024-05-30T12:51:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex d9bf2c8120..9a298209d0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495943","messageId":"e5d14a51738cd4972f35aa30f7b00ab4e6060d31.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 08/19] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:08Z","receivedAt":"2024-05-30T12:51:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex c9e374e57e..46ea00ac46 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2483,12 +2483,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2498,11 +2499,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2519,8 +2519,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495944","messageId":"dd40c7464d642d16e0d7f84936be1076b99e8a6c.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 09/19] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:13Z","receivedAt":"2024-05-30T12:51:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495945","messageId":"462502127d8bdfc77f9e4b8ba975f41fb12b3476.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 10/19] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:18Z","receivedAt":"2024-05-30T12:51:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495946","messageId":"884fbe1da59cdaf092b14b2e4c70b8f8c931f4de.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 11/19] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:23Z","receivedAt":"2024-05-30T12:51:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..d0f767df8e 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchrnul(arg, ' ');\n+\t\t\tsize_t arglen = value - arg;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n+\t\t\tif (*value)\n+\t\t\t\tvalue++; /* skip over SP */\n \t\t\telse\n \t\t\t\tvalue = \"true\";\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495947","messageId":"502380c2cade79303980e9b6023048f5c21bf132.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 12/19] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:28Z","receivedAt":"2024-05-30T12:51:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495948","messageId":"ffacdc37795dd5350c5a3e144eaaf5272523a499.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 13/19] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:33Z","receivedAt":"2024-05-30T12:51:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495949","messageId":"c0fce9b87eea3d4dfc5b2ae5eb947f7d1495491a.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 14/19] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:38Z","receivedAt":"2024-05-30T12:51:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495950","messageId":"e0a5b83f0e5179f84b2c7fdb80cd01722bddd0b0.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 15/19] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:43Z","receivedAt":"2024-05-30T12:51:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused member `imap_server_conf::name`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 42 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..da3e7ec17e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n+\tint ret;\n \n \tsetup_git_directory_gently(&nongit_ok);\n \tgit_config(git_imap_config, &server);\n@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495951","messageId":"36a7b0a4b074e71dc07412d6d7c509dd4d90723e.1717073346.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 16/19] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:48Z","receivedAt":"2024-05-30T12:51:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::default_backend` field is a non-constant\nstring, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\nRefactor the code to initialize and release options via two functions\n`rebase_options_init()` and `rebase_options_release()`. Like this, we\ncan easily adapt the former funnction to use `xstrdup()` on the default\nvalue without hiding it away in a macro.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..11f276012c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -131,25 +131,40 @@ struct rebase_options {\n \tint config_update_refs;\n };\n \n-#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n-\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n-\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n-\t\t.flags = REBASE_NO_QUIET, \t\t\\\n-\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n-\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n-\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n-\t\t.fork_point = -1,\t\t\t\\\n-\t\t.reapply_cherry_picks = -1,             \\\n-\t\t.allow_empty_message = 1,               \\\n-\t\t.autosquash = -1,                       \\\n-\t\t.rebase_merges = -1,                    \\\n-\t\t.config_rebase_merges = -1,             \\\n-\t\t.update_refs = -1,                      \\\n-\t\t.config_update_refs = -1,               \\\n-\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n-\t}\n+static void rebase_options_init(struct rebase_options *opts)\n+{\n+\tmemset(opts, 0, sizeof(*opts));\n+\topts->type = REBASE_UNSPECIFIED;\n+\topts->empty = EMPTY_UNSPECIFIED;\n+\topts->default_backend = xstrdup(\"merge\");\n+\topts->keep_empty = 1;\n+\topts->flags = REBASE_NO_QUIET;\n+\tstrvec_init(&opts->git_am_opts);\n+\tstring_list_init_nodup(&opts->exec);\n+\tstrbuf_init(&opts->git_format_patch_opt, 0);\n+\topts->fork_point = -1;\n+\topts->reapply_cherry_picks = -1;\n+\topts->allow_empty_message = 1;\n+\topts->autosquash = -1;\n+\topts->rebase_merges = -1;\n+\topts->config_rebase_merges = -1;\n+\topts->update_refs = -1;\n+\topts->config_update_refs = -1;\n+\tstring_list_init_nodup(&opts->strategy_opts);\n+}\n+\n+static void rebase_options_release(struct rebase_options *opts)\n+{\n+\tfree(opts->default_backend);\n+\tfree(opts->reflog_action);\n+\tfree(opts->head_name);\n+\tstrvec_clear(&opts->git_am_opts);\n+\tfree(opts->gpg_sign_opt);\n+\tstring_list_clear(&opts->exec, 0);\n+\tfree(opts->strategy);\n+\tstring_list_clear(&opts->strategy_opts, 0);\n+\tstrbuf_release(&opts->git_format_patch_opt);\n+}\n \n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n {\n@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)\n \n int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n-\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n+\tstruct rebase_options options;\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tint i;\n \n+\trebase_options_init(&options);\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n-\tfree(options.reflog_action);\n-\tfree(options.head_name);\n-\tstrvec_clear(&options.git_am_opts);\n-\tfree(options.gpg_sign_opt);\n-\tstring_list_clear(&options.exec, 0);\n-\tfree(options.strategy);\n-\tstring_list_clear(&options.strategy_opts, 0);\n-\tstrbuf_release(&options.git_format_patch_opt);\n+\trebase_options_release(&options);\n \tfree(squash_onto_name);\n \tfree(keep_base_onto_name);\n \treturn !!ret;\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495952","messageId":"3552ab9748bec8efd62145f590e888aeed7c9b43.1717073347.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 17/19] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:53Z","receivedAt":"2024-05-30T12:51:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::strategy` field is a `char *`, but we do end\nup assigning string constants to it in two cases:\n\n  - When being passed a `--strategy=` option via the command line.\n\n  - When being passed a strategy option via `--strategy-option=`, but\n    not a strategy.\n\nThis will cause warnings once we enable `-Wwrite-strings`.\n\nIdeally, we'd just convert the field to be a `const char *`. But we also\nassign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\nhave to strdup(3P) into it.\n\nInstead, refactor the code to make sure that we only ever assign\nallocated strings to this field.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 11f276012c..26068cf542 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n \tstruct rebase_options options;\n \tconst char *branch_name;\n+\tconst char *strategy_opt = NULL;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n+\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n \t\t\t\tN_(\"option\"),\n@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\tif (strategy_opt)\n+\t\toptions.strategy = xstrdup(strategy_opt);\n+\telse if (options.strategy_opts.nr && !options.strategy)\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495953","messageId":"bf854b39794a9aa9f63471cc09c6f31685d1f0cf.1717073347.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 18/19] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:51:58Z","receivedAt":"2024-05-30T12:52:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495954","messageId":"9b9d57ae8432bc0f638bc784b23526d5a26627a5.1717073347.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"[PATCH v2 19/19] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-30T12:52:03Z","receivedAt":"2024-05-30T12:52:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.1.313.g3a57aa566a.dirty\n\n"},{"id":"495962","messageId":"xmqqfrtzqog5.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlhjRtRwxlnchOaV@tanuki","subject":"Re: [PATCH 03/19] global: convert intentionally-leaking config strings to consts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-30T16:00:58Z","receivedAt":"2024-05-30T16:01:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 29, 2024 at 10:28:05AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > There are multiple cases where we intentionally leak config strings:\n>> >\n>> >   - `struct gpg_format` is used to track programs that can be used for\n>> >     signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n>> >     user can override the commands via several config variables. As the\n>> >     array is populated once, only, and will never be free'd, it is fine\n>> >     to treat the program as a quasi-constant.\n>> >\n>> >   - `struct ll_merge_driver` is used to track merge drivers. Same as\n>> >     with the GPG format, these drivers are populated once and then\n>> >     reused. Its data is never free'd, either.\n>> >\n>> >   - `struct userdiff_funcname` and `struct userdiff_driver` can be\n>> >     configured via `diff.<driver>.*` to add additional drivers. Again,\n>> >     these have a global lifetime and are never free'd.\n>> >\n>> > All of these are intentionally kept alive and never free'd. Let's mark\n>> > the respective fields as `const char *` and cast away the constness when\n>> > assigning those values.\n>> \n>> It is not unclear where the linkage between \"not freed\" and \"must be\n>> const\" comes from.  What am I missing?\n>\n> It comes from `-Wwrite-strings`, which will mark string constants as\n> `const char *`. This will cause warnings in all of the above cases\n> because the fields are being assigned constants, but those fields are\n> currently `char *`. Will clarify.\n\nIn short, these warnings have nothing to do with the pointee by\nthese variables or struct members are eventually freed or not, no?\n\n"},{"id":"495963","messageId":"xmqq7cfbqo5m.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlhjVIEld7_T0q-M@tanuki","subject":"Re: [PATCH 05/19] reftable: improve const correctness when assigning string constants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-30T16:07:17Z","receivedAt":"2024-05-30T16:07:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 29, 2024 at 10:43:47AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > diff --git a/reftable/basics_test.c b/reftable/basics_test.c\n>> > index 997c4d9e01..af9209d535 100644\n>> > --- a/reftable/basics_test.c\n>> > +++ b/reftable/basics_test.c\n>> > @@ -58,8 +58,8 @@ static void test_binsearch(void)\n>> >  \n>> >  static void test_names_length(void)\n>> >  {\n>> > -\tchar *a[] = { \"a\", \"b\", NULL };\n>> > -\tEXPECT(names_length(a) == 2);\n>> > +\tchar *names[] = { (char *)\"a\", (char *)\"b\", NULL };\n>> > +\tEXPECT(names_length(names) == 2);\n>> >  }\n>> \n>> I would have preferred to see this kind of rewrite more than\n>> separate and clearly writable variables that are initialied with the\n>> constant contents e.g. branches[] = \"refs/heads/*\", we saw in\n>> earlier steps.  Wouldn't that approach, combined with making the\n>> literal constants stored in read-only segment to trigger runtime\n>> failure when a bug causes the \"unfortunately non-const\" variables\n>> to be written, give us a better result?\n>\n> Depends on what we mean by \"better\", I guess. But yeah, I was torn\n> myself when writing this commit because there are so many string\n> constants in the reftable tests that we assign to non-constant fields. I\n> didn't find the result particularly easy to read when putting each of\n> the constants into a separate variable.\n\nOh, I do *not* want to see.\n\n\tchar a_string[] = \"a\";\n\tchar *names[] = { a_string, ... };\n\nAs a way to workaround the -Wwritable-strings warnings, what we see\nin this patch is much better.\n\nI was referring to a redoing of this series in the other direction.\nThe earlier one that introduced\n\n\tchar refspec_string[] = \"refs/heads/*\";\n\nand rewrite assignments of\n\n\tnon_const_pointer_to_char = \"refs/heads/*\";\n\nwith\n\n\tnon_const_pointer_to_char = refspec_string;\n\nwould have been better if it were done without refspec_string[]\narray.  It is a runtime bug to overwrite the refspec_string[] via\nthe non_const_pointer_to_char, but use of refspec_string[] would\nmake it impossible for us to catch.\n\n\n"},{"id":"495964","messageId":"xmqqr0djp8i0.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlhjdPkEEwg7Wog7@tanuki","subject":"Re: [PATCH 16/19] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-30T16:30:47Z","receivedAt":"2024-05-30T16:30:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Oh, I wasn't aware that the parameter being `NULL` actually causes a\n> change in behaviour. Which nicely demonstrates that we have some missing\n> test coverage for git-imap-send(1).\n>\n> In fact, it's not only \"some\". We don't have any test coverage at all\n> for git-imap-send(1) as far as I can see. Which does make me rest a bit\n> uneasy. And I suspect that it wouldn't be trivial to add given that it\n> kind of requires something that talks IMAP on the receiving end.\n\nQuite honestly, there is absolutely nothing that makes imap-send\nnecessary to be part of Git suite.  It does depend on Git as it uses\nour configuration files to store its knobs, but if it were written\noutside the context of the Git project as a handy way to move a draft\nmessage to your imap draft folder, it would have been perfectly fine\nas a standalone tool (and it would have used its own simple\nconfiguration file format).\n\nOther than its use of .gitconfig, it does not even know what a\ncommit is, unlike, say send-email [*], which is also fairly\nunrelated to Git proper.\n\n    Side note: send-email is only marginally closer to Git than\n    imap-send, as it optionally can run format-patch as a part of\n    its operation.  Without the format-patch integration, it is just\n    a MUA whose primary purpose is not to munge its payload.\n\nSo I do not find it surprising at all that we have no tests for it.\nWe could do something like this at least, perhaps on top of the\nrecent topic that added a test to ensure commands that ought to be\noperable without any repository?\n\n t/t1517-outside-repo.sh | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git c/t/t1517-outside-repo.sh w/t/t1517-outside-repo.sh\nindex 557808ffa7..6d5f07df93 100755\n--- c/t/t1517-outside-repo.sh\n+++ w/t/t1517-outside-repo.sh\n@@ -56,4 +56,19 @@ test_expect_success 'grep outside repository' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'imap-send outside repository' '\n+\ttest_config_global imap.host imaps://localhost &&\n+\ttest_config_global imap.folder Drafts &&\n+\n+\techo nothing to send >expect &&\n+\ttest_must_fail git imap-send -v </dev/null 2>actual &&\n+\ttest_cmp expect actual &&\n+\n+\t(\n+\t\tcd non-repo &&\n+\t\ttest_must_fail git imap-send -v </dev/null 2>../actual\n+\t) &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n"},{"id":"495968","messageId":"xmqq34pzozsq.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlhjN7K8JKYz6Opd@tanuki","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-30T19:38:45Z","receivedAt":"2024-05-30T19:38:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It\n> does weird stuff where it writes the result into either `src` or `dst`\n> depending on which of these fields is provided by the caller. Which\n> means that one of the fields would typically be a constant, whereas the\n> other one will be allocated.\n\nYes, <src, dst> is used as a pair of <key, value> to query one with\nthe other (i.e. \"where does this one go?\" \"where does this one come\nfrom?\").\n\nBut we are not talking about const-ness of the member (iow, once you\npoint a string with the member, you cannot repoint the pointer to\nanother string), but we are talking about const-ness of the string\nthat is pointed by the member (iow, not \"char const *src\" but \"const\nchar *src\"), no?  If I ask \"I have this src, where does it go?\" with\na refspec element filled with src, the dst member may need to be\nupdated to point at the string that is the answer of the query, but\nthat still can be done with \"const char *src, *dst\", can't it?  That\nwas what I was wondering.\n\nAnd again you are conflating \"allocated\" with \"read-write\" here.  It\nis often convenient if a variable that points at an allocated string\nis of type \"char *\" and not \"const char *\", because you do not cast\nit when calling free().  But if you want to make a structure member\nor a variable that holds an allocated string responsible for\n_owning_ the piece of memory, then you need to consistently have the\nmember point at an allocated piece of memory (or NULL), no?  What\nthis patch does, i.e. prepare an on-stack refspec_str[] array that\nis initialized from a constant string, and have .src member point at\nit, would not make .src freeable.  In other words, .src pointing at\nan allocated piece of string \"some of the time\" alone is not a good\njustification to make it a non-const pointer, I would think.\n\n"},{"id":"495983","messageId":"xmqqle3qjqdk.fsf@gitster.g","threadId":"61561","inReplyTo":"cover.1717073346.git.ps@pks.im","subject":"Re: [PATCH v2 00/19] Compile with `-Wwrite-strings`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-31T09:13:27Z","receivedAt":"2024-05-31T09:13:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>      @@ reftable/readwrite_test.c: static void test_write_key_order(void)\n>     + \tstruct strbuf buf = STRBUF_INIT;\n>     + \tstruct reftable_writer *w =\n>       \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n>     ++\tchar a[] = \"a\", b[] = \"b\", target[] = \"target\";\n\nSo you decided to go in the complete opposite direction, hmph...\n\nI was hoping that we do not add more \"writable\" pieces of memory\nlike target[] only to please the constness-strict compilers.\n\n\n\n \n"},{"id":"496004","messageId":"Zlm-HzEqQ4yUO2QF@tanuki","threadId":"61561","inReplyTo":"xmqqle3qjqdk.fsf@gitster.g","subject":"Re: [PATCH v2 00/19] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-31T12:10:07Z","receivedAt":"2024-05-31T12:10:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, May 31, 2024 at 02:13:27AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >      @@ reftable/readwrite_test.c: static void test_write_key_order(void)\n> >     + \tstruct strbuf buf = STRBUF_INIT;\n> >     + \tstruct reftable_writer *w =\n> >       \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n> >     ++\tchar a[] = \"a\", b[] = \"b\", target[] = \"target\";\n> \n> So you decided to go in the complete opposite direction, hmph...\n> \n> I was hoping that we do not add more \"writable\" pieces of memory\n> like target[] only to please the constness-strict compilers.\n\nI guess I misunderstood what you were saying. I'll revise this to go\ninto the other direction.\n\nPatrick\n"},{"id":"496005","messageId":"ZlnJ83qSnGlQ0FWR@tanuki","threadId":"61561","inReplyTo":"xmqq34pzozsq.fsf@gitster.g","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-31T13:00:35Z","receivedAt":"2024-05-31T13:00:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It\n> > does weird stuff where it writes the result into either `src` or `dst`\n> > depending on which of these fields is provided by the caller. Which\n> > means that one of the fields would typically be a constant, whereas the\n> > other one will be allocated.\n> \n> Yes, <src, dst> is used as a pair of <key, value> to query one with\n> the other (i.e. \"where does this one go?\" \"where does this one come\n> from?\").\n> \n> But we are not talking about const-ness of the member (iow, once you\n> point a string with the member, you cannot repoint the pointer to\n> another string), but we are talking about const-ness of the string\n> that is pointed by the member (iow, not \"char const *src\" but \"const\n> char *src\"), no?  If I ask \"I have this src, where does it go?\" with\n> a refspec element filled with src, the dst member may need to be\n> updated to point at the string that is the answer of the query, but\n> that still can be done with \"const char *src, *dst\", can't it?  That\n> was what I was wondering.\n\nWell, yes, the pointers can be updated. But that doesn't solve the\nunderlying problem that the interface is not well-prepared to track\nownership of its inputs and outputs.\n\nThe root problem is that `struct refspec_item` is being abused for\nmultiple purposes. One of these purposes it to store parsed refspec\nitems. Another purpose is to actually serve as a query. The requirements\nfor those two purposes regarding the lifetime of associated strings is\ndifferent:\n\n  - When using it for storage purposes the strings should be owned by\n    the structure itself. Consequently, the lifetime should be managed\n    by the structure too, namely via `refspec_item_clear()`.\n\n  - When using it for parsing purposes the input string is owned by the\n    caller, whereas the output string is owned by the callee and then\n    handed over to the caller. Here we cannot rely on the structure\n    itself to manage the lifetimes of both strings, at least not in the\n    current form.\n\nNow of course, we could amend `struct refspec_item` to grow two\nadditional fields `src_to_free` and `dst_to_free`. But that only doubles\ndown on this misdesigned interface.\n\nThe proper solution in my opinion is to detangle those two usecases and\nsplit them out into two independent interfaces.\n\n> And again you are conflating \"allocated\" with \"read-write\" here.  It\n> is often convenient if a variable that points at an allocated string\n> is of type \"char *\" and not \"const char *\", because you do not cast\n> it when calling free().  But if you want to make a structure member\n> or a variable that holds an allocated string responsible for\n> _owning_ the piece of memory, then you need to consistently have the\n> member point at an allocated piece of memory (or NULL), no?  What\n> this patch does, i.e. prepare an on-stack refspec_str[] array that\n> is initialized from a constant string, and have .src member point at\n> it, would not make .src freeable.  In other words, .src pointing at\n> an allocated piece of string \"some of the time\" alone is not a good\n> justification to make it a non-const pointer, I would think.\n\nThat's fair enough. I'm trying to work around a broken interface, but do\nnot solve the underlying issue.\n\nPatrick\n"},{"id":"496006","messageId":"ZlnRlRdfbddzYMnO@tanuki","threadId":"61561","inReplyTo":"ZlnJ83qSnGlQ0FWR@tanuki","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-31T13:33:09Z","receivedAt":"2024-05-31T13:33:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, May 31, 2024 at 03:00:36PM +0200, Patrick Steinhardt wrote:\n> On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:\n> > Patrick Steinhardt <ps@pks.im> writes:\n> > \n> > > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It\n> > > does weird stuff where it writes the result into either `src` or `dst`\n> > > depending on which of these fields is provided by the caller. Which\n> > > means that one of the fields would typically be a constant, whereas the\n> > > other one will be allocated.\n> > \n> > Yes, <src, dst> is used as a pair of <key, value> to query one with\n> > the other (i.e. \"where does this one go?\" \"where does this one come\n> > from?\").\n> > \n> > But we are not talking about const-ness of the member (iow, once you\n> > point a string with the member, you cannot repoint the pointer to\n> > another string), but we are talking about const-ness of the string\n> > that is pointed by the member (iow, not \"char const *src\" but \"const\n> > char *src\"), no?  If I ask \"I have this src, where does it go?\" with\n> > a refspec element filled with src, the dst member may need to be\n> > updated to point at the string that is the answer of the query, but\n> > that still can be done with \"const char *src, *dst\", can't it?  That\n> > was what I was wondering.\n> \n> Well, yes, the pointers can be updated. But that doesn't solve the\n> underlying problem that the interface is not well-prepared to track\n> ownership of its inputs and outputs.\n> \n> The root problem is that `struct refspec_item` is being abused for\n> multiple purposes. One of these purposes it to store parsed refspec\n> items. Another purpose is to actually serve as a query. The requirements\n> for those two purposes regarding the lifetime of associated strings is\n> different:\n> \n>   - When using it for storage purposes the strings should be owned by\n>     the structure itself. Consequently, the lifetime should be managed\n>     by the structure too, namely via `refspec_item_clear()`.\n> \n>   - When using it for parsing purposes the input string is owned by the\n>     caller, whereas the output string is owned by the callee and then\n>     handed over to the caller. Here we cannot rely on the structure\n>     itself to manage the lifetimes of both strings, at least not in the\n>     current form.\n> \n> Now of course, we could amend `struct refspec_item` to grow two\n> additional fields `src_to_free` and `dst_to_free`. But that only doubles\n> down on this misdesigned interface.\n> \n> The proper solution in my opinion is to detangle those two usecases and\n> split them out into two independent interfaces.\n> \n> > And again you are conflating \"allocated\" with \"read-write\" here.  It\n> > is often convenient if a variable that points at an allocated string\n> > is of type \"char *\" and not \"const char *\", because you do not cast\n> > it when calling free().  But if you want to make a structure member\n> > or a variable that holds an allocated string responsible for\n> > _owning_ the piece of memory, then you need to consistently have the\n> > member point at an allocated piece of memory (or NULL), no?  What\n> > this patch does, i.e. prepare an on-stack refspec_str[] array that\n> > is initialized from a constant string, and have .src member point at\n> > it, would not make .src freeable.  In other words, .src pointing at\n> > an allocated piece of string \"some of the time\" alone is not a good\n> > justification to make it a non-const pointer, I would think.\n> \n> That's fair enough. I'm trying to work around a broken interface, but do\n> not solve the underlying issue.\n> \n> Patrick\n\nIn any case, I have now invested some more time into the individual\nsites where I was converting to use on-stack strings. In many of the\ncases we were indeed able to avoid the issue altogether without too much\nof a hassle. The end result is more pleasing indeed, I'd say.\n\nPatrick\n"},{"id":"496017","messageId":"xmqq4jaehui6.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlnJ83qSnGlQ0FWR@tanuki","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-31T15:27:13Z","receivedAt":"2024-05-31T15:27:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, May 30, 2024 at 12:38:45PM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > Well, we do. Not in `get_fetch_map()`, but in `query_refspecs()`. It\n>> > does weird stuff where it writes the result into either `src` or `dst`\n>> > depending on which of these fields is provided by the caller. Which\n>> > means that one of the fields would typically be a constant, whereas the\n>> > other one will be allocated.\n> ...\n> Well, yes, the pointers can be updated. But that doesn't solve the\n> underlying problem that the interface is not well-prepared to track\n> ownership of its inputs and outputs.\n\nOh, absolutely.  That is exactly why I wondered why .src and .dst\nneed to be \"char *\" and not \"const char *\".  If the piece of memory\nthese members point at are never modified via these pointers, marking\nthem as \"const char *\" would help the compilers help us by catching\nwhen we try to break the promise.\n\nSometimes, the \"constness of a pointer should help compilers help us\navoid modifying a piece of memory we promised not to modify through\nthe pointer\" contradicts with the idea of (ab)using the constness to\nsignal ownership, i.e., \"const pointers point at something owned by\nsomebody else, but via non-const pointers you always own the memory\".\n\nI wonder if we can do something to separate these two concerns\napart, using a trick similar to what we often use with an extra\nvariable \"to_free\".  Doing so would bloat the refspec_item, but\nunlike the references themselves, there won't be thousands of them,\nso it may not be an issue, perhaps?\n\n>> And again you are conflating \"allocated\" with \"read-write\" here.  It\n>> is often convenient if a variable that points at an allocated string\n>> is of type \"char *\" and not \"const char *\", because you do not cast\n>> it when calling free().  But if you want to make a structure member\n>> or a variable that holds an allocated string responsible for\n>> _owning_ the piece of memory, then you need to consistently have the\n>> member point at an allocated piece of memory (or NULL), no?  What\n>> this patch does, i.e. prepare an on-stack refspec_str[] array that\n>> is initialized from a constant string, and have .src member point at\n>> it, would not make .src freeable.  In other words, .src pointing at\n>> an allocated piece of string \"some of the time\" alone is not a good\n>> justification to make it a non-const pointer, I would think.\n>\n> That's fair enough. I'm trying to work around a broken interface, but do\n> not solve the underlying issue.\n\n\n"},{"id":"496018","messageId":"xmqqzfs6gfwn.fsf@gitster.g","threadId":"61561","inReplyTo":"ZlnRlRdfbddzYMnO@tanuki","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-31T15:27:52Z","receivedAt":"2024-05-31T15:27:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In any case, I have now invested some more time into the individual\n> sites where I was converting to use on-stack strings. In many of the\n> cases we were indeed able to avoid the issue altogether without too much\n> of a hassle. The end result is more pleasing indeed, I'd say.\n\nNice to hear that.\nThanks.\n"},{"id":"496089","messageId":"cover.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH v3 00/27] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:38:56Z","receivedAt":"2024-06-03T09:39:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the third version of my patch series that prepares the Git\ncodebase to compile with `-Wwrite-strings`. This compiler warning\nchanges the type of string constants from `char []` to `const char []`,\nwhich helps to identify cases where we may accidentally write to or free\nsuch constants.\n\nChanges compared to v2:\n\n  - I have split up the second patch into multiple patches. This was\n    done so that we can have a deeper look at the respective sites that\n    need adjustment.\n\n  - I dropped the local string arrays that I used in v2 and instead now\n    use string constants with casts in some places where we expect that\n    those should never be written to. This has the benefit that we would\n    segfault in case that expectation is violated, instead of silently\n    allowing a write to those local arrays.\n\n  - I adapted multiple commit messages to not only mention freeing of\n    string constants, but also that writing to string constants is\n    illegal.\n\nDue to the split-up patch the range-diff got quite messy and was barely\nreadable anymore. I thus included an interdiff instead, which should be\nway easier to read.\n\nI realize that this patch series has grown quite large. Please let me\nknow in case I shall split it up into multiple patch series.\n\nPatrick\n\nPatrick Steinhardt (27):\n  global: improve const correctness when assigning string constants\n  global: convert intentionally-leaking config strings to consts\n  refs/reftable: stop micro-optimizing refname allocations on copy\n  reftable: cast away constness when assigning constants to records\n  refspec: remove global tag refspec structure\n  builtin/remote: cast away constness in `get_head_names()`\n  diff: cast string constant in `fill_textconv()`\n  line-log: stop assigning string constant to file parent buffer\n  line-log: always allocate the output prefix\n  entry: refactor how we remove items for delayed checkouts\n  ident: add casts for fallback name and GECOS\n  object-file: mark cached object buffers as const\n  object-file: make `buf` parameter of `index_mem()` a constant\n  pretty: add casts for decoration option pointers\n  compat/win32: fix const-correctness with string constants\n  http: do not assign string constant to non-const field\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: do not assign default backend to non-constant field\n  builtin/rebase: always store allocated string in `options.strategy`\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  81 ++++++++++++----------\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |  12 ++--\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  18 ++++-\n compat/mingw.c               |  28 ++++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   6 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |  14 ++--\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   4 +-\n imap-send.c                  | 130 ++++++++++++++++++++---------------\n line-log.c                   |  22 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  23 ++++---\n parse-options.h              |   2 +-\n pretty.c                     |   6 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |  28 ++++----\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics.c            |  15 ++--\n reftable/basics.h            |   4 +-\n reftable/basics_test.c       |   4 +-\n reftable/block_test.c        |   2 +-\n reftable/merged_test.c       |  44 ++++++------\n reftable/readwrite_test.c    |  32 ++++-----\n reftable/record.c            |   6 +-\n reftable/stack.c             |  10 +--\n reftable/stack_test.c        |  56 +++++++--------\n remote-curl.c                |  53 +++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 68 files changed, 470 insertions(+), 386 deletions(-)\n\nInterdiff against v2:\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 0324a5d48d..b44f580b8c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -493,13 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n {\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n-\tstruct refspec_item refspec;\n-\tchar refspec_str[] = \"refs/heads/*\";\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n \n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.force = 0;\n-\trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = refspec_str;\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\n@@ -508,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \n \tfree_refs(fetch_map);\n \tfree_refs(matches);\n-\n \treturn 0;\n }\n \ndiff --git a/compat/basename.c b/compat/basename.c\nindex c3c9d65fac..5aa320306b 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -1,11 +1,6 @@\n #include \"../git-compat-util.h\"\n #include \"../strbuf.h\"\n \n-/*\n- * Both basename(3P) and dirname(3P) are mis-specified because they return a\n- * non-constant pointer even though it is specified that they may return a\n- * pointer to internal memory. This variable here is a result of that.\n- */\n static char current_directory[] = \".\";\n \n /* Adapted from libiberty's basename.c.  */\n@@ -17,7 +12,13 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn current_directory;\n+\t\t/*\n+\t\t * basename(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \"\";\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -40,12 +41,14 @@ char *gitdirname(char *path)\n \tchar *p = path, *slash = NULL, c;\n \tint dos_drive_prefix;\n \n-\t/*\n-\t * Same here, dirname(3P) is broken because it returns a non-constant\n-\t * pointer that may point to internal memory.\n-\t */\n \tif (!p)\n-\t\treturn current_directory;\n+\t\t/*\n+\t\t * dirname(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \"\";\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 60f0986f76..d378cd04cb 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2257,7 +2257,6 @@ struct passwd *getpwuid(int uid)\n {\n \tstatic unsigned initialized;\n \tstatic char user_name[100];\n-\tstatic char unknown[] = \"unknown\";\n \tstatic struct passwd *p;\n \twchar_t buf[100];\n \tDWORD len;\n@@ -2280,7 +2279,11 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = unknown;\n+\t\t/*\n+\t\t * Data returned by getpwuid(3P) is treated as internal and\n+\t\t * must never be written to or freed.\n+\t\t */\n+\t\tp->pw_gecos = (char *) \"unknown\";\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\ndiff --git a/diff.c b/diff.c\nindex 1439a5a01d..cecda216cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7231,12 +7231,11 @@ size_t fill_textconv(struct repository *r,\n \t\t     struct diff_filespec *df,\n \t\t     char **outbuf)\n {\n-\tstatic char empty_str[] = \"\";\n \tsize_t size;\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = empty_str;\n+\t\t\t*outbuf = (char *) \"\";\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\ndiff --git a/entry.c b/entry.c\nindex 2fc06ac90c..f291d8eee6 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)\n \treturn !available;\n }\n \n+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)\n+{\n+\treturn !!item->string;\n+}\n+\n int finish_delayed_checkout(struct checkout *state, int show_progress)\n {\n \tint errs = 0;\n@@ -175,7 +180,6 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \tstruct string_list_item *filter, *path;\n \tstruct progress *progress = NULL;\n \tstruct delayed_checkout *dco = state->delayed_checkout;\n-\tchar empty_str[] = \"\";\n \n \tif (!state->delayed_checkout)\n \t\treturn errs;\n@@ -190,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = empty_str;\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -200,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = empty_str;\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -226,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = empty_str;\n+\t\t\t\t\tfilter->string = NULL;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\n@@ -240,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\terrs = 1;\n \t\t\t}\n \t\t}\n-\t\tstring_list_remove_empty_items(&dco->filters, 0);\n+\n+\t\tfilter_string_list(&dco->filters, 0, string_is_not_null, NULL);\n \t}\n \tstop_progress(&progress);\n \tstring_list_clear(&dco->filters, 0);\ndiff --git a/ident.c b/ident.c\nindex df7aa42802..caf41fb2a9 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,14 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tstatic char fallback_name[] = \"unknown\";\n+\t\tfallback.pw_name = (char *) \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tstatic char fallback_gcos[] = \"Unknown\";\n-#endif\n-\n-\t\tfallback.pw_name = fallback_name;\n-#ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = fallback_gcos;\n+\t\tfallback.pw_gecos = (char *) \"Unknown\";\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\ndiff --git a/line-log.c b/line-log.c\nindex 9a298209d0..67c80b39a0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1036,7 +1036,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n-\tchar empty_str[] = \"\";\n+\tchar *parent_data_to_free = NULL;\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1061,7 +1061,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = empty_str;\n+\t\tfile_parent.ptr = parent_data_to_free = xstrdup(\"\");\n \t\tfile_parent.size = 0;\n \t}\n \n@@ -1080,6 +1080,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n \tdiff_ranges_release(&diff);\n \n+\tfree(parent_data_to_free);\n \treturn ((*diff_out)->parent.nr > 0);\n }\n \ndiff --git a/object-file.c b/object-file.c\nindex 46ea00ac46..b5b5a59dc6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -277,18 +277,17 @@ int hash_algo_by_length(int len)\n static struct cached_object {\n \tstruct object_id oid;\n \tenum object_type type;\n-\tvoid *buf;\n+\tconst void *buf;\n \tunsigned long size;\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n \n-static char empty_tree_buf[] = \"\";\n static struct cached_object empty_tree = {\n \t.oid = {\n \t\t.hash = EMPTY_TREE_SHA1_BIN_LITERAL,\n \t},\n \t.type = OBJ_TREE,\n-\t.buf = empty_tree_buf,\n+\t.buf = \"\",\n };\n \n static struct cached_object *find_cached_object(const struct object_id *oid)\n@@ -1779,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \t\t\tstruct object_id *oid)\n {\n \tstruct cached_object *co;\n+\tchar *co_buf;\n+\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n@@ -1788,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n-\tco->buf = xmalloc(len);\n-\tmemcpy(co->buf, buf, len);\n+\tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n }\ndiff --git a/pretty.c b/pretty.c\nindex 1a0030b32a..1df9d635fb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1583,10 +1583,9 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\treturn 1;\n \tcase 'D':\n \t\t{\n-\t\t\tchar empty_str[] = \"\";\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = empty_str,\n-\t\t\t\t.suffix = empty_str,\n+\t\t\t\t.prefix = (char *) \"\",\n+\t\t\t\t.suffix = (char *) \"\",\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1908e74dea..e77faa2b9d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1285,7 +1285,6 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tstruct strbuf errbuf = STRBUF_INIT;\n \tsize_t logs_nr = 0, logs_alloc = 0, i;\n \tconst char *committer_info;\n-\tchar head[] = \"HEAD\";\n \tint ret;\n \n \tcommitter_info = git_committer_info(0);\n@@ -1341,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t * old reference.\n \t */\n \trefs[0] = old_ref;\n-\trefs[0].refname = (char *)arg->newname;\n+\trefs[0].refname = xstrdup(arg->newname);\n \trefs[0].update_index = creation_ts;\n \tif (arg->delete_old) {\n-\t\trefs[1].refname = (char *)arg->oldname;\n+\t\trefs[1].refname = xstrdup(arg->oldname);\n \t\trefs[1].value_type = REFTABLE_REF_DELETION;\n \t\trefs[1].update_index = deletion_ts;\n \t}\n@@ -1367,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \t\tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n \t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1388,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = head;\n+\t\t\tlogs[logs_nr].refname = xstrdup(\"HEAD\");\n+\t\t\tlogs[logs_nr].value.update.name =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.name);\n+\t\t\tlogs[logs_nr].value.update.email =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.email);\n+\t\t\tlogs[logs_nr].value.update.message =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.message);\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1399,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\tlogs[logs_nr].refname = (char *)arg->newname;\n+\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n \t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1431,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\t */\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tlogs[logs_nr] = old_log;\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs_nr++;\n \n \t\t/*\n@@ -1440,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (arg->delete_old) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n-\t\t\tlogs[logs_nr].refname = (char *)arg->oldname;\n+\t\t\tlogs[logs_nr].refname = xstrdup(arg->oldname);\n \t\t\tlogs[logs_nr].value_type = REFTABLE_LOG_DELETION;\n \t\t\tlogs[logs_nr].update_index = old_log.update_index;\n \t\t\tlogs_nr++;\n@@ -1463,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \treftable_iterator_destroy(&it);\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n-\tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (logs[i].refname == head)\n-\t\t\tcontinue;\n-\t\tlogs[i].refname = NULL;\n+\tfor (i = 0; i < logs_nr; i++)\n \t\treftable_log_record_release(&logs[i]);\n-\t}\n \tfree(logs);\n+\tfor (i = 0; i < ARRAY_SIZE(refs); i++)\n+\t\treftable_ref_record_release(&refs[i]);\n \treftable_ref_record_release(&old_ref);\n \treftable_log_record_release(&old_log);\n \treturn ret;\ndiff --git a/reftable/basics.c b/reftable/basics.c\nindex fea711db7e..0058619ca6 100644\n--- a/reftable/basics.c\n+++ b/reftable/basics.c\n@@ -67,9 +67,9 @@ void free_names(char **a)\n \treftable_free(a);\n }\n \n-size_t names_length(char **names)\n+size_t names_length(const char **names)\n {\n-\tchar **p = names;\n+\tconst char **p = names;\n \twhile (*p)\n \t\tp++;\n \treturn p - names;\n@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)\n \t*namesp = names;\n }\n \n-int names_equal(char **a, char **b)\n+int names_equal(const char **a, const char **b)\n {\n-\tint i = 0;\n-\tfor (; a[i] && b[i]; i++) {\n-\t\tif (strcmp(a[i], b[i])) {\n+\tsize_t i = 0;\n+\tfor (; a[i] && b[i]; i++)\n+\t\tif (strcmp(a[i], b[i]))\n \t\t\treturn 0;\n-\t\t}\n-\t}\n-\n \treturn a[i] == b[i];\n }\n \ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 523ecd5307..c8fec68d4e 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -42,10 +42,10 @@ void free_names(char **a);\n void parse_names(char *buf, int size, char ***namesp);\n \n /* compares two NULL-terminated arrays of strings. */\n-int names_equal(char **a, char **b);\n+int names_equal(const char **a, const char **b);\n \n /* returns the array size of a NULL-terminated array of strings. */\n-size_t names_length(char **names);\n+size_t names_length(const char **names);\n \n /* Allocation routines; they invoke the functions set through\n  * reftable_set_alloc() */\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 23fab22eb1..13bc761817 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,7 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar a[] = \"a\", b[] = \"b\";\n-\tchar *names[] = { a, b, NULL };\n+\tconst char *names[] = { \"a\", \"b\", NULL };\n \tEXPECT(names_length(names) == 2);\n }\n \ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex d5967b214d..90aecd5a7c 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -19,7 +19,7 @@ license that can be found in the LICENSE file or at\n static void test_block_read_write(void)\n {\n \tconst int header_off = 21; /* random */\n-\tchar *names[30], empty_str[] = \"\";\n+\tchar *names[30];\n \tconst int N = ARRAY_SIZE(names);\n \tconst int block_size = 1024;\n \tstruct reftable_block block = { NULL };\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = empty_str;\n+\trec.u.ref.refname = (char *) \"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex fd5a065e42..6d1159d12d 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -123,15 +123,14 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n \n static void test_merged_between(void)\n {\n-\tchar a[] = \"a\", b[] = \"b\";\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = b,\n+\t\t.refname = (char *) \"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = a,\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -164,41 +163,40 @@ static void test_merged_between(void)\n \n static void test_merged(void)\n {\n-\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = b,\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = c,\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = a,\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = c,\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = d,\n+\t\t\t.refname = (char *) \"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,52 +289,48 @@ merged_table_from_log_records(struct reftable_log_record **logs,\n \n static void test_merged_logs(void)\n {\n-\tchar a[] = \"a\";\n-\tchar name[] = \"jane doe\", email[] = \"jane@invalid\";\n-\tchar message1[] = \"message1\", message2[] = \"message2\";\n-\tchar message3[] = \"message3\";\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = name,\n-\t\t\t\t.email = email,\n-\t\t\t\t.message = message2,\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = name,\n-\t\t\t\t.email = email,\n-\t\t\t\t.message = message1,\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = name,\n-\t\t\t\t.email = email,\n-\t\t\t\t.message = message3,\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -406,13 +400,13 @@ static void test_merged_logs(void)\n \n static void test_default_write_opts(void)\n {\n-\tchar master[] = \"master\";\n \tstruct reftable_write_options opts = { 0 };\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n+\n \tstruct reftable_ref_record rec = {\n-\t\t.refname = master,\n+\t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex 064d693111..c55019232b 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -56,7 +56,6 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \tint i = 0, n;\n \tstruct reftable_log_record log = { NULL };\n \tconst struct reftable_stats *stats = NULL;\n-\tchar message[] = \"message\";\n \n \tREFTABLE_CALLOC_ARRAY(*names, N + 1);\n \n@@ -87,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = message;\n+\t\tlog.value.update.message = (char *) \"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -112,28 +111,23 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \n static void test_log_buffer_size(void)\n {\n-\tchar refname[] = \"refs/heads/master\";\n-\tchar name[] = \"Han-Wen Hienhuys\";\n-\tchar email[] = \"hanwen@google.com\";\n-\tchar message[] = \"commit: 9\\n\";\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_write_options opts = {\n \t\t.block_size = 4096,\n \t};\n \tint err;\n \tint i;\n-\tstruct reftable_log_record log = {\n-\t\t.refname = refname,\n-\t\t.update_index = 0xa,\n-\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t.value.update = {\n-\t\t\t.name = name,\n-\t\t\t.email = email,\n-\t\t\t.tz_offset = 100,\n-\t\t\t.time = 0x5e430672,\n-\t\t\t.message = message,\n-\t\t},\n-\t};\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n+\t\t\t.update_index = 0xa,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n+\t\t\t\t   } } };\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n@@ -155,9 +149,6 @@ static void test_log_buffer_size(void)\n \n static void test_log_overflow(void)\n {\n-\tchar refname[] = \"refs/heads/master\";\n-\tchar name[] = \"Han-Wen Hienhuys\";\n-\tchar email[] = \"hanwen@google.com\";\n \tstruct strbuf buf = STRBUF_INIT;\n \tchar msg[256] = { 0 };\n \tstruct reftable_write_options opts = {\n@@ -165,15 +156,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = refname,\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = name,\n-\t\t\t\t.email = email,\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -299,20 +290,17 @@ static void test_log_zlib_corruption(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tconst struct reftable_stats *stats = NULL;\n-\tchar refname[] = \"refname\";\n-\tchar name[] = \"My Name\";\n-\tchar email[] = \"myname@invalid\";\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = refname,\n+\t\t.refname = (char *) \"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = name,\n-\t\t\t\t.email = email,\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -739,9 +727,8 @@ static void test_write_empty_key(void)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n-\tchar refname[] = \"\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = refname,\n+\t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -763,21 +750,20 @@ static void test_write_key_order(void)\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n-\tchar a[] = \"a\", b[] = \"b\", target[] = \"target\";\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = b,\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = target,\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = a,\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = target,\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex a59ebe038d..09549c51c9 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n \treturn cur;\n }\n \n-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+static int reftable_stack_reload_once(struct reftable_stack *st,\n+\t\t\t\t      const char **names,\n \t\t\t\t      int reuse_open)\n {\n \tsize_t cur_len = !st->merged ? 0 : st->merged->stack_len;\n@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n \n \twhile (*names) {\n \t\tstruct reftable_reader *rd = NULL;\n-\t\tchar *name = *names++;\n+\t\tconst char *name = *names++;\n \n \t\t/* this is linear; we assume compaction keeps the number of\n \t\t   tables under control so this is not quadratic. */\n@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\t\t\tgoto out;\n \t\t}\n \n-\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\terr = reftable_stack_reload_once(st, (const char **) names, reuse_open);\n \t\tif (!err)\n \t\t\tbreak;\n \t\tif (err != REFTABLE_NOT_EXIST_ERROR)\n@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\terr = read_lines(st->list_file, &names_after);\n \t\tif (err < 0)\n \t\t\tgoto out;\n-\t\tif (names_equal(names_after, names)) {\n+\t\tif (names_equal((const char **) names_after,\n+\t\t\t\t(const char **) names)) {\n \t\t\terr = REFTABLE_NOT_EXIST_ERROR;\n \t\t\tgoto out;\n \t\t}\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex c6d88e6ea8..4abf92636d 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -116,14 +116,13 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar a[] = \"a\", b[] = \"b\", c[] = \"c\", d[] = \"d\";\n-\tchar *abc[] = { a, b, c, NULL };\n-\tchar *abd[] = { a, b, d, NULL };\n-\tchar *ab[] = { a, b, NULL };\n-\n-\tEXPECT(names_equal(abc, abc));\n-\tEXPECT(!names_equal(abc, abd));\n-\tEXPECT(!names_equal(abc, ab));\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n+\n+\tEXPECT(names_equal(a, a));\n+\tEXPECT(!names_equal(a, b));\n+\tEXPECT(!names_equal(a, c));\n }\n \n static int write_test_ref(struct reftable_writer *wr, void *arg)\n@@ -156,12 +155,11 @@ static void test_reftable_stack_add_one(void)\n \t};\n \tstruct reftable_stack *st = NULL;\n \tint err;\n-\tchar head[] = \"HEAD\", master[] = \"master\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = head,\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -217,18 +215,17 @@ static void test_reftable_stack_uptodate(void)\n \tchar *dir = get_tmp_dir(__LINE__);\n \n \tint err;\n-\tchar head[] = \"HEAD\", branch2[] = \"branch2\", master[] = \"master\";\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = head,\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = branch2,\n+\t\t.refname = (char *) \"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \n@@ -265,12 +262,12 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_addition *add = NULL;\n-\tchar head[] = \"HEAD\", master[] = \"master\";\n+\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = head,\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,11 +310,10 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i <= n; i++) {\n-\t\tchar master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = master,\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -359,9 +355,8 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n-\tchar master[] = \"refs/meads/master\";\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = master,\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,21 +404,21 @@ static int write_error(struct reftable_writer *wr, void *arg)\n static void test_reftable_stack_update_index_check(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n+\n \tstruct reftable_write_options cfg = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tint err;\n-\tchar name1[] = \"name1\", name2[] = \"name2\", master[] = \"master\";\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = name1,\n+\t\t.refname = (char *) \"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = name2,\n+\t\t.refname = (char *) \"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = master,\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -565,12 +560,8 @@ static void test_reftable_stack_log_normalize(void)\n \t};\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n-\tchar branch[] = \"branch\";\n-\tchar onetwomessage[] = \"one\\ntwo\";\n-\tchar onemessage[] = \"one\";\n-\tchar twomessage[] = \"two\\n\";\n \tstruct reftable_log_record input = {\n-\t\t.refname = branch,\n+\t\t.refname = (char *) \"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -591,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = onetwomessage;\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = onemessage;\n+\tinput.value.update.message = (char *) \"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -603,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = twomessage;\n+\tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -700,14 +691,15 @@ static void test_reftable_stack_tombstone(void)\n static void test_reftable_stack_hash_id(void)\n {\n \tchar *dir = get_tmp_dir(__LINE__);\n+\n \tstruct reftable_write_options cfg = { 0 };\n \tstruct reftable_stack *st = NULL;\n \tint err;\n-\tchar master[] = \"master\", target[] = \"target\";\n+\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = master,\n+\t\t.refname = (char *) \"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = target,\n+\t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -882,12 +874,12 @@ static void test_reftable_stack_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100], master[] = \"master\";\n+\t\tchar name[100];\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = master,\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -918,11 +910,10 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i <= n; i++) {\n-\t\tchar master[] = \"master\";\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = master,\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \n \t\t/*\n@@ -968,12 +959,12 @@ static void test_reftable_stack_compaction_concurrent(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100], master[] = \"master\";\n+\t\tchar name[100];\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = master,\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1018,12 +1009,12 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \tEXPECT_ERR(err);\n \n \tfor (i = 0; i < N; i++) {\n-\t\tchar name[100], master[] = \"master\";\n+\t\tchar name[100];\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = master,\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496090","messageId":"e01fde88fe35d6da358af5689320db50750c29dc.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 01/27] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:01Z","receivedAt":"2024-06-03T09:39:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/basics.c            | 15 +++++------\n reftable/basics.h            |  4 +--\n reftable/basics_test.c       |  4 +--\n reftable/record.c            |  6 ++---\n reftable/stack.c             | 10 ++++---\n reftable/stack_test.c        |  8 +++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 38 files changed, 106 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/basics.c b/reftable/basics.c\nindex fea711db7e..0058619ca6 100644\n--- a/reftable/basics.c\n+++ b/reftable/basics.c\n@@ -67,9 +67,9 @@ void free_names(char **a)\n \treftable_free(a);\n }\n \n-size_t names_length(char **names)\n+size_t names_length(const char **names)\n {\n-\tchar **p = names;\n+\tconst char **p = names;\n \twhile (*p)\n \t\tp++;\n \treturn p - names;\n@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)\n \t*namesp = names;\n }\n \n-int names_equal(char **a, char **b)\n+int names_equal(const char **a, const char **b)\n {\n-\tint i = 0;\n-\tfor (; a[i] && b[i]; i++) {\n-\t\tif (strcmp(a[i], b[i])) {\n+\tsize_t i = 0;\n+\tfor (; a[i] && b[i]; i++)\n+\t\tif (strcmp(a[i], b[i]))\n \t\t\treturn 0;\n-\t\t}\n-\t}\n-\n \treturn a[i] == b[i];\n }\n \ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 523ecd5307..c8fec68d4e 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -42,10 +42,10 @@ void free_names(char **a);\n void parse_names(char *buf, int size, char ***namesp);\n \n /* compares two NULL-terminated arrays of strings. */\n-int names_equal(char **a, char **b);\n+int names_equal(const char **a, const char **b);\n \n /* returns the array size of a NULL-terminated array of strings. */\n-size_t names_length(char **names);\n+size_t names_length(const char **names);\n \n /* Allocation routines; they invoke the functions set through\n  * reftable_set_alloc() */\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..13bc761817 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,8 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tconst char *names[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex a59ebe038d..09549c51c9 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n \treturn cur;\n }\n \n-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+static int reftable_stack_reload_once(struct reftable_stack *st,\n+\t\t\t\t      const char **names,\n \t\t\t\t      int reuse_open)\n {\n \tsize_t cur_len = !st->merged ? 0 : st->merged->stack_len;\n@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n \n \twhile (*names) {\n \t\tstruct reftable_reader *rd = NULL;\n-\t\tchar *name = *names++;\n+\t\tconst char *name = *names++;\n \n \t\t/* this is linear; we assume compaction keeps the number of\n \t\t   tables under control so this is not quadratic. */\n@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\t\t\tgoto out;\n \t\t}\n \n-\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\terr = reftable_stack_reload_once(st, (const char **) names, reuse_open);\n \t\tif (!err)\n \t\t\tbreak;\n \t\tif (err != REFTABLE_NOT_EXIST_ERROR)\n@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\terr = read_lines(st->list_file, &names_after);\n \t\tif (err < 0)\n \t\t\tgoto out;\n-\t\tif (names_equal(names_after, names)) {\n+\t\tif (names_equal((const char **) names_after,\n+\t\t\t\t(const char **) names)) {\n \t\t\terr = REFTABLE_NOT_EXIST_ERROR;\n \t\t\tgoto out;\n \t\t}\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..07d89b45da 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,9 +116,9 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n \n \tEXPECT(names_equal(a, a));\n \tEXPECT(!names_equal(a, b));\ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496091","messageId":"92cb0b28c65bed0f3cd10e6c4c57b98f199536a9.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 02/27] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:06Z","receivedAt":"2024-06-03T09:39:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and the struct memers are never\n    written to or free'd.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never written to or free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never written to or free'd.\n\nAll of these are intentionally kept alive and are never written to.\nFurthermore, all of these are being assigned both string constants in\nsome places, and allocated strings in other places. This will cause\nwarnings once we enable `-Wwrite-strings`, so let's mark the respective\nfields as `const char *` and cast away the constness when assigning\nthose values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496092","messageId":"085d90c8b63e937e3d76637ce2422320b5801ebb.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 03/27] refs/reftable: stop micro-optimizing refname allocations on copy","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:10Z","receivedAt":"2024-06-03T09:39:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When copying refs, we execute `write_copy_table()` to write the new\ntable. As the names arge given to use via `arg->newname` and\n`arg->oldname`, respectively, we optimize away some allocations by\nassigning those fields to the reftable records we are about to write.\nThis requires us to cast the input to `char *` pointers as they are in\nfact constant strings. Later on, we then unset the refname for all of\nthe records before calling `reftable_log_record_release()` on them.\n\nWe also do this when assigning the \"HEAD\" constant, but here we do not\ncast because its type is `char[]` by default. It's about to be turned\ninto `const char *` though once we enable `-Wwrite-strings` and will\nthus cause another warning.\n\nIt's quite dubious whether this micro-optimization really helps. We're\nabout to write to disk anyway, which is going to be way slower than a\nsmall handful of allocations. Let's drop the optimization altogther and\ninstead copy arguments to simplify the code and avoid the future warning\nwith `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c | 28 ++++++++++++++++------------\n 1 file changed, 16 insertions(+), 12 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..e77faa2b9d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t * old reference.\n \t */\n \trefs[0] = old_ref;\n-\trefs[0].refname = (char *)arg->newname;\n+\trefs[0].refname = xstrdup(arg->newname);\n \trefs[0].update_index = creation_ts;\n \tif (arg->delete_old) {\n-\t\trefs[1].refname = (char *)arg->oldname;\n+\t\trefs[1].refname = xstrdup(arg->oldname);\n \t\trefs[1].value_type = REFTABLE_REF_DELETION;\n \t\trefs[1].update_index = deletion_ts;\n \t}\n@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \t\tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n \t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = xstrdup(\"HEAD\");\n+\t\t\tlogs[logs_nr].value.update.name =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.name);\n+\t\t\tlogs[logs_nr].value.update.email =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.email);\n+\t\t\tlogs[logs_nr].value.update.message =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.message);\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\tlogs[logs_nr].refname = (char *)arg->newname;\n+\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n \t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\t */\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tlogs[logs_nr] = old_log;\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs_nr++;\n \n \t\t/*\n@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (arg->delete_old) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n-\t\t\tlogs[logs_nr].refname = (char *)arg->oldname;\n+\t\t\tlogs[logs_nr].refname = xstrdup(arg->oldname);\n \t\t\tlogs[logs_nr].value_type = REFTABLE_LOG_DELETION;\n \t\t\tlogs[logs_nr].update_index = old_log.update_index;\n \t\t\tlogs_nr++;\n@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \treftable_iterator_destroy(&it);\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n-\tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n-\t\t\tcontinue;\n-\t\tlogs[i].refname = NULL;\n+\tfor (i = 0; i < logs_nr; i++)\n \t\treftable_log_record_release(&logs[i]);\n-\t}\n \tfree(logs);\n+\tfor (i = 0; i < ARRAY_SIZE(refs); i++)\n+\t\treftable_ref_record_release(&refs[i]);\n \treftable_ref_record_release(&old_ref);\n \treftable_log_record_release(&old_log);\n \treturn ret;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496093","messageId":"8692d9d9afbe69642484fd7548bd6af76c5db1f5.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 04/27] reftable: cast away constness when assigning constants to records","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:15Z","receivedAt":"2024-06-03T09:39:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reftable records are used in multiple ways throughout the reftable\nlibrary. In many of those cases they merely act as input to a function\nwithout getting modified by it at all. Most importantly, this happens\nwhen writing records and when querying for records.\n\nWe rely on this in our tests and thus assign string constants to those\nfields, which is about to generate warnings as those fields are of type\n`char *`. While we could go through the process and instead allocate\nthose strings in all of our tests, this feels quite unnecessary.\n\nInstead, add casts to `char *` for all of those strings. As this is part\nof our tests, this also nicely serves as a demonstration that nothing\nwrites or frees those string constants, which would otherwise lead to\nsegfaults.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reftable/block_test.c     |  2 +-\n reftable/merged_test.c    | 44 +++++++++++++++++------------------\n reftable/readwrite_test.c | 32 +++++++++++++-------------\n reftable/stack_test.c     | 48 +++++++++++++++++++--------------------\n 4 files changed, 63 insertions(+), 63 deletions(-)\n\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..90aecd5a7c 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = (char *) \"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..6d1159d12d 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n static void test_merged_between(void)\n {\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = (char *) \"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -165,38 +165,38 @@ static void test_merged(void)\n {\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = (char *) \"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,46 +291,46 @@ static void test_merged_logs(void)\n {\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -406,7 +406,7 @@ static void test_default_write_opts(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..c55019232b 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = (char *) \"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)\n \tint err;\n \tint i;\n \tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n \t\t\t.update_index = 0xa,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n \t\t\t\t\t   .tz_offset = 100,\n \t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n \t\t\t\t   } } };\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n@@ -156,15 +156,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = (char *) \"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -728,7 +728,7 @@ static void test_write_empty_key(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -752,18 +752,18 @@ static void test_write_key_order(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 07d89b45da..4abf92636d 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)\n \n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = (char *) \"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \n@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_addition *add = NULL;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = (char *) \"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = (char *) \"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = (char *) \"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = (char *) \"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)\n \tint err;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \n \t\t/*\n@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496094","messageId":"db4d0620146fdf7acd59c649d93c0da68289a3d7.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 05/27] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:20Z","receivedAt":"2024-06-03T09:39:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization fo the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496095","messageId":"6a3c8d351e9defcc3b700761e96c8f29f2366df0.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 06/27] builtin/remote: cast away constness in `get_head_names()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:24Z","receivedAt":"2024-06-03T09:39:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_head_names()`, we assign the \"refs/heads/*\" string constant to\n`struct refspec_item::{src,dst}`, which are both non-constant pointers.\nIdeally, we'd refactor the code such that both of these fields were\nconstant. But `struct refspec_item` is used for two different usecases\nwith conflicting requirements:\n\n  - To query for a source or destination based on the given refspec. The\n    caller either sets `src` or `dst` as the branch that we want to\n    search for, and the respective other field gets populated. The\n    fields should be constant when being used as a query parameter,\n    which is owned by the caller, and non-constant when being used as an\n    out parameter, which is owned by the refspec item. This is is\n    contradictory in itself already.\n\n  - To store refspec items with their respective source and destination\n    branches, in which case both fields should be owned by the struct.\n\nIdeally, we'd split up this interface to clearly separate between\nquerying and storing, which would enable us to clarify lifetimes of the\nstrings. This would be a much bigger undertaking though.\n\nInstead, accept the status quo for now and cast away the constness of\nthe source and destination patterns. We know that those are not being\nwritten to or freed, so while this is ugly it certainly is fine for now.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..b44f580b8c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n {\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n-\tstruct refspec_item refspec;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n \n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.force = 0;\n-\trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\n@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \n \tfree_refs(fetch_map);\n \tfree_refs(matches);\n-\n \treturn 0;\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496096","messageId":"750429472aedd05c33722607d3f8bd7a8c432a8a.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 07/27] diff: cast string constant in `fill_textconv()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:29Z","receivedAt":"2024-06-03T09:39:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `fill_textconv()` function is responsible for converting an input\nfile with a textconv driver, which is then passed to the caller. Weirdly\nthough, the function also handles the case where there is no textconv\ndriver at all. In that case, it will return either the contents of the\npopulated filespec, or an empty string if the filespec is invalid.\n\nThese two cases have differing memory ownership semantics. When there is\na textconv driver, then the result is an allocated string. Otherwise,\nthe result is either a string constant or owned by the filespec struct.\nAll callers are in fact aware of this weirdness and only end up freeing\nthe output buffer when they had a textconv driver.\n\nIdeally, we'd split up this interface to only perform the conversion via\nthe textconv driver, and BUG in case the caller didn't provide one. This\nwould make memory ownership semantics much more straight forward. For\nnow though, let's simply cast the empty string constant to `char *` to\navoid a warning with `-Wwrite-strings`. This is equivalent to the same\ncast that we already have in `fill_mmfile()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..cecda216cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = (char *) \"\";\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496097","messageId":"cc8fa1896d4b49be36495456dd4ebd4544c6b0dd.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 08/27] line-log: stop assigning string constant to file parent buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:34Z","receivedAt":"2024-06-03T09:39:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Stop assigning a string constant to the file parent buffer and instead\nassign an allocated string. While the code is fine in practice, it will\nbreak once we compile with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..bd3e663c24 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar *parent_data_to_free = NULL;\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = parent_data_to_free = xstrdup(\"\");\n \t\tfile_parent.size = 0;\n \t}\n \n@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n \tdiff_ranges_release(&diff);\n \n+\tfree(parent_data_to_free);\n \treturn ((*diff_out)->parent.nr > 0);\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496098","messageId":"03dbdd235b880e65012018a5f6418d23d02f697f.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 09/27] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:38Z","receivedAt":"2024-06-03T09:39:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex bd3e663c24..67c80b39a0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496099","messageId":"6fcb7d6685e171c5ec781ed86058906129232fd0.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 10/27] entry: refactor how we remove items for delayed checkouts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:43Z","receivedAt":"2024-06-03T09:39:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When finalizing a delayed checkout, we sort out several strings from the\npassed-in string list by first assigning the empty string to those\nfilters and then calling `string_list_remove_empty_items()`. Assigning\nthe empty string will cause compiler warnings though as the string is\na `char *` once we enable `-Wwrite-strings`.\n\nRefactor the code to use a `NULL` pointer with `filter_string_list()`\ninstead to avoid this warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n entry.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..f291d8eee6 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)\n \treturn !available;\n }\n \n+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)\n+{\n+\treturn !!item->string;\n+}\n+\n int finish_delayed_checkout(struct checkout *state, int show_progress)\n {\n \tint errs = 0;\n@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = NULL;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\n@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\terrs = 1;\n \t\t\t}\n \t\t}\n-\t\tstring_list_remove_empty_items(&dco->filters, 0);\n+\n+\t\tfilter_string_list(&dco->filters, 0, string_is_not_null, NULL);\n \t}\n \tstop_progress(&progress);\n \tstring_list_clear(&dco->filters, 0);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496100","messageId":"81e20a7bb88b1ff0308517bc19a73aad93c027e9.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 11/27] ident: add casts for fallback name and GECOS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:48Z","receivedAt":"2024-06-03T09:39:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `xgetpwuid_self()`, we return a fallback identity when it was not\npossible to look up the current identity. This fallback identity needs\nto be internal and must never be written to by the calles as specified\nby getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked\nas non-constant though, it will cause a warning to assign constant\nstrings to them once compiling with `-Wwrite-strings`.\n\nAdd explicit casts to avoid the warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ident.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..caf41fb2a9 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tfallback.pw_name = (char *) \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tfallback.pw_gecos = (char *) \"Unknown\";\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496101","messageId":"384b4c896756b6c709506a23bb2f751bf1fbab03.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:52Z","receivedAt":"2024-06-03T09:39:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The buffers of cached objects are never modified, but are still stored\nas a non-constant pointer. This will cause a compiler warning once we\nenable the `-Wwrite-strings` compiler warning as we assign an empty\nconstant string when initializing the static `empty_tree` cached object.\n\nConvert the field to be constant. This requires us to shuffle around\nthe code a bit because we memcpy(3P) into the allocated buffer in\n`pretend_object_file()`. This is easily fixed though by allocating the\nbuffer into a temporary variable first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..3afe9fce06 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)\n static struct cached_object {\n \tstruct object_id oid;\n \tenum object_type type;\n-\tvoid *buf;\n+\tconst void *buf;\n \tunsigned long size;\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n@@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \t\t\tstruct object_id *oid)\n {\n \tstruct cached_object *co;\n+\tchar *co_buf;\n+\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n@@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n-\tco->buf = xmalloc(len);\n-\tmemcpy(co->buf, buf, len);\n+\tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496102","messageId":"a1e8e776417c2c754ba5683f8931f520adc47b02.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 13/27] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:39:57Z","receivedAt":"2024-06-03T09:40:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3afe9fce06..b5b5a59dc6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2485,12 +2485,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2500,11 +2501,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2521,8 +2521,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496103","messageId":"4d95abe9ccd41f97733995c06ba41ebeef584ac9.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 14/27] pretty: add casts for decoration option pointers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:02Z","receivedAt":"2024-06-03T09:40:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct decoration_options` have a prefix and suffix field which are\nboth non-constant, but we assign a constant pointer to them. This is\nsafe to do because we pass them to `format_decorations()`, which never\nmodifies these pointers, and then immediately discard the structure. Add\nexplicit casts to avoid compilation warnings with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n pretty.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1df9d635fb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tcase 'D':\n \t\t{\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = (char *) \"\",\n+\t\t\t\t.suffix = (char *) \"\",\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496104","messageId":"3d92528125ee419aefdac790dc1a4106be632c60.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 15/27] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:07Z","receivedAt":"2024-06-03T09:40:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 18 ++++++++++++++++--\n compat/mingw.c    | 28 ++++++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..5aa320306b 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -1,6 +1,8 @@\n #include \"../git-compat-util.h\"\n #include \"../strbuf.h\"\n \n+static char current_directory[] = \".\";\n+\n /* Adapted from libiberty's basename.c.  */\n char *gitbasename (char *path)\n {\n@@ -10,7 +12,13 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * basename(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \"\";\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -34,7 +42,13 @@ char *gitdirname(char *path)\n \tint dos_drive_prefix;\n \n \tif (!p)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * dirname(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \"\";\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..d378cd04cb 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\t/*\n+\t\t * Data returned by getpwuid(3P) is treated as internal and\n+\t\t * must never be written to or freed.\n+\t\t */\n+\t\tp->pw_gecos = (char *) \"unknown\";\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496105","messageId":"8a98078439163f8af7ad537bc4a010043f758a2c.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 16/27] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:12Z","receivedAt":"2024-06-03T09:40:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496106","messageId":"26c4c76c8684788e8be6e378c8eed63011050518.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 17/27] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:17Z","receivedAt":"2024-06-03T09:40:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496107","messageId":"e3227bd3f33ea395b083d13a25ee74a08315bf50.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 18/27] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:22Z","receivedAt":"2024-06-03T09:40:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496108","messageId":"af82e49682d065a07809dbb55d40a6f92c0ff17d.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 19/27] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:27Z","receivedAt":"2024-06-03T09:40:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..d0f767df8e 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchrnul(arg, ' ');\n+\t\t\tsize_t arglen = value - arg;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n+\t\t\tif (*value)\n+\t\t\t\tvalue++; /* skip over SP */\n \t\t\telse\n \t\t\t\tvalue = \"true\";\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496109","messageId":"077f10d0dc80124e3c76e5918ba07241e29b5f28.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 20/27] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:32Z","receivedAt":"2024-06-03T09:40:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496110","messageId":"fb240598b4ecc5b64069c5e7bd8cd9f71d16b564.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 21/27] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:37Z","receivedAt":"2024-06-03T09:40:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496111","messageId":"291030faa76da72250ae96dfd426b47d7eed032b.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 22/27] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:42Z","receivedAt":"2024-06-03T09:40:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496112","messageId":"9b376a313fa0d94a9fff88c32d1911b4b59fce2d.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 23/27] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:47Z","receivedAt":"2024-06-03T09:40:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused member `imap_server_conf::name`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 42 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..da3e7ec17e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n+\tint ret;\n \n \tsetup_git_directory_gently(&nongit_ok);\n \tgit_config(git_imap_config, &server);\n@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496113","messageId":"0a84d66e68b185b792ac3b3925d1bf7394e03362.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:54Z","receivedAt":"2024-06-03T09:40:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::default_backend` field is a non-constant\nstring, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\nRefactor the code to initialize and release options via two functions\n`rebase_options_init()` and `rebase_options_release()`. Like this, we\ncan easily adapt the former funnction to use `xstrdup()` on the default\nvalue without hiding it away in a macro.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..11f276012c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -131,25 +131,40 @@ struct rebase_options {\n \tint config_update_refs;\n };\n \n-#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n-\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n-\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n-\t\t.flags = REBASE_NO_QUIET, \t\t\\\n-\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n-\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n-\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n-\t\t.fork_point = -1,\t\t\t\\\n-\t\t.reapply_cherry_picks = -1,             \\\n-\t\t.allow_empty_message = 1,               \\\n-\t\t.autosquash = -1,                       \\\n-\t\t.rebase_merges = -1,                    \\\n-\t\t.config_rebase_merges = -1,             \\\n-\t\t.update_refs = -1,                      \\\n-\t\t.config_update_refs = -1,               \\\n-\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n-\t}\n+static void rebase_options_init(struct rebase_options *opts)\n+{\n+\tmemset(opts, 0, sizeof(*opts));\n+\topts->type = REBASE_UNSPECIFIED;\n+\topts->empty = EMPTY_UNSPECIFIED;\n+\topts->default_backend = xstrdup(\"merge\");\n+\topts->keep_empty = 1;\n+\topts->flags = REBASE_NO_QUIET;\n+\tstrvec_init(&opts->git_am_opts);\n+\tstring_list_init_nodup(&opts->exec);\n+\tstrbuf_init(&opts->git_format_patch_opt, 0);\n+\topts->fork_point = -1;\n+\topts->reapply_cherry_picks = -1;\n+\topts->allow_empty_message = 1;\n+\topts->autosquash = -1;\n+\topts->rebase_merges = -1;\n+\topts->config_rebase_merges = -1;\n+\topts->update_refs = -1;\n+\topts->config_update_refs = -1;\n+\tstring_list_init_nodup(&opts->strategy_opts);\n+}\n+\n+static void rebase_options_release(struct rebase_options *opts)\n+{\n+\tfree(opts->default_backend);\n+\tfree(opts->reflog_action);\n+\tfree(opts->head_name);\n+\tstrvec_clear(&opts->git_am_opts);\n+\tfree(opts->gpg_sign_opt);\n+\tstring_list_clear(&opts->exec, 0);\n+\tfree(opts->strategy);\n+\tstring_list_clear(&opts->strategy_opts, 0);\n+\tstrbuf_release(&opts->git_format_patch_opt);\n+}\n \n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n {\n@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)\n \n int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n-\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n+\tstruct rebase_options options;\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tint i;\n \n+\trebase_options_init(&options);\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n-\tfree(options.reflog_action);\n-\tfree(options.head_name);\n-\tstrvec_clear(&options.git_am_opts);\n-\tfree(options.gpg_sign_opt);\n-\tstring_list_clear(&options.exec, 0);\n-\tfree(options.strategy);\n-\tstring_list_clear(&options.strategy_opts, 0);\n-\tstrbuf_release(&options.git_format_patch_opt);\n+\trebase_options_release(&options);\n \tfree(squash_onto_name);\n \tfree(keep_base_onto_name);\n \treturn !!ret;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496114","messageId":"5c8bee3695e224fb1d7b1d0071e32e133065ee0c.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 25/27] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:40:59Z","receivedAt":"2024-06-03T09:41:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::strategy` field is a `char *`, but we do end\nup assigning string constants to it in two cases:\n\n  - When being passed a `--strategy=` option via the command line.\n\n  - When being passed a strategy option via `--strategy-option=`, but\n    not a strategy.\n\nThis will cause warnings once we enable `-Wwrite-strings`.\n\nIdeally, we'd just convert the field to be a `const char *`. But we also\nassign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\nhave to strdup(3P) into it.\n\nInstead, refactor the code to make sure that we only ever assign\nallocated strings to this field.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 11f276012c..26068cf542 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n \tstruct rebase_options options;\n \tconst char *branch_name;\n+\tconst char *strategy_opt = NULL;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n+\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n \t\t\t\tN_(\"option\"),\n@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\tif (strategy_opt)\n+\t\toptions.strategy = xstrdup(strategy_opt);\n+\telse if (options.strategy_opts.nr && !options.strategy)\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496115","messageId":"84c0149c8fadd5b3f002593873bea3d447832f2a.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 26/27] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:41:04Z","receivedAt":"2024-06-03T09:41:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496116","messageId":"342754713476cd680b31420681fb1da624c91675.1717402403.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"[PATCH v3 27/27] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:41:08Z","receivedAt":"2024-06-03T09:41:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496163","messageId":"CAPig+cTC83ZE2kGy=epeRFJ7a3jjRQXsvUYzB6jdOYfmy0yVOg@mail.gmail.com","threadId":"61561","inReplyTo":"3d92528125ee419aefdac790dc1a4106be632c60.1717402403.git.ps@pks.im","subject":"Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2024-06-03T16:57:45Z","receivedAt":"2024-06-03T16:57:57Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:\n> Adjust various places in our Win32 compatibility layer where we are not\n> assigning string constants to `const char *` variables.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> diff --git a/compat/basename.c b/compat/basename.c\n> @@ -1,6 +1,8 @@\n> +static char current_directory[] = \".\";\n> @@ -10,7 +12,13 @@ char *gitbasename (char *path)\n>         if (!path || !*path)\n> -               return \".\";\n> +               /*\n> +                * basename(3P) is mis-specified because it returns a\n> +                * non-constant pointer even though it is specified to return a\n> +                * pointer to internal memory at times. The cast is a result of\n> +                * that.\n> +                */\n> +               return (char *) \"\";\n\nThe change from returning \".\" to returning \"\" is unexplained by the\ncommit message. Did you mean to return the newly-introduced\n`current_directory` instead?\n\n> @@ -34,7 +42,13 @@ char *gitdirname(char *path)\n>         if (!p)\n> -               return \".\";\n> +               /*\n> +                * dirname(3P) is mis-specified because it returns a\n> +                * non-constant pointer even though it is specified to return a\n> +                * pointer to internal memory at times. The cast is a result of\n> +                * that.\n> +                */\n> +               return (char *) \"\";\n\nDitto.\n"},{"id":"496164","messageId":"xmqqh6ea3qt4.fsf@gitster.g","threadId":"61561","inReplyTo":"cover.1717402403.git.ps@pks.im","subject":"Re: [PATCH v3 00/27] Compile with `-Wwrite-strings`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-03T16:59:51Z","receivedAt":"2024-06-03T16:59:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Changes compared to v2:\n>\n>   - I have split up the second patch into multiple patches. This was\n>     done so that we can have a deeper look at the respective sites that\n>     need adjustment.\n\nWe will see how well this helps reviewer.\n\n>   - I dropped the local string arrays that I used in v2 and instead now\n>     use string constants with casts in some places where we expect that\n>     those should never be written to. This has the benefit that we would\n>     segfault in case that expectation is violated, instead of silently\n>     allowing a write to those local arrays.\n\nNice.\n\n>   - I adapted multiple commit messages to not only mention freeing of\n>     string constants, but also that writing to string constants is\n>     illegal.\n\nNice again.\n\n> Due to the split-up patch the range-diff got quite messy and was barely\n> readable anymore. I thus included an interdiff instead, which should be\n> way easier to read.\n\nTriply nice.\n\nThanks.\n"},{"id":"496167","messageId":"xmqqo78h3nms.fsf@gitster.g","threadId":"61561","inReplyTo":"085d90c8b63e937e3d76637ce2422320b5801ebb.1717402403.git.ps@pks.im","subject":"Re: [PATCH v3 03/27] refs/reftable: stop micro-optimizing refname allocations on copy","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-03T18:08:27Z","receivedAt":"2024-06-03T18:08:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When copying refs, we execute `write_copy_table()` to write the new\n> table. As the names arge given to use via `arg->newname` and\n\narge???\n\n> `arg->oldname`, respectively, we optimize away some allocations by\n> assigning those fields to the reftable records we are about to write.\n> This requires us to cast the input to `char *` pointers as they are in\n> fact constant strings. Later on, we then unset the refname for all of\n> the records before calling `reftable_log_record_release()` on them.\n>\n> We also do this when assigning the \"HEAD\" constant, but here we do not\n> cast because its type is `char[]` by default. It's about to be turned\n> into `const char *` though once we enable `-Wwrite-strings` and will\n> thus cause another warning.\n>\n> It's quite dubious whether this micro-optimization really helps. We're\n> about to write to disk anyway, which is going to be way slower than a\n> small handful of allocations. Let's drop the optimization altogther and\n> instead copy arguments to simplify the code and avoid the future warning\n> with `-Wwrite-strings`.\n\nIt certainly makes the final clean-up part simpler, which is a good\nsign.\n"},{"id":"496168","messageId":"xmqqikyp3nhb.fsf@gitster.g","threadId":"61561","inReplyTo":"db4d0620146fdf7acd59c649d93c0da68289a3d7.1717402403.git.ps@pks.im","subject":"Re: [PATCH v3 05/27] refspec: remove global tag refspec structure","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-03T18:11:44Z","receivedAt":"2024-06-03T18:11:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We have a global tag refspec structure that is used by both git-clone(1)\n> and git-fetch(1). Initialization fo the structure will break once we\n\n\"fo\" -> \"of\".\n\n> enable `-Wwrite-strings`, even though the breakage is harmless. While we\n> could just add casts, the structure isn't really required in the first\n> place as we can simply initialize the structures at the respective\n> callsites.\n>\n> Refactor the code accordingly.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c |  8 ++++++--\n>  builtin/fetch.c | 11 ++++++++---\n>  refspec.c       | 13 -------------\n>  refspec.h       |  1 -\n>  4 files changed, 14 insertions(+), 19 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 92ab7d7165..bde1d284a2 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n>  \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n>  \tstruct ref *local_refs = head;\n>  \tstruct ref **tail = head ? &head->next : &local_refs;\n> +\tstruct refspec_item tag_refspec;\n> +\n> +\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n>  \n>  \tif (option_single_branch) {\n>  \t\tstruct ref *remote_head = NULL;\n> @@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n>  \t\t\t\t\t      &tail, 0);\n>  \n>  \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n> -\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n> +\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n>  \t\t}\n>  \t\tfree_refs(remote_head);\n>  \t} else {\n> @@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n>  \t}\n>  \n>  \tif (!option_mirror && !option_single_branch && !option_no_tags)\n> -\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n> +\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n>  \n> +\trefspec_item_clear(&tag_refspec);\n>  \treturn local_refs;\n>  }\n>  \n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index 75255dc600..06b60867f5 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n>  \t\t}\n>  \t}\n>  \n> -\tif (tags == TAGS_SET)\n> +\tif (tags == TAGS_SET) {\n> +\t\tstruct refspec_item tag_refspec;\n> +\n>  \t\t/* also fetch all tags */\n> -\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n> -\telse if (tags == TAGS_DEFAULT && *autotags)\n> +\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n> +\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n> +\t\trefspec_item_clear(&tag_refspec);\n> +\t} else if (tags == TAGS_DEFAULT && *autotags) {\n>  \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n> +\t}\n>  \n>  \t/* Now append any refs to be updated opportunistically: */\n>  \t*tail = orefs;\n> diff --git a/refspec.c b/refspec.c\n> index d60932f4de..1df5de6c2f 100644\n> --- a/refspec.c\n> +++ b/refspec.c\n> @@ -7,19 +7,6 @@\n>  #include \"refspec.h\"\n>  #include \"strbuf.h\"\n>  \n> -static struct refspec_item s_tag_refspec = {\n> -\t.force = 0,\n> -\t.pattern = 1,\n> -\t.matching = 0,\n> -\t.exact_sha1 = 0,\n> -\t.negative = 0,\n> -\t.src = \"refs/tags/*\",\n> -\t.dst = \"refs/tags/*\",\n> -};\n> -\n> -/* See TAG_REFSPEC for the string version */\n> -const struct refspec_item *tag_refspec = &s_tag_refspec;\n> -\n>  /*\n>   * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n>   * Returns 1 if successful and 0 if the refspec is invalid.\n> diff --git a/refspec.h b/refspec.h\n> index 8c0c446993..754be45cee 100644\n> --- a/refspec.h\n> +++ b/refspec.h\n> @@ -2,7 +2,6 @@\n>  #define REFSPEC_H\n>  \n>  #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n> -extern const struct refspec_item *tag_refspec;\n>  \n>  /**\n>   * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n"},{"id":"496171","messageId":"xmqq4ja93l16.fsf@gitster.g","threadId":"61561","inReplyTo":"CAPig+cTC83ZE2kGy=epeRFJ7a3jjRQXsvUYzB6jdOYfmy0yVOg@mail.gmail.com","subject":"Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-03T19:04:37Z","receivedAt":"2024-06-03T19:04:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:\n>> Adjust various places in our Win32 compatibility layer where we are not\n>> assigning string constants to `const char *` variables.\n>>\n>> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>> ---\n>> diff --git a/compat/basename.c b/compat/basename.c\n>> @@ -1,6 +1,8 @@\n>> +static char current_directory[] = \".\";\n>> @@ -10,7 +12,13 @@ char *gitbasename (char *path)\n>>         if (!path || !*path)\n>> -               return \".\";\n>> +               /*\n>> +                * basename(3P) is mis-specified because it returns a\n>> +                * non-constant pointer even though it is specified to return a\n>> +                * pointer to internal memory at times. The cast is a result of\n>> +                * that.\n>> +                */\n>> +               return (char *) \"\";\n>\n> The change from returning \".\" to returning \"\" is unexplained by the\n> commit message. Did you mean to return the newly-introduced\n> `current_directory` instead?\n\nI suspect that these places wanted to return (char *) \".\" instead,\nwithout introducing a new variable.\n\n\n\n"},{"id":"496197","messageId":"Zl63Y7AIkguoA55l@framework","threadId":"61561","inReplyTo":"xmqq4ja93l16.fsf@gitster.g","subject":"Re: [PATCH v3 15/27] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T06:42:43Z","receivedAt":"2024-06-04T06:42:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Jun 03, 2024 at 12:04:37PM -0700, Junio C Hamano wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n> > On Mon, Jun 3, 2024 at 5:46 AM Patrick Steinhardt <ps@pks.im> wrote:\n> >> Adjust various places in our Win32 compatibility layer where we are not\n> >> assigning string constants to `const char *` variables.\n> >>\n> >> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> >> ---\n> >> diff --git a/compat/basename.c b/compat/basename.c\n> >> @@ -1,6 +1,8 @@\n> >> +static char current_directory[] = \".\";\n> >> @@ -10,7 +12,13 @@ char *gitbasename (char *path)\n> >>         if (!path || !*path)\n> >> -               return \".\";\n> >> +               /*\n> >> +                * basename(3P) is mis-specified because it returns a\n> >> +                * non-constant pointer even though it is specified to return a\n> >> +                * pointer to internal memory at times. The cast is a result of\n> >> +                * that.\n> >> +                */\n> >> +               return (char *) \"\";\n> >\n> > The change from returning \".\" to returning \"\" is unexplained by the\n> > commit message. Did you mean to return the newly-introduced\n> > `current_directory` instead?\n> \n> I suspect that these places wanted to return (char *) \".\" instead,\n> without introducing a new variable.\n\nOof, that's bad. Thanks for catching this!\n\nPatrick\n"},{"id":"496250","messageId":"cover.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH v4 00/27] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:36:50Z","receivedAt":"2024-06-04T12:36:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is another minor reroll of my patch series that prepares the code\nbase to compile with `-Wwrite-strings`.\n\nThere are only some small changes compared to v3:\n\n  - Fix typos in some commit messages.\n\n  - Fix accidental conversion of \".\" to \"\" in basename/dirname\n    compatibility code. Also, remove the unused variable.\n\nThanks for your reviews!\n\nPatrick\n\nPatrick Steinhardt (27):\n  global: improve const correctness when assigning string constants\n  global: convert intentionally-leaking config strings to consts\n  refs/reftable: stop micro-optimizing refname allocations on copy\n  reftable: cast away constness when assigning constants to records\n  refspec: remove global tag refspec structure\n  builtin/remote: cast away constness in `get_head_names()`\n  diff: cast string constant in `fill_textconv()`\n  line-log: stop assigning string constant to file parent buffer\n  line-log: always allocate the output prefix\n  entry: refactor how we remove items for delayed checkouts\n  ident: add casts for fallback name and GECOS\n  object-file: mark cached object buffers as const\n  object-file: make `buf` parameter of `index_mem()` a constant\n  pretty: add casts for decoration option pointers\n  compat/win32: fix const-correctness with string constants\n  http: do not assign string constant to non-const field\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: do not assign default backend to non-constant field\n  builtin/rebase: always store allocated string in `options.strategy`\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  81 ++++++++++++----------\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |  12 ++--\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  16 ++++-\n compat/mingw.c               |  28 ++++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   6 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |  14 ++--\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   4 +-\n imap-send.c                  | 130 ++++++++++++++++++++---------------\n line-log.c                   |  22 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  23 ++++---\n parse-options.h              |   2 +-\n pretty.c                     |   6 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |  28 ++++----\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics.c            |  15 ++--\n reftable/basics.h            |   4 +-\n reftable/basics_test.c       |   4 +-\n reftable/block_test.c        |   2 +-\n reftable/merged_test.c       |  44 ++++++------\n reftable/readwrite_test.c    |  32 ++++-----\n reftable/record.c            |   6 +-\n reftable/stack.c             |  10 +--\n reftable/stack_test.c        |  56 +++++++--------\n remote-curl.c                |  53 +++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 68 files changed, 468 insertions(+), 386 deletions(-)\n\nRange-diff against v3:\n 1:  e01fde88fe =  1:  e01fde88fe global: improve const correctness when assigning string constants\n 2:  92cb0b28c6 =  2:  92cb0b28c6 global: convert intentionally-leaking config strings to consts\n 3:  085d90c8b6 !  3:  379145478c refs/reftable: stop micro-optimizing refname allocations on copy\n    @@ Commit message\n         refs/reftable: stop micro-optimizing refname allocations on copy\n     \n         When copying refs, we execute `write_copy_table()` to write the new\n    -    table. As the names arge given to use via `arg->newname` and\n    +    table. As the names are given to us via `arg->newname` and\n         `arg->oldname`, respectively, we optimize away some allocations by\n    -    assigning those fields to the reftable records we are about to write.\n    -    This requires us to cast the input to `char *` pointers as they are in\n    -    fact constant strings. Later on, we then unset the refname for all of\n    -    the records before calling `reftable_log_record_release()` on them.\n    +    assigning those fields to the reftable records we are about to write\n    +    directly, without duplicating them. This requires us to cast the input\n    +    to `char *` pointers as they are in fact constant strings. Later on, we\n    +    then unset the refname for all of the records before calling\n    +    `reftable_log_record_release()` on them.\n     \n         We also do this when assigning the \"HEAD\" constant, but here we do not\n         cast because its type is `char[]` by default. It's about to be turned\n 4:  8692d9d9af =  4:  d0a2a2f6c5 reftable: cast away constness when assigning constants to records\n 5:  db4d062014 !  5:  ead27d3d97 refspec: remove global tag refspec structure\n    @@ Commit message\n         refspec: remove global tag refspec structure\n     \n         We have a global tag refspec structure that is used by both git-clone(1)\n    -    and git-fetch(1). Initialization fo the structure will break once we\n    +    and git-fetch(1). Initialization of the structure will break once we\n         enable `-Wwrite-strings`, even though the breakage is harmless. While we\n         could just add casts, the structure isn't really required in the first\n         place as we can simply initialize the structures at the respective\n 6:  6a3c8d351e =  6:  7cb5df9182 builtin/remote: cast away constness in `get_head_names()`\n 7:  750429472a =  7:  6e631a9ea4 diff: cast string constant in `fill_textconv()`\n 8:  cc8fa1896d =  8:  ac164651a3 line-log: stop assigning string constant to file parent buffer\n 9:  03dbdd235b =  9:  b717af02f0 line-log: always allocate the output prefix\n10:  6fcb7d6685 = 10:  b46dd3210d entry: refactor how we remove items for delayed checkouts\n11:  81e20a7bb8 = 11:  030dbd0288 ident: add casts for fallback name and GECOS\n12:  384b4c8967 = 12:  ecca8e973d object-file: mark cached object buffers as const\n13:  a1e8e77641 = 13:  62f0e47f94 object-file: make `buf` parameter of `index_mem()` a constant\n14:  4d95abe9cc = 14:  e057ead2b4 pretty: add casts for decoration option pointers\n15:  3d92528125 ! 15:  06b6120d26 compat/win32: fix const-correctness with string constants\n    @@ Commit message\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## compat/basename.c ##\n    -@@\n    - #include \"../git-compat-util.h\"\n    - #include \"../strbuf.h\"\n    - \n    -+static char current_directory[] = \".\";\n    -+\n    - /* Adapted from libiberty's basename.c.  */\n    - char *gitbasename (char *path)\n    - {\n     @@ compat/basename.c: char *gitbasename (char *path)\n      \t\tskip_dos_drive_prefix(&path);\n      \n    @@ compat/basename.c: char *gitbasename (char *path)\n     +\t\t * pointer to internal memory at times. The cast is a result of\n     +\t\t * that.\n     +\t\t */\n    -+\t\treturn (char *) \"\";\n    ++\t\treturn (char *) \".\";\n      \n      \tfor (base = path; *path; path++) {\n      \t\tif (!is_dir_sep(*path))\n    @@ compat/basename.c: char *gitdirname(char *path)\n     +\t\t * pointer to internal memory at times. The cast is a result of\n     +\t\t * that.\n     +\t\t */\n    -+\t\treturn (char *) \"\";\n    ++\t\treturn (char *) \".\";\n      \n      \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n      \t\tgoto dot;\n16:  8a98078439 = 16:  a8ef39d73d http: do not assign string constant to non-const field\n17:  26c4c76c86 = 17:  9d596a07c5 parse-options: cast long name for OPTION_ALIAS\n18:  e3227bd3f3 = 18:  4019b532f9 send-pack: always allocate receive status\n19:  af82e49682 = 19:  f2f1ada143 remote-curl: avoid assigning string constant to non-const variable\n20:  077f10d0dc = 20:  27660b908c revision: always store allocated strings in output encoding\n21:  fb240598b4 = 21:  ef43c1b18f mailmap: always store allocated strings in mailmap blob\n22:  291030faa7 = 22:  0a69ce4b44 imap-send: drop global `imap_server_conf` variable\n23:  9b376a313f = 23:  9ccafd286b imap-send: fix leaking memory in `imap_server_conf`\n24:  0a84d66e68 = 24:  e19457d20c builtin/rebase: do not assign default backend to non-constant field\n25:  5c8bee3695 = 25:  f548241960 builtin/rebase: always store allocated string in `options.strategy`\n26:  84c0149c8f = 26:  78ac075644 builtin/merge: always store allocated strings in `pull_twohead`\n27:  3427547134 = 27:  0cd4ce07d8 config.mak.dev: enable `-Wwrite-strings` warning\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496251","messageId":"e01fde88fe35d6da358af5689320db50750c29dc.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 01/27] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:36:55Z","receivedAt":"2024-06-04T12:36:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/basics.c            | 15 +++++------\n reftable/basics.h            |  4 +--\n reftable/basics_test.c       |  4 +--\n reftable/record.c            |  6 ++---\n reftable/stack.c             | 10 ++++---\n reftable/stack_test.c        |  8 +++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 38 files changed, 106 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/basics.c b/reftable/basics.c\nindex fea711db7e..0058619ca6 100644\n--- a/reftable/basics.c\n+++ b/reftable/basics.c\n@@ -67,9 +67,9 @@ void free_names(char **a)\n \treftable_free(a);\n }\n \n-size_t names_length(char **names)\n+size_t names_length(const char **names)\n {\n-\tchar **p = names;\n+\tconst char **p = names;\n \twhile (*p)\n \t\tp++;\n \treturn p - names;\n@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)\n \t*namesp = names;\n }\n \n-int names_equal(char **a, char **b)\n+int names_equal(const char **a, const char **b)\n {\n-\tint i = 0;\n-\tfor (; a[i] && b[i]; i++) {\n-\t\tif (strcmp(a[i], b[i])) {\n+\tsize_t i = 0;\n+\tfor (; a[i] && b[i]; i++)\n+\t\tif (strcmp(a[i], b[i]))\n \t\t\treturn 0;\n-\t\t}\n-\t}\n-\n \treturn a[i] == b[i];\n }\n \ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 523ecd5307..c8fec68d4e 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -42,10 +42,10 @@ void free_names(char **a);\n void parse_names(char *buf, int size, char ***namesp);\n \n /* compares two NULL-terminated arrays of strings. */\n-int names_equal(char **a, char **b);\n+int names_equal(const char **a, const char **b);\n \n /* returns the array size of a NULL-terminated array of strings. */\n-size_t names_length(char **names);\n+size_t names_length(const char **names);\n \n /* Allocation routines; they invoke the functions set through\n  * reftable_set_alloc() */\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..13bc761817 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,8 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tconst char *names[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex a59ebe038d..09549c51c9 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n \treturn cur;\n }\n \n-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+static int reftable_stack_reload_once(struct reftable_stack *st,\n+\t\t\t\t      const char **names,\n \t\t\t\t      int reuse_open)\n {\n \tsize_t cur_len = !st->merged ? 0 : st->merged->stack_len;\n@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n \n \twhile (*names) {\n \t\tstruct reftable_reader *rd = NULL;\n-\t\tchar *name = *names++;\n+\t\tconst char *name = *names++;\n \n \t\t/* this is linear; we assume compaction keeps the number of\n \t\t   tables under control so this is not quadratic. */\n@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\t\t\tgoto out;\n \t\t}\n \n-\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\terr = reftable_stack_reload_once(st, (const char **) names, reuse_open);\n \t\tif (!err)\n \t\t\tbreak;\n \t\tif (err != REFTABLE_NOT_EXIST_ERROR)\n@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\terr = read_lines(st->list_file, &names_after);\n \t\tif (err < 0)\n \t\t\tgoto out;\n-\t\tif (names_equal(names_after, names)) {\n+\t\tif (names_equal((const char **) names_after,\n+\t\t\t\t(const char **) names)) {\n \t\t\terr = REFTABLE_NOT_EXIST_ERROR;\n \t\t\tgoto out;\n \t\t}\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..07d89b45da 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,9 +116,9 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n \n \tEXPECT(names_equal(a, a));\n \tEXPECT(!names_equal(a, b));\ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496252","messageId":"92cb0b28c65bed0f3cd10e6c4c57b98f199536a9.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 02/27] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:00Z","receivedAt":"2024-06-04T12:37:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and the struct memers are never\n    written to or free'd.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never written to or free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never written to or free'd.\n\nAll of these are intentionally kept alive and are never written to.\nFurthermore, all of these are being assigned both string constants in\nsome places, and allocated strings in other places. This will cause\nwarnings once we enable `-Wwrite-strings`, so let's mark the respective\nfields as `const char *` and cast away the constness when assigning\nthose values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496253","messageId":"379145478cbbf59387c799952a97abc16a528eab.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 03/27] refs/reftable: stop micro-optimizing refname allocations on copy","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:04Z","receivedAt":"2024-06-04T12:37:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When copying refs, we execute `write_copy_table()` to write the new\ntable. As the names are given to us via `arg->newname` and\n`arg->oldname`, respectively, we optimize away some allocations by\nassigning those fields to the reftable records we are about to write\ndirectly, without duplicating them. This requires us to cast the input\nto `char *` pointers as they are in fact constant strings. Later on, we\nthen unset the refname for all of the records before calling\n`reftable_log_record_release()` on them.\n\nWe also do this when assigning the \"HEAD\" constant, but here we do not\ncast because its type is `char[]` by default. It's about to be turned\ninto `const char *` though once we enable `-Wwrite-strings` and will\nthus cause another warning.\n\nIt's quite dubious whether this micro-optimization really helps. We're\nabout to write to disk anyway, which is going to be way slower than a\nsmall handful of allocations. Let's drop the optimization altogther and\ninstead copy arguments to simplify the code and avoid the future warning\nwith `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c | 28 ++++++++++++++++------------\n 1 file changed, 16 insertions(+), 12 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..e77faa2b9d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t * old reference.\n \t */\n \trefs[0] = old_ref;\n-\trefs[0].refname = (char *)arg->newname;\n+\trefs[0].refname = xstrdup(arg->newname);\n \trefs[0].update_index = creation_ts;\n \tif (arg->delete_old) {\n-\t\trefs[1].refname = (char *)arg->oldname;\n+\t\trefs[1].refname = xstrdup(arg->oldname);\n \t\trefs[1].value_type = REFTABLE_REF_DELETION;\n \t\trefs[1].update_index = deletion_ts;\n \t}\n@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \t\tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n \t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = xstrdup(\"HEAD\");\n+\t\t\tlogs[logs_nr].value.update.name =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.name);\n+\t\t\tlogs[logs_nr].value.update.email =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.email);\n+\t\t\tlogs[logs_nr].value.update.message =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.message);\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\tlogs[logs_nr].refname = (char *)arg->newname;\n+\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n \t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\t */\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tlogs[logs_nr] = old_log;\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs_nr++;\n \n \t\t/*\n@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (arg->delete_old) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n-\t\t\tlogs[logs_nr].refname = (char *)arg->oldname;\n+\t\t\tlogs[logs_nr].refname = xstrdup(arg->oldname);\n \t\t\tlogs[logs_nr].value_type = REFTABLE_LOG_DELETION;\n \t\t\tlogs[logs_nr].update_index = old_log.update_index;\n \t\t\tlogs_nr++;\n@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \treftable_iterator_destroy(&it);\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n-\tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n-\t\t\tcontinue;\n-\t\tlogs[i].refname = NULL;\n+\tfor (i = 0; i < logs_nr; i++)\n \t\treftable_log_record_release(&logs[i]);\n-\t}\n \tfree(logs);\n+\tfor (i = 0; i < ARRAY_SIZE(refs); i++)\n+\t\treftable_ref_record_release(&refs[i]);\n \treftable_ref_record_release(&old_ref);\n \treftable_log_record_release(&old_log);\n \treturn ret;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496254","messageId":"d0a2a2f6c55af6da9703a1d64f45f77f4c00cd80.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 04/27] reftable: cast away constness when assigning constants to records","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:10Z","receivedAt":"2024-06-04T12:37:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reftable records are used in multiple ways throughout the reftable\nlibrary. In many of those cases they merely act as input to a function\nwithout getting modified by it at all. Most importantly, this happens\nwhen writing records and when querying for records.\n\nWe rely on this in our tests and thus assign string constants to those\nfields, which is about to generate warnings as those fields are of type\n`char *`. While we could go through the process and instead allocate\nthose strings in all of our tests, this feels quite unnecessary.\n\nInstead, add casts to `char *` for all of those strings. As this is part\nof our tests, this also nicely serves as a demonstration that nothing\nwrites or frees those string constants, which would otherwise lead to\nsegfaults.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reftable/block_test.c     |  2 +-\n reftable/merged_test.c    | 44 +++++++++++++++++------------------\n reftable/readwrite_test.c | 32 +++++++++++++-------------\n reftable/stack_test.c     | 48 +++++++++++++++++++--------------------\n 4 files changed, 63 insertions(+), 63 deletions(-)\n\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..90aecd5a7c 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = (char *) \"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..6d1159d12d 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n static void test_merged_between(void)\n {\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = (char *) \"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -165,38 +165,38 @@ static void test_merged(void)\n {\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = (char *) \"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,46 +291,46 @@ static void test_merged_logs(void)\n {\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -406,7 +406,7 @@ static void test_default_write_opts(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..c55019232b 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = (char *) \"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)\n \tint err;\n \tint i;\n \tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n \t\t\t.update_index = 0xa,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n \t\t\t\t\t   .tz_offset = 100,\n \t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n \t\t\t\t   } } };\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n@@ -156,15 +156,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = (char *) \"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -728,7 +728,7 @@ static void test_write_empty_key(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -752,18 +752,18 @@ static void test_write_key_order(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 07d89b45da..4abf92636d 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)\n \n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = (char *) \"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \n@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_addition *add = NULL;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = (char *) \"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = (char *) \"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = (char *) \"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = (char *) \"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)\n \tint err;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \n \t\t/*\n@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496255","messageId":"ead27d3d978701966cdc2884dab2db77fae5d031.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 05/27] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:14Z","receivedAt":"2024-06-04T12:37:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization of the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496256","messageId":"7cb5df9182e5739f54be089832d85e0447efe46d.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 06/27] builtin/remote: cast away constness in `get_head_names()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:19Z","receivedAt":"2024-06-04T12:37:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_head_names()`, we assign the \"refs/heads/*\" string constant to\n`struct refspec_item::{src,dst}`, which are both non-constant pointers.\nIdeally, we'd refactor the code such that both of these fields were\nconstant. But `struct refspec_item` is used for two different usecases\nwith conflicting requirements:\n\n  - To query for a source or destination based on the given refspec. The\n    caller either sets `src` or `dst` as the branch that we want to\n    search for, and the respective other field gets populated. The\n    fields should be constant when being used as a query parameter,\n    which is owned by the caller, and non-constant when being used as an\n    out parameter, which is owned by the refspec item. This is is\n    contradictory in itself already.\n\n  - To store refspec items with their respective source and destination\n    branches, in which case both fields should be owned by the struct.\n\nIdeally, we'd split up this interface to clearly separate between\nquerying and storing, which would enable us to clarify lifetimes of the\nstrings. This would be a much bigger undertaking though.\n\nInstead, accept the status quo for now and cast away the constness of\nthe source and destination patterns. We know that those are not being\nwritten to or freed, so while this is ugly it certainly is fine for now.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..b44f580b8c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n {\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n-\tstruct refspec_item refspec;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n \n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.force = 0;\n-\trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\n@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \n \tfree_refs(fetch_map);\n \tfree_refs(matches);\n-\n \treturn 0;\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496257","messageId":"6e631a9ea46aa563d197f4a057698a0af53ad50f.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 07/27] diff: cast string constant in `fill_textconv()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:24Z","receivedAt":"2024-06-04T12:37:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `fill_textconv()` function is responsible for converting an input\nfile with a textconv driver, which is then passed to the caller. Weirdly\nthough, the function also handles the case where there is no textconv\ndriver at all. In that case, it will return either the contents of the\npopulated filespec, or an empty string if the filespec is invalid.\n\nThese two cases have differing memory ownership semantics. When there is\na textconv driver, then the result is an allocated string. Otherwise,\nthe result is either a string constant or owned by the filespec struct.\nAll callers are in fact aware of this weirdness and only end up freeing\nthe output buffer when they had a textconv driver.\n\nIdeally, we'd split up this interface to only perform the conversion via\nthe textconv driver, and BUG in case the caller didn't provide one. This\nwould make memory ownership semantics much more straight forward. For\nnow though, let's simply cast the empty string constant to `char *` to\navoid a warning with `-Wwrite-strings`. This is equivalent to the same\ncast that we already have in `fill_mmfile()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..cecda216cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = (char *) \"\";\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496258","messageId":"ac164651a325476ecf041641c11785ac8c0c1501.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 08/27] line-log: stop assigning string constant to file parent buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:28Z","receivedAt":"2024-06-04T12:37:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Stop assigning a string constant to the file parent buffer and instead\nassign an allocated string. While the code is fine in practice, it will\nbreak once we compile with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..bd3e663c24 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar *parent_data_to_free = NULL;\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = parent_data_to_free = xstrdup(\"\");\n \t\tfile_parent.size = 0;\n \t}\n \n@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n \tdiff_ranges_release(&diff);\n \n+\tfree(parent_data_to_free);\n \treturn ((*diff_out)->parent.nr > 0);\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496259","messageId":"b717af02f0958b0c204164ba92453ea7845186b7.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 09/27] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:33Z","receivedAt":"2024-06-04T12:37:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex bd3e663c24..67c80b39a0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496260","messageId":"b46dd3210d3e6f73c84fba6aaa8f21929f7052bb.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 10/27] entry: refactor how we remove items for delayed checkouts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:37Z","receivedAt":"2024-06-04T12:37:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When finalizing a delayed checkout, we sort out several strings from the\npassed-in string list by first assigning the empty string to those\nfilters and then calling `string_list_remove_empty_items()`. Assigning\nthe empty string will cause compiler warnings though as the string is\na `char *` once we enable `-Wwrite-strings`.\n\nRefactor the code to use a `NULL` pointer with `filter_string_list()`\ninstead to avoid this warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n entry.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..f291d8eee6 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)\n \treturn !available;\n }\n \n+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)\n+{\n+\treturn !!item->string;\n+}\n+\n int finish_delayed_checkout(struct checkout *state, int show_progress)\n {\n \tint errs = 0;\n@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = NULL;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\n@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\terrs = 1;\n \t\t\t}\n \t\t}\n-\t\tstring_list_remove_empty_items(&dco->filters, 0);\n+\n+\t\tfilter_string_list(&dco->filters, 0, string_is_not_null, NULL);\n \t}\n \tstop_progress(&progress);\n \tstring_list_clear(&dco->filters, 0);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496261","messageId":"030dbd0288170eb1cc8fa0186f3b5e022ff67c40.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 11/27] ident: add casts for fallback name and GECOS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:42Z","receivedAt":"2024-06-04T12:37:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `xgetpwuid_self()`, we return a fallback identity when it was not\npossible to look up the current identity. This fallback identity needs\nto be internal and must never be written to by the calles as specified\nby getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked\nas non-constant though, it will cause a warning to assign constant\nstrings to them once compiling with `-Wwrite-strings`.\n\nAdd explicit casts to avoid the warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ident.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..caf41fb2a9 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tfallback.pw_name = (char *) \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tfallback.pw_gecos = (char *) \"Unknown\";\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496262","messageId":"ecca8e973df77cfc8233ab63bf7d1f6fa83031a3.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:47Z","receivedAt":"2024-06-04T12:37:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The buffers of cached objects are never modified, but are still stored\nas a non-constant pointer. This will cause a compiler warning once we\nenable the `-Wwrite-strings` compiler warning as we assign an empty\nconstant string when initializing the static `empty_tree` cached object.\n\nConvert the field to be constant. This requires us to shuffle around\nthe code a bit because we memcpy(3P) into the allocated buffer in\n`pretend_object_file()`. This is easily fixed though by allocating the\nbuffer into a temporary variable first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..3afe9fce06 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)\n static struct cached_object {\n \tstruct object_id oid;\n \tenum object_type type;\n-\tvoid *buf;\n+\tconst void *buf;\n \tunsigned long size;\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n@@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \t\t\tstruct object_id *oid)\n {\n \tstruct cached_object *co;\n+\tchar *co_buf;\n+\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n@@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n-\tco->buf = xmalloc(len);\n-\tmemcpy(co->buf, buf, len);\n+\tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496263","messageId":"62f0e47f94dad4f9a7318a0349bb83bcd29cba61.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 13/27] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:52Z","receivedAt":"2024-06-04T12:37:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3afe9fce06..b5b5a59dc6 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2485,12 +2485,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2500,11 +2501,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2521,8 +2521,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496264","messageId":"e057ead2b49cec1f5d7d5df1bf172bc45d139478.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 14/27] pretty: add casts for decoration option pointers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:37:57Z","receivedAt":"2024-06-04T12:38:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct decoration_options` have a prefix and suffix field which are\nboth non-constant, but we assign a constant pointer to them. This is\nsafe to do because we pass them to `format_decorations()`, which never\nmodifies these pointers, and then immediately discard the structure. Add\nexplicit casts to avoid compilation warnings with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n pretty.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1df9d635fb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tcase 'D':\n \t\t{\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = (char *) \"\",\n+\t\t\t\t.suffix = (char *) \"\",\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496265","messageId":"06b6120d266858ab4563781f9f2d47226681548e.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 15/27] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:01Z","receivedAt":"2024-06-04T12:38:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 16 ++++++++++++++--\n compat/mingw.c    | 28 ++++++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 31 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..c33579ef61 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -10,7 +10,13 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * basename(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -34,7 +40,13 @@ char *gitdirname(char *path)\n \tint dos_drive_prefix;\n \n \tif (!p)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * dirname(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..d378cd04cb 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\t/*\n+\t\t * Data returned by getpwuid(3P) is treated as internal and\n+\t\t * must never be written to or freed.\n+\t\t */\n+\t\tp->pw_gecos = (char *) \"unknown\";\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496266","messageId":"a8ef39d73d36333685f6a79f83d73ff73470fa48.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 16/27] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:06Z","receivedAt":"2024-06-04T12:38:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496267","messageId":"9d596a07c5e6418948632617d9cdf4a66796bd50.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 17/27] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:12Z","receivedAt":"2024-06-04T12:38:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496268","messageId":"4019b532f96536f57161ce90f75c5958939b29ce.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 18/27] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:17Z","receivedAt":"2024-06-04T12:38:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496269","messageId":"f2f1ada1432abffbed20227ef6ff8ca1571b9263.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 19/27] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:25Z","receivedAt":"2024-06-04T12:38:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..d0f767df8e 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchrnul(arg, ' ');\n+\t\t\tsize_t arglen = value - arg;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n+\t\t\tif (*value)\n+\t\t\t\tvalue++; /* skip over SP */\n \t\t\telse\n \t\t\t\tvalue = \"true\";\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496270","messageId":"27660b908ca6ca0a1d992d0f2e6f2989742fe956.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 20/27] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:30Z","receivedAt":"2024-06-04T12:38:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496271","messageId":"ef43c1b18f1f2d891ac1677c7dc1406b80e2add0.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 21/27] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:35Z","receivedAt":"2024-06-04T12:38:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496272","messageId":"0a69ce4b444b2924abd9275ae75f00ac2d545aca.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 22/27] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:40Z","receivedAt":"2024-06-04T12:38:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496273","messageId":"9ccafd286b3d15ccf6da83b9d2c0cba26672f35f.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 23/27] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:44Z","receivedAt":"2024-06-04T12:38:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused member `imap_server_conf::name`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 42 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..da3e7ec17e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n+\tint ret;\n \n \tsetup_git_directory_gently(&nongit_ok);\n \tgit_config(git_imap_config, &server);\n@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496274","messageId":"e19457d20c80f9ce332f2d890a5089972e28f0cf.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:49Z","receivedAt":"2024-06-04T12:38:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::default_backend` field is a non-constant\nstring, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\nRefactor the code to initialize and release options via two functions\n`rebase_options_init()` and `rebase_options_release()`. Like this, we\ncan easily adapt the former funnction to use `xstrdup()` on the default\nvalue without hiding it away in a macro.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..11f276012c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -131,25 +131,40 @@ struct rebase_options {\n \tint config_update_refs;\n };\n \n-#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n-\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n-\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n-\t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n-\t\t.flags = REBASE_NO_QUIET, \t\t\\\n-\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n-\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n-\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n-\t\t.fork_point = -1,\t\t\t\\\n-\t\t.reapply_cherry_picks = -1,             \\\n-\t\t.allow_empty_message = 1,               \\\n-\t\t.autosquash = -1,                       \\\n-\t\t.rebase_merges = -1,                    \\\n-\t\t.config_rebase_merges = -1,             \\\n-\t\t.update_refs = -1,                      \\\n-\t\t.config_update_refs = -1,               \\\n-\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n-\t}\n+static void rebase_options_init(struct rebase_options *opts)\n+{\n+\tmemset(opts, 0, sizeof(*opts));\n+\topts->type = REBASE_UNSPECIFIED;\n+\topts->empty = EMPTY_UNSPECIFIED;\n+\topts->default_backend = xstrdup(\"merge\");\n+\topts->keep_empty = 1;\n+\topts->flags = REBASE_NO_QUIET;\n+\tstrvec_init(&opts->git_am_opts);\n+\tstring_list_init_nodup(&opts->exec);\n+\tstrbuf_init(&opts->git_format_patch_opt, 0);\n+\topts->fork_point = -1;\n+\topts->reapply_cherry_picks = -1;\n+\topts->allow_empty_message = 1;\n+\topts->autosquash = -1;\n+\topts->rebase_merges = -1;\n+\topts->config_rebase_merges = -1;\n+\topts->update_refs = -1;\n+\topts->config_update_refs = -1;\n+\tstring_list_init_nodup(&opts->strategy_opts);\n+}\n+\n+static void rebase_options_release(struct rebase_options *opts)\n+{\n+\tfree(opts->default_backend);\n+\tfree(opts->reflog_action);\n+\tfree(opts->head_name);\n+\tstrvec_clear(&opts->git_am_opts);\n+\tfree(opts->gpg_sign_opt);\n+\tstring_list_clear(&opts->exec, 0);\n+\tfree(opts->strategy);\n+\tstring_list_clear(&opts->strategy_opts, 0);\n+\tstrbuf_release(&opts->git_format_patch_opt);\n+}\n \n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n {\n@@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)\n \n int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n-\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n+\tstruct rebase_options options;\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n@@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tint i;\n \n+\trebase_options_init(&options);\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n@@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n-\tfree(options.reflog_action);\n-\tfree(options.head_name);\n-\tstrvec_clear(&options.git_am_opts);\n-\tfree(options.gpg_sign_opt);\n-\tstring_list_clear(&options.exec, 0);\n-\tfree(options.strategy);\n-\tstring_list_clear(&options.strategy_opts, 0);\n-\tstrbuf_release(&options.git_format_patch_opt);\n+\trebase_options_release(&options);\n \tfree(squash_onto_name);\n \tfree(keep_base_onto_name);\n \treturn !!ret;\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496275","messageId":"f548241960d1f41e010516d68df9107528567514.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 25/27] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:54Z","receivedAt":"2024-06-04T12:38:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::strategy` field is a `char *`, but we do end\nup assigning string constants to it in two cases:\n\n  - When being passed a `--strategy=` option via the command line.\n\n  - When being passed a strategy option via `--strategy-option=`, but\n    not a strategy.\n\nThis will cause warnings once we enable `-Wwrite-strings`.\n\nIdeally, we'd just convert the field to be a `const char *`. But we also\nassign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\nhave to strdup(3P) into it.\n\nInstead, refactor the code to make sure that we only ever assign\nallocated strings to this field.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 11f276012c..26068cf542 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n \tstruct rebase_options options;\n \tconst char *branch_name;\n+\tconst char *strategy_opt = NULL;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n@@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n+\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n \t\t\t\tN_(\"option\"),\n@@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\tif (strategy_opt)\n+\t\toptions.strategy = xstrdup(strategy_opt);\n+\telse if (options.strategy_opts.nr && !options.strategy)\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496276","messageId":"78ac075644c222d27ee3c5faec41c955037dbf12.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 26/27] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:38:59Z","receivedAt":"2024-06-04T12:39:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496277","messageId":"0cd4ce07d8329829c5b914e2d78761ea32b25e6c.1717504517.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717504517.git.ps@pks.im","subject":"[PATCH v4 27/27] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T12:39:04Z","receivedAt":"2024-06-04T12:39:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496280","messageId":"be8a315a-3fa7-40d3-80e1-6a5c01afee70@gmail.com","threadId":"61561","inReplyTo":"e19457d20c80f9ce332f2d890a5089972e28f0cf.1717504517.git.ps@pks.im","subject":"Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-04T14:06:38Z","receivedAt":"2024-06-04T14:06:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 04/06/2024 13:38, Patrick Steinhardt wrote:\n> The `struct rebase_options::default_backend` field is a non-constant\n> string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\n> Refactor the code to initialize and release options via two functions\n> `rebase_options_init()` and `rebase_options_release()`. Like this, we\n> can easily adapt the former funnction to use `xstrdup()` on the default\n> value without hiding it away in a macro.\n\nPersonally I'd be happy with\n\n-\t\t.default_backend = \"merge\",\t\t\\\n+\t\t.default_backend = xstrdup(\"merge\"),\t\\\n\nrather than adding an init function. I do agree that adding \nrebase_options_release() is a good idea and the rest of the changes look \ngood to me\n\nThanks\n\nPhillip\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>   builtin/rebase.c | 67 ++++++++++++++++++++++++++++--------------------\n>   1 file changed, 39 insertions(+), 28 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 14d4f0a5e6..11f276012c 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -131,25 +131,40 @@ struct rebase_options {\n>   \tint config_update_refs;\n>   };\n>   \n> -#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n> -\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n> -\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n> -\t\t.keep_empty = 1,\t\t\t\\\n> -\t\t.default_backend = \"merge\",\t  \t\\\n> -\t\t.flags = REBASE_NO_QUIET, \t\t\\\n> -\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n> -\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n> -\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n> -\t\t.fork_point = -1,\t\t\t\\\n> -\t\t.reapply_cherry_picks = -1,             \\\n> -\t\t.allow_empty_message = 1,               \\\n> -\t\t.autosquash = -1,                       \\\n> -\t\t.rebase_merges = -1,                    \\\n> -\t\t.config_rebase_merges = -1,             \\\n> -\t\t.update_refs = -1,                      \\\n> -\t\t.config_update_refs = -1,               \\\n> -\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n> -\t}\n> +static void rebase_options_init(struct rebase_options *opts)\n> +{\n> +\tmemset(opts, 0, sizeof(*opts));\n> +\topts->type = REBASE_UNSPECIFIED;\n> +\topts->empty = EMPTY_UNSPECIFIED;\n> +\topts->default_backend = xstrdup(\"merge\");\n> +\topts->keep_empty = 1;\n> +\topts->flags = REBASE_NO_QUIET;\n> +\tstrvec_init(&opts->git_am_opts);\n> +\tstring_list_init_nodup(&opts->exec);\n> +\tstrbuf_init(&opts->git_format_patch_opt, 0);\n> +\topts->fork_point = -1;\n> +\topts->reapply_cherry_picks = -1;\n> +\topts->allow_empty_message = 1;\n> +\topts->autosquash = -1;\n> +\topts->rebase_merges = -1;\n> +\topts->config_rebase_merges = -1;\n> +\topts->update_refs = -1;\n> +\topts->config_update_refs = -1;\n> +\tstring_list_init_nodup(&opts->strategy_opts);\n> +}\n> +\n> +static void rebase_options_release(struct rebase_options *opts)\n> +{\n> +\tfree(opts->default_backend);\n> +\tfree(opts->reflog_action);\n> +\tfree(opts->head_name);\n> +\tstrvec_clear(&opts->git_am_opts);\n> +\tfree(opts->gpg_sign_opt);\n> +\tstring_list_clear(&opts->exec, 0);\n> +\tfree(opts->strategy);\n> +\tstring_list_clear(&opts->strategy_opts, 0);\n> +\tstrbuf_release(&opts->git_format_patch_opt);\n> +}\n>   \n>   static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>   {\n> @@ -796,6 +811,7 @@ static int rebase_config(const char *var, const char *value,\n>   \t}\n>   \n>   \tif (!strcmp(var, \"rebase.backend\")) {\n> +\t\tFREE_AND_NULL(opts->default_backend);\n>   \t\treturn git_config_string(&opts->default_backend, var, value);\n>   \t}\n>   \n> @@ -1045,7 +1061,7 @@ static int check_exec_cmd(const char *cmd)\n>   \n>   int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   {\n> -\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n> +\tstruct rebase_options options;\n>   \tconst char *branch_name;\n>   \tint ret, flags, total_argc, in_progress = 0;\n>   \tint keep_base = 0;\n> @@ -1178,6 +1194,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t};\n>   \tint i;\n>   \n> +\trebase_options_init(&options);\n> +\n>   \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>   \t\tusage_with_options(builtin_rebase_usage,\n>   \t\t\t\t   builtin_rebase_options);\n> @@ -1833,14 +1851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   cleanup:\n>   \tstrbuf_release(&buf);\n>   \tstrbuf_release(&revisions);\n> -\tfree(options.reflog_action);\n> -\tfree(options.head_name);\n> -\tstrvec_clear(&options.git_am_opts);\n> -\tfree(options.gpg_sign_opt);\n> -\tstring_list_clear(&options.exec, 0);\n> -\tfree(options.strategy);\n> -\tstring_list_clear(&options.strategy_opts, 0);\n> -\tstrbuf_release(&options.git_format_patch_opt);\n> +\trebase_options_release(&options);\n>   \tfree(squash_onto_name);\n>   \tfree(keep_base_onto_name);\n>   \treturn !!ret;\n"},{"id":"496281","messageId":"30718fa9-6197-424c-a58c-8bf3c909cb25@gmail.com","threadId":"61561","inReplyTo":"f548241960d1f41e010516d68df9107528567514.1717504517.git.ps@pks.im","subject":"Re: [PATCH v4 25/27] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-04T14:10:01Z","receivedAt":"2024-06-04T14:10:04Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 04/06/2024 13:38, Patrick Steinhardt wrote:\n> The `struct rebase_options::strategy` field is a `char *`, but we do end\n> up assigning string constants to it in two cases:\n> \n>    - When being passed a `--strategy=` option via the command line.\n> \n>    - When being passed a strategy option via `--strategy-option=`, but\n>      not a strategy.\n> \n> This will cause warnings once we enable `-Wwrite-strings`.\n> \n> Ideally, we'd just convert the field to be a `const char *`. But we also\n> assign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\n> have to strdup(3P) into it.\n> \n> Instead, refactor the code to make sure that we only ever assign\n> allocated strings to this field.\n\nThis looks sensible\n\nThanks\n\nPhillip\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>   builtin/rebase.c | 14 +++++++-------\n>   1 file changed, 7 insertions(+), 7 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 11f276012c..26068cf542 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -1063,6 +1063,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   {\n>   \tstruct rebase_options options;\n>   \tconst char *branch_name;\n> +\tconst char *strategy_opt = NULL;\n>   \tint ret, flags, total_argc, in_progress = 0;\n>   \tint keep_base = 0;\n>   \tint ok_to_skip_pre_rebase = 0;\n> @@ -1177,7 +1178,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n>   \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n>   \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n> -\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n> +\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n>   \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n>   \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n>   \t\t\t\tN_(\"option\"),\n> @@ -1488,13 +1489,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \t\t}\n>   \t}\n>   \n> -\tif (options.strategy_opts.nr && !options.strategy)\n> -\t\toptions.strategy = \"ort\";\n> -\n> -\tif (options.strategy) {\n> -\t\toptions.strategy = xstrdup(options.strategy);\n> +\tif (strategy_opt)\n> +\t\toptions.strategy = xstrdup(strategy_opt);\n> +\telse if (options.strategy_opts.nr && !options.strategy)\n> +\t\toptions.strategy = xstrdup(\"ort\");\n> +\tif (options.strategy)\n>   \t\timply_merge(&options, \"--strategy\");\n> -\t}\n>   \n>   \tif (options.root && !options.onto_name)\n>   \t\timply_merge(&options, \"--root without --onto\");\n"},{"id":"496333","messageId":"Zl_6anUl_C3W2j2Z@tanuki","threadId":"61561","inReplyTo":"be8a315a-3fa7-40d3-80e1-6a5c01afee70@gmail.com","subject":"Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-05T05:40:58Z","receivedAt":"2024-06-05T05:41:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:\n> Hi Patrick\n> \n> On 04/06/2024 13:38, Patrick Steinhardt wrote:\n> > The `struct rebase_options::default_backend` field is a non-constant\n> > string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\n> > Refactor the code to initialize and release options via two functions\n> > `rebase_options_init()` and `rebase_options_release()`. Like this, we\n> > can easily adapt the former funnction to use `xstrdup()` on the default\n> > value without hiding it away in a macro.\n> \n> Personally I'd be happy with\n> \n> -\t\t.default_backend = \"merge\",\t\t\\\n> +\t\t.default_backend = xstrdup(\"merge\"),\t\\\n> \n> rather than adding an init function. I do agree that adding\n> rebase_options_release() is a good idea and the rest of the changes look\n> good to me\n\nDo we have any other cases where we allocate inside of a `_INIT` style\nmacro? If so, I'd go with that precedence and just allocate inside of\nthe macro. But if we don't, then I think I'm leaning more towards the\nway I did it in this patch.\n\nHappy to be convinced otherwise, I don't really feel all that strongly\nabout this. I'm merely aiming for the interface wth least surprises.\n\nPatrick\n"},{"id":"496365","messageId":"20240605104646.GA3972667@coredump.intra.peff.net","threadId":"61561","inReplyTo":"xmqq4jaehui6.fsf@gitster.g","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-06-05T10:46:46Z","receivedAt":"2024-06-05T10:46:47Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:\n\n> I wonder if we can do something to separate these two concerns\n> apart, using a trick similar to what we often use with an extra\n> variable \"to_free\".  Doing so would bloat the refspec_item, but\n> unlike the references themselves, there won't be thousands of them,\n> so it may not be an issue, perhaps?\n\nI had a similar thought while looking at this spot a while ago, so I dug\nthis attempt out of my stash. It's quite ugly, as you need to keep the\nstorage pointer and the const pointer in sync. Especially because\nthere's a lot of clever pointer indirection via match_name_with_pattern().\n\nSo I don't think it's the best way to refactor this code, but I present\nit here for your amusement/disgust. This is on top of what you have\nqueued in ps/no-writable-strings.\n\n---\n branch.c         |  6 +++---\n builtin/fetch.c  |  8 ++++----\n builtin/remote.c |  6 +++---\n checkout.c       | 13 +++++++------\n refspec.c        |  6 ++++--\n refspec.h        |  6 ++++--\n remote.c         | 30 +++++++++++++++++-------------\n transport.c      |  2 +-\n 8 files changed, 43 insertions(+), 34 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex df5d24fec6..e1585b10c2 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -38,7 +38,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tswitch (++tracking->matches) {\n \t\tcase 1:\n-\t\t\tstring_list_append_nodup(tracking->srcs, tracking->spec.src);\n+\t\t\tstring_list_append_nodup(tracking->srcs, tracking->spec.src_storage);\n \t\t\ttracking->remote = remote->name;\n \t\t\tbreak;\n \t\tcase 2:\n@@ -47,7 +47,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \t\t\t/* fall through */\n \t\tdefault:\n \t\t\tstring_list_append(&ftb->ambiguous_remotes, remote->name);\n-\t\t\tfree(tracking->spec.src);\n+\t\t\tfree(tracking->spec.src_storage);\n \t\t\tstring_list_clear(tracking->srcs, 0);\n \t\tbreak;\n \t\t}\n@@ -489,7 +489,7 @@ static int check_tracking_branch(struct remote *remote, void *cb_data)\n \tmemset(&query, 0, sizeof(struct refspec_item));\n \tquery.dst = tracking_branch;\n \tres = !remote_find_tracking(remote, &query);\n-\tfree(query.src);\n+\tfree(query.src_storage);\n \treturn res;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 06b60867f5..00a67c99ef 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -443,7 +443,7 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \n \tfor (i = 0; i < rs->nr; i++) {\n \t\tstruct strbuf new_dst = STRBUF_INIT;\n-\t\tchar *old_dst;\n+\t\tconst char *old_dst;\n \t\tconst char *sub = NULL;\n \n \t\tif (rs->items[i].negative)\n@@ -454,8 +454,8 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \t\t\t\t ref_namespace[NAMESPACE_TAGS].ref))) {\n \t\t\tint j;\n \n-\t\t\tfree(rs->items[i].src);\n-\t\t\tfree(rs->items[i].dst);\n+\t\t\tfree(rs->items[i].src_storage);\n+\t\t\tfree(rs->items[i].dst_storage);\n \n \t\t\tfor (j = i + 1; j < rs->nr; j++) {\n \t\t\t\trs->items[j - 1] = rs->items[j];\n@@ -481,7 +481,7 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \t\trs->items[i].dst = strbuf_detach(&new_dst, NULL);\n \t\trs->items[i].force = 1;\n \n-\t\tfree(old_dst);\n+\t\tfree(rs->items[i].dst_storage);\n \t}\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex b44f580b8c..87b97f5ef1 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -496,8 +496,8 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \tstruct refspec_item refspec = {\n \t\t.force = 0,\n \t\t.pattern = 1,\n-\t\t.src = (char *) \"refs/heads/*\",\n-\t\t.dst = (char *) \"refs/heads/*\",\n+\t\t.src = \"refs/heads/*\",\n+\t\t.dst = \"refs/heads/*\",\n \t};\n \n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n@@ -981,7 +981,7 @@ static int append_ref_to_tracked_list(const char *refname,\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = refname;\n \tif (!remote_find_tracking(states->remote, &refspec))\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \ndiff --git a/checkout.c b/checkout.c\nindex cfaea4bd10..327ab5f6e3 100644\n--- a/checkout.c\n+++ b/checkout.c\n@@ -8,7 +8,7 @@\n #include \"strbuf.h\"\n \n struct tracking_name_data {\n-\t/* const */ char *src_ref;\n+\tconst char *src_ref;\n \tchar *dst_ref;\n \tstruct object_id *dst_oid;\n \tint num_matches;\n@@ -27,7 +27,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data)\n \tquery.src = cb->src_ref;\n \tif (remote_find_tracking(remote, &query) ||\n \t    repo_get_oid(the_repository, query.dst, cb->dst_oid)) {\n-\t\tfree(query.dst);\n+\t\tfree(query.dst_storage);\n \t\treturn 0;\n \t}\n \tcb->num_matches++;\n@@ -38,10 +38,10 @@ static int check_tracking_name(struct remote *remote, void *cb_data)\n \t\tcb->default_dst_oid = dst;\n \t}\n \tif (cb->dst_ref) {\n-\t\tfree(query.dst);\n+\t\tfree(query.dst_storage);\n \t\treturn 0;\n \t}\n-\tcb->dst_ref = query.dst;\n+\tcb->dst_ref = query.dst_storage;\n \treturn 0;\n }\n \n@@ -50,14 +50,15 @@ char *unique_tracking_name(const char *name, struct object_id *oid,\n {\n \tstruct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;\n \tconst char *default_remote = NULL;\n+\tchar *src_ref;\n \tif (!git_config_get_string_tmp(\"checkout.defaultremote\", &default_remote))\n \t\tcb_data.default_remote = default_remote;\n-\tcb_data.src_ref = xstrfmt(\"refs/heads/%s\", name);\n+\tcb_data.src_ref = src_ref = xstrfmt(\"refs/heads/%s\", name);\n \tcb_data.dst_oid = oid;\n \tfor_each_remote(check_tracking_name, &cb_data);\n \tif (dwim_remotes_matched)\n \t\t*dwim_remotes_matched = cb_data.num_matches;\n-\tfree(cb_data.src_ref);\n+\tfree(src_ref);\n \tif (cb_data.num_matches == 1) {\n \t\tfree(cb_data.default_dst_ref);\n \t\tfree(cb_data.default_dst_oid);\ndiff --git a/refspec.c b/refspec.c\nindex 1df5de6c2f..a7473c9628 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -163,8 +163,10 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,\n \n void refspec_item_clear(struct refspec_item *item)\n {\n-\tFREE_AND_NULL(item->src);\n-\tFREE_AND_NULL(item->dst);\n+\tFREE_AND_NULL(item->src_storage);\n+\titem->src = NULL;\n+\tFREE_AND_NULL(item->dst_storage);\n+\titem->dst = NULL;\n \titem->force = 0;\n \titem->pattern = 0;\n \titem->matching = 0;\ndiff --git a/refspec.h b/refspec.h\nindex 754be45cee..fbc4352397 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -24,8 +24,10 @@ struct refspec_item {\n \tunsigned exact_sha1 : 1;\n \tunsigned negative : 1;\n \n-\tchar *src;\n-\tchar *dst;\n+\tconst char *src;\n+\tconst char *dst;\n+\tchar *src_storage;\n+\tchar *dst_storage;\n };\n \n #define REFSPEC_FETCH 1\ndiff --git a/remote.c b/remote.c\nindex d319f28757..5cdc3a17cc 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -827,7 +827,8 @@ int remote_has_url(struct remote *remote, const char *url)\n }\n \n static int match_name_with_pattern(const char *key, const char *name,\n-\t\t\t\t   const char *value, char **result)\n+\t\t\t\t   const char *value, char **result,\n+\t\t\t\t   const char **result_const)\n {\n \tconst char *kstar = strchr(key, '*');\n \tsize_t klen;\n@@ -850,6 +851,8 @@ static int match_name_with_pattern(const char *key, const char *name,\n \t\tstrbuf_add(&sb, name + klen, namelen - klen - ksuffixlen);\n \t\tstrbuf_addstr(&sb, vstar + 1);\n \t\t*result = strbuf_detach(&sb, NULL);\n+\t\tif (result_const)\n+\t\t\t*result_const = *result;\n \t}\n \treturn ret;\n }\n@@ -858,7 +861,7 @@ static int refspec_match(const struct refspec_item *refspec,\n \t\t\t const char *name)\n {\n \tif (refspec->pattern)\n-\t\treturn match_name_with_pattern(refspec->src, name, NULL, NULL);\n+\t\treturn match_name_with_pattern(refspec->src, name, NULL, NULL, NULL);\n \n \treturn !strcmp(refspec->src, name);\n }\n@@ -927,7 +930,7 @@ static int query_matches_negative_refspec(struct refspec *rs, struct refspec_ite\n \t\t\tconst char *key = refspec->dst ? refspec->dst : refspec->src;\n \t\t\tconst char *value = refspec->src;\n \n-\t\t\tif (match_name_with_pattern(key, needle, value, &expn_name))\n+\t\t\tif (match_name_with_pattern(key, needle, value, &expn_name, NULL))\n \t\t\t\tstring_list_append_nodup(&reversed, expn_name);\n \t\t} else if (refspec->matching) {\n \t\t\t/* For the special matching refspec, any query should match */\n@@ -967,12 +970,12 @@ static void query_refspecs_multiple(struct refspec *rs,\n \t\tconst char *key = find_src ? refspec->dst : refspec->src;\n \t\tconst char *value = find_src ? refspec->src : refspec->dst;\n \t\tconst char *needle = find_src ? query->dst : query->src;\n-\t\tchar **result = find_src ? &query->src : &query->dst;\n+\t\tchar **result = find_src ? &query->src_storage : &query->dst_storage;\n \n \t\tif (!refspec->dst || refspec->negative)\n \t\t\tcontinue;\n \t\tif (refspec->pattern) {\n-\t\t\tif (match_name_with_pattern(key, needle, value, result))\n+\t\t\tif (match_name_with_pattern(key, needle, value, result, NULL))\n \t\t\t\tstring_list_append_nodup(results, *result);\n \t\t} else if (!strcmp(needle, key)) {\n \t\t\tstring_list_append(results, value);\n@@ -985,7 +988,8 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)\n \tint i;\n \tint find_src = !query->src;\n \tconst char *needle = find_src ? query->dst : query->src;\n-\tchar **result = find_src ? &query->src : &query->dst;\n+\tchar **result = find_src ? &query->src_storage : &query->dst_storage;\n+\tconst char **result_const = find_src ? &query->src : &query->dst;\n \n \tif (find_src && !query->dst)\n \t\tBUG(\"query_refspecs: need either src or dst\");\n@@ -1001,12 +1005,12 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)\n \t\tif (!refspec->dst || refspec->negative)\n \t\t\tcontinue;\n \t\tif (refspec->pattern) {\n-\t\t\tif (match_name_with_pattern(key, needle, value, result)) {\n+\t\t\tif (match_name_with_pattern(key, needle, value, result, result_const)) {\n \t\t\t\tquery->force = refspec->force;\n \t\t\t\treturn 0;\n \t\t\t}\n \t\t} else if (!strcmp(needle, key)) {\n-\t\t\t*result = xstrdup(value);\n+\t\t\t*result_const = *result = xstrdup(value);\n \t\t\tquery->force = refspec->force;\n \t\t\treturn 0;\n \t\t}\n@@ -1019,12 +1023,12 @@ char *apply_refspecs(struct refspec *rs, const char *name)\n \tstruct refspec_item query;\n \n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.src = (char *)name;\n+\tquery.src = name;\n \n \tif (query_refspecs(rs, &query))\n \t\treturn NULL;\n \n-\treturn query.dst;\n+\treturn query.dst_storage;\n }\n \n int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)\n@@ -1399,9 +1403,9 @@ static char *get_ref_match(const struct refspec *rs, const struct ref *ref,\n \t\t\tconst char *dst_side = item->dst ? item->dst : item->src;\n \t\t\tint match;\n \t\t\tif (direction == FROM_SRC)\n-\t\t\t\tmatch = match_name_with_pattern(item->src, ref->name, dst_side, &name);\n+\t\t\t\tmatch = match_name_with_pattern(item->src, ref->name, dst_side, &name, NULL);\n \t\t\telse\n-\t\t\t\tmatch = match_name_with_pattern(dst_side, ref->name, item->src, &name);\n+\t\t\t\tmatch = match_name_with_pattern(dst_side, ref->name, item->src, &name, NULL);\n \t\t\tif (match) {\n \t\t\t\tmatching_refs = i;\n \t\t\t\tbreak;\n@@ -2020,7 +2024,7 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,\n \t\tif (strchr(ref->name, '^'))\n \t\t\tcontinue; /* a dereference item */\n \t\tif (match_name_with_pattern(refspec->src, ref->name,\n-\t\t\t\t\t    refspec->dst, &expn_name) &&\n+\t\t\t\t\t    refspec->dst, &expn_name, NULL) &&\n \t\t    !ignore_symref_update(expn_name, &scratch)) {\n \t\t\tstruct ref *cpy = copy_ref(ref);\n \ndiff --git a/transport.c b/transport.c\nindex 83ddea8fbc..0cedda425c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -550,7 +550,7 @@ static void update_one_tracking_ref(struct remote *remote, char *refname,\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n \t\t\t\t\t\"update by push\", rs.dst, new_oid,\n \t\t\t\t\tNULL, 0, 0);\n-\t\tfree(rs.dst);\n+\t\tfree(rs.dst_storage);\n \t}\n }\n \n"},{"id":"496366","messageId":"d83f640e-2528-4326-bc46-d2b99f83a153@gmail.com","threadId":"61561","inReplyTo":"Zl_6anUl_C3W2j2Z@tanuki","subject":"Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-05T13:06:55Z","receivedAt":"2024-06-05T13:06:58Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 05/06/2024 06:40, Patrick Steinhardt wrote:\n> On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:\n\n> Do we have any other cases where we allocate inside of a `_INIT` style\n> macro? If so, I'd go with that precedence and just allocate inside of\n> the macro. But if we don't, then I think I'm leaning more towards the\n> way I did it in this patch.\n\nI recently added an allocation to REPLAY_OPTS_INIT, I'm not sure if \nthere are any others. In general code that does\n\n\tstruct foo = FOO_INIT;\n\nand does not call\n\n\tfoo_release();\n\nis asking for trouble so I don't feel that allocations in _INIT macros \nare generally a problem. The only reason not to have allocations in an \n_INIT macro is if it might be used to initialize a file scope or global \nvariable but that's not the case here.\n\n> Happy to be convinced otherwise, I don't really feel all that strongly\n> about this. I'm merely aiming for the interface wth least surprises.\n\nI'm not that fussed either, but I do prefer our _INIT macros over \n_init() functions as I think they're nicer to use and easier to write \n(no need to worry about memset() to zero out the struct).\n\nBest Wishes\n\nPhillip\n\n"},{"id":"496374","messageId":"xmqq8qzjwerb.fsf@gitster.g","threadId":"61561","inReplyTo":"Zl_6anUl_C3W2j2Z@tanuki","subject":"Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-05T16:11:52Z","receivedAt":"2024-06-05T16:11:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> Personally I'd be happy with\n>> \n>> -\t\t.default_backend = \"merge\",\t\t\\\n>> +\t\t.default_backend = xstrdup(\"merge\"),\t\\\n>> \n>> rather than adding an init function. I do agree that adding\n>> rebase_options_release() is a good idea and the rest of the changes look\n>> good to me\n>\n> Do we have any other cases where we allocate inside of a `_INIT` style\n> macro? If so, I'd go with that precedence and just allocate inside of\n> the macro. But if we don't, then I think I'm leaning more towards the\n> way I did it in this patch.\n>\n> Happy to be convinced otherwise, I don't really feel all that strongly\n> about this. I'm merely aiming for the interface wth least surprises.\n\nFWIW, I am OK either way.\n\nHaving _init() and _release() may look symmetrical and there _might_\nbe cases where an initialization with a simple _INIT macro may not\nsuffice and we must have _init() function.\n\nBut otherwise, especially with .designated_initializers, the _INIT\nmacro makes it slightly easier to read (but not all much for this\nparticular case, whose initial values have too many non-zero values\nand pretty much everything must be spelled out).\n\nThanks.\n"},{"id":"496384","messageId":"xmqqplsvtirr.fsf@gitster.g","threadId":"61561","inReplyTo":"20240605104646.GA3972667@coredump.intra.peff.net","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-05T17:13:28Z","receivedAt":"2024-06-05T17:13:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:\n>\n>> I wonder if we can do something to separate these two concerns\n>> apart, using a trick similar to what we often use with an extra\n>> variable \"to_free\".  Doing so would bloat the refspec_item, but\n>> unlike the references themselves, there won't be thousands of them,\n>> so it may not be an issue, perhaps?\n>\n> I had a similar thought while looking at this spot a while ago, so I dug\n> this attempt out of my stash. It's quite ugly, as you need to keep the\n> storage pointer and the const pointer in sync. Especially because\n> there's a lot of clever pointer indirection via match_name_with_pattern().\n\nAh, true.  The patch itself does not look _too_ bad, but that may\nsimply be because the original is bad enough ;-)\n"},{"id":"496429","messageId":"xmqqr0dapq1s.fsf@gitster.g","threadId":"61561","inReplyTo":"ecca8e973df77cfc8233ab63bf7d1f6fa83031a3.1717504517.git.ps@pks.im","subject":"Re: [PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-06T06:02:07Z","receivedAt":"2024-06-06T06:02:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The buffers of cached objects are never modified, but are still stored\n> as a non-constant pointer. This will cause a compiler warning once we\n> enable the `-Wwrite-strings` compiler warning as we assign an empty\n> constant string when initializing the static `empty_tree` cached object.\n>\n> Convert the field to be constant. This requires us to shuffle around\n> the code a bit because we memcpy(3P) into the allocated buffer in\n> `pretend_object_file()`. This is easily fixed though by allocating the\n> buffer into a temporary variable first.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 9 ++++++---\n>  1 file changed, 6 insertions(+), 3 deletions(-)\n\nI haven't dug into the exact details, but I noticed that t6130,\nt7010, and t8002 started breaking linux-leaks/linux-reftable-leaks\nCI jobs for 'seen'.  'seen' excluding ps/no-writable-strings seems\nto pass the tests, and bisection indicates that 'seen' excluding\nps/no-writable-strings after this step [12/27] still passes, but\nonce this step is included, the leak tests break.\n\n> diff --git a/object-file.c b/object-file.c\n> index 610b1f465c..3afe9fce06 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -277,7 +277,7 @@ int hash_algo_by_length(int len)\n>  static struct cached_object {\n>  \tstruct object_id oid;\n>  \tenum object_type type;\n> -\tvoid *buf;\n> +\tconst void *buf;\n>  \tunsigned long size;\n>  } *cached_objects;\n>  static int cached_object_nr, cached_object_alloc;\n> @@ -1778,6 +1778,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n>  \t\t\tstruct object_id *oid)\n>  {\n>  \tstruct cached_object *co;\n> +\tchar *co_buf;\n> +\n> +\tco_buf = xmalloc(len);\n> +\tmemcpy(co_buf, buf, len);\n>  \n>  \thash_object_file(the_hash_algo, buf, len, type, oid);\n>  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n\nThere is an early return around here.  Perhaps we are leaking co_buf\nwhen we hit it?\n\n> @@ -1787,8 +1791,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n>  \tco = &cached_objects[cached_object_nr++];\n>  \tco->size = len;\n>  \tco->type = type;\n> -\tco->buf = xmalloc(len);\n> -\tmemcpy(co->buf, buf, len);\n> +\tco->buf = co_buf;\n>  \toidcpy(&co->oid, oid);\n>  \treturn 0;\n>  }\n"},{"id":"496431","messageId":"xmqqmsnyppo3.fsf@gitster.g","threadId":"61561","inReplyTo":"xmqqr0dapq1s.fsf@gitster.g","subject":"Re: [PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-06T06:10:20Z","receivedAt":"2024-06-06T06:10:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +\tco_buf = xmalloc(len);\n>> +\tmemcpy(co_buf, buf, len);\n>>  \n>>  \thash_object_file(the_hash_algo, buf, len, type, oid);\n>>  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n>\n> There is an early return around here.  Perhaps we are leaking co_buf\n> when we hit it?\n\nIndeed, that seems to be the case.  With the attached at the tip of\nthe branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002\ntests with SANTIZE=leak.\n\nFrom f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001\nFrom: Junio C Hamano <gitster@pobox.com>\nDate: Wed, 5 Jun 2024 23:03:34 -0700\nSubject: [PATCH] fixup! object-file: mark cached object buffers as const\n\n---\n object-file.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex b5b5a59dc6..2d5bd3a211 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n-\t    find_cached_object(oid))\n+\t    find_cached_object(oid)) {\n+\t\tfree(co_buf);\n \t\treturn 0;\n+\t}\n \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n-- \n2.45.2-409-g7b0defb391\n\n"},{"id":"496477","messageId":"ZmGGWdClbLxNfRwF@tanuki","threadId":"61561","inReplyTo":"d83f640e-2528-4326-bc46-d2b99f83a153@gmail.com","subject":"Re: [PATCH v4 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T09:50:17Z","receivedAt":"2024-06-06T09:50:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jun 05, 2024 at 02:06:55PM +0100, Phillip Wood wrote:\n> On 05/06/2024 06:40, Patrick Steinhardt wrote:\n> > On Tue, Jun 04, 2024 at 03:06:38PM +0100, Phillip Wood wrote:\n> \n> > Do we have any other cases where we allocate inside of a `_INIT` style\n> > macro? If so, I'd go with that precedence and just allocate inside of\n> > the macro. But if we don't, then I think I'm leaning more towards the\n> > way I did it in this patch.\n> \n> I recently added an allocation to REPLAY_OPTS_INIT, I'm not sure if there\n> are any others. In general code that does\n> \n> \tstruct foo = FOO_INIT;\n> \n> and does not call\n> \n> \tfoo_release();\n> \n> is asking for trouble so I don't feel that allocations in _INIT macros are\n> generally a problem. The only reason not to have allocations in an _INIT\n> macro is if it might be used to initialize a file scope or global variable\n> but that's not the case here.\n> \n> > Happy to be convinced otherwise, I don't really feel all that strongly\n> > about this. I'm merely aiming for the interface wth least surprises.\n> \n> I'm not that fussed either, but I do prefer our _INIT macros over _init()\n> functions as I think they're nicer to use and easier to write (no need to\n> worry about memset() to zero out the struct).\n\nOkay, given that there is precedent, and given that Junio also seems to\nslightly lean into the direction of the `_INIT` macro, I'll adapt this\nas proposed by you.\n\nPatrick\n"},{"id":"496480","messageId":"ZmGJW4Ruz_ZafNZ8@tanuki","threadId":"61561","inReplyTo":"xmqqmsnyppo3.fsf@gitster.g","subject":"Re: [PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:03:07Z","receivedAt":"2024-06-06T10:03:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jun 05, 2024 at 11:10:20PM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> >> +\tco_buf = xmalloc(len);\n> >> +\tmemcpy(co_buf, buf, len);\n> >>  \n> >>  \thash_object_file(the_hash_algo, buf, len, type, oid);\n> >>  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n> >\n> > There is an early return around here.  Perhaps we are leaking co_buf\n> > when we hit it?\n> \n> Indeed, that seems to be the case.  With the attached at the tip of\n> the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002\n> tests with SANTIZE=leak.\n\nIndeed, thanks. Rolled into my local version now.\n\nPatrick\n"},{"id":"496483","messageId":"cover.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH v5 00/27] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:27:46Z","receivedAt":"2024-06-06T10:27:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is another version of my patch series that prepares our code base\nto compile with `-Wwrite-strings`. The effect of that warning is to turn\nstring constants from type `char []` to `const char []` so that we can\ndetect more readily when something may accidentally try to write to or\nfree a constant.\n\nChanges compared to v5:\n\n  - Plug a memory leak in `pretend_object_file()`.\n\n  - Drop `rebase_options_init()` in favor of `REBASE_OPTIONS_INIT`.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (27):\n  global: improve const correctness when assigning string constants\n  global: convert intentionally-leaking config strings to consts\n  refs/reftable: stop micro-optimizing refname allocations on copy\n  reftable: cast away constness when assigning constants to records\n  refspec: remove global tag refspec structure\n  builtin/remote: cast away constness in `get_head_names()`\n  diff: cast string constant in `fill_textconv()`\n  line-log: stop assigning string constant to file parent buffer\n  line-log: always allocate the output prefix\n  entry: refactor how we remove items for delayed checkouts\n  ident: add casts for fallback name and GECOS\n  object-file: mark cached object buffers as const\n  object-file: make `buf` parameter of `index_mem()` a constant\n  pretty: add casts for decoration option pointers\n  compat/win32: fix const-correctness with string constants\n  http: do not assign string constant to non-const field\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: do not assign default backend to non-constant field\n  builtin/rebase: always store allocated string in `options.strategy`\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  39 ++++++-----\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |  12 ++--\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  16 ++++-\n compat/mingw.c               |  28 ++++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   6 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |  14 ++--\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   4 +-\n imap-send.c                  | 130 ++++++++++++++++++++---------------\n line-log.c                   |  22 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  27 +++++---\n parse-options.h              |   2 +-\n pretty.c                     |   6 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |  28 ++++----\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics.c            |  15 ++--\n reftable/basics.h            |   4 +-\n reftable/basics_test.c       |   4 +-\n reftable/block_test.c        |   2 +-\n reftable/merged_test.c       |  44 ++++++------\n reftable/readwrite_test.c    |  32 ++++-----\n reftable/record.c            |   6 +-\n reftable/stack.c             |  10 +--\n reftable/stack_test.c        |  56 +++++++--------\n remote-curl.c                |  53 +++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 68 files changed, 448 insertions(+), 368 deletions(-)\n\nRange-diff against v4:\n 1:  e01fde88fe =  1:  e01fde88fe global: improve const correctness when assigning string constants\n 2:  92cb0b28c6 =  2:  92cb0b28c6 global: convert intentionally-leaking config strings to consts\n 3:  379145478c =  3:  379145478c refs/reftable: stop micro-optimizing refname allocations on copy\n 4:  d0a2a2f6c5 =  4:  d0a2a2f6c5 reftable: cast away constness when assigning constants to records\n 5:  ead27d3d97 =  5:  ead27d3d97 refspec: remove global tag refspec structure\n 6:  7cb5df9182 =  6:  7cb5df9182 builtin/remote: cast away constness in `get_head_names()`\n 7:  6e631a9ea4 =  7:  6e631a9ea4 diff: cast string constant in `fill_textconv()`\n 8:  ac164651a3 =  8:  ac164651a3 line-log: stop assigning string constant to file parent buffer\n 9:  b717af02f0 =  9:  b717af02f0 line-log: always allocate the output prefix\n10:  b46dd3210d = 10:  b46dd3210d entry: refactor how we remove items for delayed checkouts\n11:  030dbd0288 = 11:  030dbd0288 ident: add casts for fallback name and GECOS\n12:  ecca8e973d ! 12:  5cd014c22c object-file: mark cached object buffers as const\n    @@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object\n      \n      \thash_object_file(the_hash_algo, buf, len, type, oid);\n      \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n    -@@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n    +-\t    find_cached_object(oid))\n    ++\t    find_cached_object(oid)) {\n    ++\t\tfree(co_buf);\n    + \t\treturn 0;\n    ++\t}\n    + \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n      \tco = &cached_objects[cached_object_nr++];\n      \tco->size = len;\n      \tco->type = type;\n13:  62f0e47f94 = 13:  69d904ddce object-file: make `buf` parameter of `index_mem()` a constant\n14:  e057ead2b4 = 14:  ed8f07aa59 pretty: add casts for decoration option pointers\n15:  06b6120d26 = 15:  5953ae1dac compat/win32: fix const-correctness with string constants\n16:  a8ef39d73d = 16:  c80f6eff8c http: do not assign string constant to non-const field\n17:  9d596a07c5 = 17:  3afd012a88 parse-options: cast long name for OPTION_ALIAS\n18:  4019b532f9 = 18:  527755b648 send-pack: always allocate receive status\n19:  f2f1ada143 = 19:  4598592d2f remote-curl: avoid assigning string constant to non-const variable\n20:  27660b908c = 20:  38fcea2845 revision: always store allocated strings in output encoding\n21:  ef43c1b18f = 21:  f990bbeb85 mailmap: always store allocated strings in mailmap blob\n22:  0a69ce4b44 = 22:  fff2379832 imap-send: drop global `imap_server_conf` variable\n23:  9ccafd286b = 23:  9ab84e459a imap-send: fix leaking memory in `imap_server_conf`\n24:  e19457d20c ! 24:  81c69da2e8 builtin/rebase: do not assign default backend to non-constant field\n    @@ Commit message\n     \n         The `struct rebase_options::default_backend` field is a non-constant\n         string, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\n    -    Refactor the code to initialize and release options via two functions\n    -    `rebase_options_init()` and `rebase_options_release()`. Like this, we\n    -    can easily adapt the former funnction to use `xstrdup()` on the default\n    -    value without hiding it away in a macro.\n    +    Fix this by using `xstrdup()` to assign the variable and introduce a new\n    +    function `rebase_options_release()` that releases memory held by the\n    +    structure, including the newly-allocated variable.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## builtin/rebase.c ##\n     @@ builtin/rebase.c: struct rebase_options {\n    - \tint config_update_refs;\n    - };\n    - \n    --#define REBASE_OPTIONS_INIT {\t\t\t  \t\\\n    --\t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n    --\t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n    --\t\t.keep_empty = 1,\t\t\t\\\n    + \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n    + \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n    + \t\t.keep_empty = 1,\t\t\t\\\n     -\t\t.default_backend = \"merge\",\t  \t\\\n    --\t\t.flags = REBASE_NO_QUIET, \t\t\\\n    --\t\t.git_am_opts = STRVEC_INIT,\t\t\\\n    --\t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n    --\t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n    --\t\t.fork_point = -1,\t\t\t\\\n    --\t\t.reapply_cherry_picks = -1,             \\\n    --\t\t.allow_empty_message = 1,               \\\n    --\t\t.autosquash = -1,                       \\\n    --\t\t.rebase_merges = -1,                    \\\n    --\t\t.config_rebase_merges = -1,             \\\n    --\t\t.update_refs = -1,                      \\\n    --\t\t.config_update_refs = -1,               \\\n    --\t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n    --\t}\n    -+static void rebase_options_init(struct rebase_options *opts)\n    -+{\n    -+\tmemset(opts, 0, sizeof(*opts));\n    -+\topts->type = REBASE_UNSPECIFIED;\n    -+\topts->empty = EMPTY_UNSPECIFIED;\n    -+\topts->default_backend = xstrdup(\"merge\");\n    -+\topts->keep_empty = 1;\n    -+\topts->flags = REBASE_NO_QUIET;\n    -+\tstrvec_init(&opts->git_am_opts);\n    -+\tstring_list_init_nodup(&opts->exec);\n    -+\tstrbuf_init(&opts->git_format_patch_opt, 0);\n    -+\topts->fork_point = -1;\n    -+\topts->reapply_cherry_picks = -1;\n    -+\topts->allow_empty_message = 1;\n    -+\topts->autosquash = -1;\n    -+\topts->rebase_merges = -1;\n    -+\topts->config_rebase_merges = -1;\n    -+\topts->update_refs = -1;\n    -+\topts->config_update_refs = -1;\n    -+\tstring_list_init_nodup(&opts->strategy_opts);\n    -+}\n    -+\n    ++\t\t.default_backend = xstrdup(\"merge\"),  \t\\\n    + \t\t.flags = REBASE_NO_QUIET, \t\t\\\n    + \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n    + \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n    +@@ builtin/rebase.c: struct rebase_options {\n    + \t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n    + \t}\n    + \n     +static void rebase_options_release(struct rebase_options *opts)\n     +{\n     +\tfree(opts->default_backend);\n    @@ builtin/rebase.c: struct rebase_options {\n     +\tstring_list_clear(&opts->strategy_opts, 0);\n     +\tstrbuf_release(&opts->git_format_patch_opt);\n     +}\n    - \n    ++\n      static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n      {\n    + \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n     @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,\n      \t}\n      \n    @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value,\n      \t\treturn git_config_string(&opts->default_backend, var, value);\n      \t}\n      \n    -@@ builtin/rebase.c: static int check_exec_cmd(const char *cmd)\n    - \n    - int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - {\n    --\tstruct rebase_options options = REBASE_OPTIONS_INIT;\n    -+\tstruct rebase_options options;\n    - \tconst char *branch_name;\n    - \tint ret, flags, total_argc, in_progress = 0;\n    - \tint keep_base = 0;\n    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \t};\n    - \tint i;\n    - \n    -+\trebase_options_init(&options);\n    -+\n    - \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n    - \t\tusage_with_options(builtin_rebase_usage,\n    - \t\t\t\t   builtin_rebase_options);\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n      cleanup:\n      \tstrbuf_release(&buf);\n25:  f548241960 ! 25:  6819bf6116 builtin/rebase: always store allocated string in `options.strategy`\n    @@ Commit message\n      ## builtin/rebase.c ##\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n      {\n    - \tstruct rebase_options options;\n    + \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n      \tconst char *branch_name;\n     +\tconst char *strategy_opt = NULL;\n      \tint ret, flags, total_argc, in_progress = 0;\n26:  78ac075644 = 26:  a1d2149429 builtin/merge: always store allocated strings in `pull_twohead`\n27:  0cd4ce07d8 = 27:  c714b67199 config.mak.dev: enable `-Wwrite-strings` warning\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496484","messageId":"e01fde88fe35d6da358af5689320db50750c29dc.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 01/27] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:27:50Z","receivedAt":"2024-06-06T10:27:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/basics.c            | 15 +++++------\n reftable/basics.h            |  4 +--\n reftable/basics_test.c       |  4 +--\n reftable/record.c            |  6 ++---\n reftable/stack.c             | 10 ++++---\n reftable/stack_test.c        |  8 +++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 38 files changed, 106 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/basics.c b/reftable/basics.c\nindex fea711db7e..0058619ca6 100644\n--- a/reftable/basics.c\n+++ b/reftable/basics.c\n@@ -67,9 +67,9 @@ void free_names(char **a)\n \treftable_free(a);\n }\n \n-size_t names_length(char **names)\n+size_t names_length(const char **names)\n {\n-\tchar **p = names;\n+\tconst char **p = names;\n \twhile (*p)\n \t\tp++;\n \treturn p - names;\n@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)\n \t*namesp = names;\n }\n \n-int names_equal(char **a, char **b)\n+int names_equal(const char **a, const char **b)\n {\n-\tint i = 0;\n-\tfor (; a[i] && b[i]; i++) {\n-\t\tif (strcmp(a[i], b[i])) {\n+\tsize_t i = 0;\n+\tfor (; a[i] && b[i]; i++)\n+\t\tif (strcmp(a[i], b[i]))\n \t\t\treturn 0;\n-\t\t}\n-\t}\n-\n \treturn a[i] == b[i];\n }\n \ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 523ecd5307..c8fec68d4e 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -42,10 +42,10 @@ void free_names(char **a);\n void parse_names(char *buf, int size, char ***namesp);\n \n /* compares two NULL-terminated arrays of strings. */\n-int names_equal(char **a, char **b);\n+int names_equal(const char **a, const char **b);\n \n /* returns the array size of a NULL-terminated array of strings. */\n-size_t names_length(char **names);\n+size_t names_length(const char **names);\n \n /* Allocation routines; they invoke the functions set through\n  * reftable_set_alloc() */\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..13bc761817 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,8 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tconst char *names[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex a59ebe038d..09549c51c9 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n \treturn cur;\n }\n \n-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+static int reftable_stack_reload_once(struct reftable_stack *st,\n+\t\t\t\t      const char **names,\n \t\t\t\t      int reuse_open)\n {\n \tsize_t cur_len = !st->merged ? 0 : st->merged->stack_len;\n@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n \n \twhile (*names) {\n \t\tstruct reftable_reader *rd = NULL;\n-\t\tchar *name = *names++;\n+\t\tconst char *name = *names++;\n \n \t\t/* this is linear; we assume compaction keeps the number of\n \t\t   tables under control so this is not quadratic. */\n@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\t\t\tgoto out;\n \t\t}\n \n-\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\terr = reftable_stack_reload_once(st, (const char **) names, reuse_open);\n \t\tif (!err)\n \t\t\tbreak;\n \t\tif (err != REFTABLE_NOT_EXIST_ERROR)\n@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\terr = read_lines(st->list_file, &names_after);\n \t\tif (err < 0)\n \t\t\tgoto out;\n-\t\tif (names_equal(names_after, names)) {\n+\t\tif (names_equal((const char **) names_after,\n+\t\t\t\t(const char **) names)) {\n \t\t\terr = REFTABLE_NOT_EXIST_ERROR;\n \t\t\tgoto out;\n \t\t}\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..07d89b45da 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,9 +116,9 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n \n \tEXPECT(names_equal(a, a));\n \tEXPECT(!names_equal(a, b));\ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496485","messageId":"92cb0b28c65bed0f3cd10e6c4c57b98f199536a9.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 02/27] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:27:55Z","receivedAt":"2024-06-06T10:27:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and the struct memers are never\n    written to or free'd.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never written to or free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never written to or free'd.\n\nAll of these are intentionally kept alive and are never written to.\nFurthermore, all of these are being assigned both string constants in\nsome places, and allocated strings in other places. This will cause\nwarnings once we enable `-Wwrite-strings`, so let's mark the respective\nfields as `const char *` and cast away the constness when assigning\nthose values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496486","messageId":"379145478cbbf59387c799952a97abc16a528eab.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 03/27] refs/reftable: stop micro-optimizing refname allocations on copy","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:27:59Z","receivedAt":"2024-06-06T10:28:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When copying refs, we execute `write_copy_table()` to write the new\ntable. As the names are given to us via `arg->newname` and\n`arg->oldname`, respectively, we optimize away some allocations by\nassigning those fields to the reftable records we are about to write\ndirectly, without duplicating them. This requires us to cast the input\nto `char *` pointers as they are in fact constant strings. Later on, we\nthen unset the refname for all of the records before calling\n`reftable_log_record_release()` on them.\n\nWe also do this when assigning the \"HEAD\" constant, but here we do not\ncast because its type is `char[]` by default. It's about to be turned\ninto `const char *` though once we enable `-Wwrite-strings` and will\nthus cause another warning.\n\nIt's quite dubious whether this micro-optimization really helps. We're\nabout to write to disk anyway, which is going to be way slower than a\nsmall handful of allocations. Let's drop the optimization altogther and\ninstead copy arguments to simplify the code and avoid the future warning\nwith `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c | 28 ++++++++++++++++------------\n 1 file changed, 16 insertions(+), 12 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..e77faa2b9d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t * old reference.\n \t */\n \trefs[0] = old_ref;\n-\trefs[0].refname = (char *)arg->newname;\n+\trefs[0].refname = xstrdup(arg->newname);\n \trefs[0].update_index = creation_ts;\n \tif (arg->delete_old) {\n-\t\trefs[1].refname = (char *)arg->oldname;\n+\t\trefs[1].refname = xstrdup(arg->oldname);\n \t\trefs[1].value_type = REFTABLE_REF_DELETION;\n \t\trefs[1].update_index = deletion_ts;\n \t}\n@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \t\tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n \t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = xstrdup(\"HEAD\");\n+\t\t\tlogs[logs_nr].value.update.name =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.name);\n+\t\t\tlogs[logs_nr].value.update.email =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.email);\n+\t\t\tlogs[logs_nr].value.update.message =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.message);\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\tlogs[logs_nr].refname = (char *)arg->newname;\n+\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n \t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\t */\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tlogs[logs_nr] = old_log;\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs_nr++;\n \n \t\t/*\n@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (arg->delete_old) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n-\t\t\tlogs[logs_nr].refname = (char *)arg->oldname;\n+\t\t\tlogs[logs_nr].refname = xstrdup(arg->oldname);\n \t\t\tlogs[logs_nr].value_type = REFTABLE_LOG_DELETION;\n \t\t\tlogs[logs_nr].update_index = old_log.update_index;\n \t\t\tlogs_nr++;\n@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \treftable_iterator_destroy(&it);\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n-\tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n-\t\t\tcontinue;\n-\t\tlogs[i].refname = NULL;\n+\tfor (i = 0; i < logs_nr; i++)\n \t\treftable_log_record_release(&logs[i]);\n-\t}\n \tfree(logs);\n+\tfor (i = 0; i < ARRAY_SIZE(refs); i++)\n+\t\treftable_ref_record_release(&refs[i]);\n \treftable_ref_record_release(&old_ref);\n \treftable_log_record_release(&old_log);\n \treturn ret;\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496487","messageId":"d0a2a2f6c55af6da9703a1d64f45f77f4c00cd80.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 04/27] reftable: cast away constness when assigning constants to records","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:04Z","receivedAt":"2024-06-06T10:28:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reftable records are used in multiple ways throughout the reftable\nlibrary. In many of those cases they merely act as input to a function\nwithout getting modified by it at all. Most importantly, this happens\nwhen writing records and when querying for records.\n\nWe rely on this in our tests and thus assign string constants to those\nfields, which is about to generate warnings as those fields are of type\n`char *`. While we could go through the process and instead allocate\nthose strings in all of our tests, this feels quite unnecessary.\n\nInstead, add casts to `char *` for all of those strings. As this is part\nof our tests, this also nicely serves as a demonstration that nothing\nwrites or frees those string constants, which would otherwise lead to\nsegfaults.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reftable/block_test.c     |  2 +-\n reftable/merged_test.c    | 44 +++++++++++++++++------------------\n reftable/readwrite_test.c | 32 +++++++++++++-------------\n reftable/stack_test.c     | 48 +++++++++++++++++++--------------------\n 4 files changed, 63 insertions(+), 63 deletions(-)\n\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..90aecd5a7c 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = (char *) \"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..6d1159d12d 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n static void test_merged_between(void)\n {\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = (char *) \"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -165,38 +165,38 @@ static void test_merged(void)\n {\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = (char *) \"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,46 +291,46 @@ static void test_merged_logs(void)\n {\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -406,7 +406,7 @@ static void test_default_write_opts(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..c55019232b 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = (char *) \"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)\n \tint err;\n \tint i;\n \tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n \t\t\t.update_index = 0xa,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n \t\t\t\t\t   .tz_offset = 100,\n \t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n \t\t\t\t   } } };\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n@@ -156,15 +156,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = (char *) \"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -728,7 +728,7 @@ static void test_write_empty_key(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -752,18 +752,18 @@ static void test_write_key_order(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 07d89b45da..4abf92636d 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)\n \n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = (char *) \"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \n@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_addition *add = NULL;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = (char *) \"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = (char *) \"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = (char *) \"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = (char *) \"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)\n \tint err;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \n \t\t/*\n@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496488","messageId":"ead27d3d978701966cdc2884dab2db77fae5d031.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 05/27] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:09Z","receivedAt":"2024-06-06T10:28:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization of the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496489","messageId":"7cb5df9182e5739f54be089832d85e0447efe46d.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 06/27] builtin/remote: cast away constness in `get_head_names()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:13Z","receivedAt":"2024-06-06T10:28:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_head_names()`, we assign the \"refs/heads/*\" string constant to\n`struct refspec_item::{src,dst}`, which are both non-constant pointers.\nIdeally, we'd refactor the code such that both of these fields were\nconstant. But `struct refspec_item` is used for two different usecases\nwith conflicting requirements:\n\n  - To query for a source or destination based on the given refspec. The\n    caller either sets `src` or `dst` as the branch that we want to\n    search for, and the respective other field gets populated. The\n    fields should be constant when being used as a query parameter,\n    which is owned by the caller, and non-constant when being used as an\n    out parameter, which is owned by the refspec item. This is is\n    contradictory in itself already.\n\n  - To store refspec items with their respective source and destination\n    branches, in which case both fields should be owned by the struct.\n\nIdeally, we'd split up this interface to clearly separate between\nquerying and storing, which would enable us to clarify lifetimes of the\nstrings. This would be a much bigger undertaking though.\n\nInstead, accept the status quo for now and cast away the constness of\nthe source and destination patterns. We know that those are not being\nwritten to or freed, so while this is ugly it certainly is fine for now.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..b44f580b8c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n {\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n-\tstruct refspec_item refspec;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n \n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.force = 0;\n-\trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\n@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \n \tfree_refs(fetch_map);\n \tfree_refs(matches);\n-\n \treturn 0;\n }\n \n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496490","messageId":"6e631a9ea46aa563d197f4a057698a0af53ad50f.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 07/27] diff: cast string constant in `fill_textconv()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:18Z","receivedAt":"2024-06-06T10:28:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `fill_textconv()` function is responsible for converting an input\nfile with a textconv driver, which is then passed to the caller. Weirdly\nthough, the function also handles the case where there is no textconv\ndriver at all. In that case, it will return either the contents of the\npopulated filespec, or an empty string if the filespec is invalid.\n\nThese two cases have differing memory ownership semantics. When there is\na textconv driver, then the result is an allocated string. Otherwise,\nthe result is either a string constant or owned by the filespec struct.\nAll callers are in fact aware of this weirdness and only end up freeing\nthe output buffer when they had a textconv driver.\n\nIdeally, we'd split up this interface to only perform the conversion via\nthe textconv driver, and BUG in case the caller didn't provide one. This\nwould make memory ownership semantics much more straight forward. For\nnow though, let's simply cast the empty string constant to `char *` to\navoid a warning with `-Wwrite-strings`. This is equivalent to the same\ncast that we already have in `fill_mmfile()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..cecda216cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = (char *) \"\";\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496491","messageId":"ac164651a325476ecf041641c11785ac8c0c1501.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 08/27] line-log: stop assigning string constant to file parent buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:23Z","receivedAt":"2024-06-06T10:28:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Stop assigning a string constant to the file parent buffer and instead\nassign an allocated string. While the code is fine in practice, it will\nbreak once we compile with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..bd3e663c24 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar *parent_data_to_free = NULL;\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = parent_data_to_free = xstrdup(\"\");\n \t\tfile_parent.size = 0;\n \t}\n \n@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n \tdiff_ranges_release(&diff);\n \n+\tfree(parent_data_to_free);\n \treturn ((*diff_out)->parent.nr > 0);\n }\n \n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496492","messageId":"b717af02f0958b0c204164ba92453ea7845186b7.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 09/27] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:27Z","receivedAt":"2024-06-06T10:28:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex bd3e663c24..67c80b39a0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496493","messageId":"b46dd3210d3e6f73c84fba6aaa8f21929f7052bb.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 10/27] entry: refactor how we remove items for delayed checkouts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:32Z","receivedAt":"2024-06-06T10:28:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When finalizing a delayed checkout, we sort out several strings from the\npassed-in string list by first assigning the empty string to those\nfilters and then calling `string_list_remove_empty_items()`. Assigning\nthe empty string will cause compiler warnings though as the string is\na `char *` once we enable `-Wwrite-strings`.\n\nRefactor the code to use a `NULL` pointer with `filter_string_list()`\ninstead to avoid this warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n entry.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..f291d8eee6 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)\n \treturn !available;\n }\n \n+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)\n+{\n+\treturn !!item->string;\n+}\n+\n int finish_delayed_checkout(struct checkout *state, int show_progress)\n {\n \tint errs = 0;\n@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = NULL;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\n@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\terrs = 1;\n \t\t\t}\n \t\t}\n-\t\tstring_list_remove_empty_items(&dco->filters, 0);\n+\n+\t\tfilter_string_list(&dco->filters, 0, string_is_not_null, NULL);\n \t}\n \tstop_progress(&progress);\n \tstring_list_clear(&dco->filters, 0);\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496494","messageId":"030dbd0288170eb1cc8fa0186f3b5e022ff67c40.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 11/27] ident: add casts for fallback name and GECOS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:37Z","receivedAt":"2024-06-06T10:28:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `xgetpwuid_self()`, we return a fallback identity when it was not\npossible to look up the current identity. This fallback identity needs\nto be internal and must never be written to by the calles as specified\nby getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked\nas non-constant though, it will cause a warning to assign constant\nstrings to them once compiling with `-Wwrite-strings`.\n\nAdd explicit casts to avoid the warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ident.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..caf41fb2a9 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tfallback.pw_name = (char *) \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tfallback.pw_gecos = (char *) \"Unknown\";\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496495","messageId":"5cd014c22cb2fc7e34666aa6bd036d3cc4ce9039.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:41Z","receivedAt":"2024-06-06T10:28:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The buffers of cached objects are never modified, but are still stored\nas a non-constant pointer. This will cause a compiler warning once we\nenable the `-Wwrite-strings` compiler warning as we assign an empty\nconstant string when initializing the static `empty_tree` cached object.\n\nConvert the field to be constant. This requires us to shuffle around\nthe code a bit because we memcpy(3P) into the allocated buffer in\n`pretend_object_file()`. This is easily fixed though by allocating the\nbuffer into a temporary variable first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..6309b24387 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)\n static struct cached_object {\n \tstruct object_id oid;\n \tenum object_type type;\n-\tvoid *buf;\n+\tconst void *buf;\n \tunsigned long size;\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n@@ -1778,17 +1778,22 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \t\t\tstruct object_id *oid)\n {\n \tstruct cached_object *co;\n+\tchar *co_buf;\n+\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n-\t    find_cached_object(oid))\n+\t    find_cached_object(oid)) {\n+\t\tfree(co_buf);\n \t\treturn 0;\n+\t}\n \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n-\tco->buf = xmalloc(len);\n-\tmemcpy(co->buf, buf, len);\n+\tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n }\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496496","messageId":"69d904ddcec17519ee981a5db2a73f467c7e747e.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 13/27] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:46Z","receivedAt":"2024-06-06T10:28:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 6309b24387..2d5bd3a211 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2487,12 +2487,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2502,11 +2503,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2523,8 +2523,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496497","messageId":"ed8f07aa59030ece540dbf8c7d64733b8b234d4c.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 14/27] pretty: add casts for decoration option pointers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:51Z","receivedAt":"2024-06-06T10:28:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct decoration_options` have a prefix and suffix field which are\nboth non-constant, but we assign a constant pointer to them. This is\nsafe to do because we pass them to `format_decorations()`, which never\nmodifies these pointers, and then immediately discard the structure. Add\nexplicit casts to avoid compilation warnings with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n pretty.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1df9d635fb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tcase 'D':\n \t\t{\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = (char *) \"\",\n+\t\t\t\t.suffix = (char *) \"\",\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496498","messageId":"5953ae1dac6847c33033218658ec466ed69de783.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 15/27] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:28:55Z","receivedAt":"2024-06-06T10:29:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 16 ++++++++++++++--\n compat/mingw.c    | 28 ++++++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 31 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..c33579ef61 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -10,7 +10,13 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * basename(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -34,7 +40,13 @@ char *gitdirname(char *path)\n \tint dos_drive_prefix;\n \n \tif (!p)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * dirname(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..d378cd04cb 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\t/*\n+\t\t * Data returned by getpwuid(3P) is treated as internal and\n+\t\t * must never be written to or freed.\n+\t\t */\n+\t\tp->pw_gecos = (char *) \"unknown\";\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496499","messageId":"c80f6eff8c11d689da2ab8731c1d716c69079407.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 16/27] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:00Z","receivedAt":"2024-06-06T10:29:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496500","messageId":"3afd012a886d4e33e976b3ef4f02b33d5c02503b.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 17/27] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:05Z","receivedAt":"2024-06-06T10:29:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496501","messageId":"527755b64859a59753bb28ecdfe1e36e827fe5f6.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 18/27] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:09Z","receivedAt":"2024-06-06T10:29:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496502","messageId":"4598592d2f3b21b8777398ee6f30eaf766aa7efe.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 19/27] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:14Z","receivedAt":"2024-06-06T10:29:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..d0f767df8e 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchrnul(arg, ' ');\n+\t\t\tsize_t arglen = value - arg;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n+\t\t\tif (*value)\n+\t\t\t\tvalue++; /* skip over SP */\n \t\t\telse\n \t\t\t\tvalue = \"true\";\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496503","messageId":"38fcea2845b16a6c23a0496330c538501cbc76b3.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 20/27] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:18Z","receivedAt":"2024-06-06T10:29:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496504","messageId":"f990bbeb858d4e3facfe079d62c4b292daef8c52.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 21/27] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:23Z","receivedAt":"2024-06-06T10:29:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496505","messageId":"fff2379832f2c59f7e1a442b50033c107719b360.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 22/27] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:27Z","receivedAt":"2024-06-06T10:29:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496506","messageId":"9ab84e459a330fbd65c4b3bfdf9b6efaa8c05aaf.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 23/27] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:32Z","receivedAt":"2024-06-06T10:29:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused member `imap_server_conf::name`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 42 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..da3e7ec17e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n+\tint ret;\n \n \tsetup_git_directory_gently(&nongit_ok);\n \tgit_config(git_imap_config, &server);\n@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496507","messageId":"81c69da2e830d04d4fbb2108944d55c8711d4e8b.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:36Z","receivedAt":"2024-06-06T10:29:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::default_backend` field is a non-constant\nstring, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\nFix this by using `xstrdup()` to assign the variable and introduce a new\nfunction `rebase_options_release()` that releases memory held by the\nstructure, including the newly-allocated variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 25 ++++++++++++++++---------\n 1 file changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..adc990b55e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -135,7 +135,7 @@ struct rebase_options {\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n+\t\t.default_backend = xstrdup(\"merge\"),  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n@@ -151,6 +151,19 @@ struct rebase_options {\n \t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n \t}\n \n+static void rebase_options_release(struct rebase_options *opts)\n+{\n+\tfree(opts->default_backend);\n+\tfree(opts->reflog_action);\n+\tfree(opts->head_name);\n+\tstrvec_clear(&opts->git_am_opts);\n+\tfree(opts->gpg_sign_opt);\n+\tstring_list_clear(&opts->exec, 0);\n+\tfree(opts->strategy);\n+\tstring_list_clear(&opts->strategy_opts, 0);\n+\tstrbuf_release(&opts->git_format_patch_opt);\n+}\n+\n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n {\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n@@ -796,6 +809,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1833,14 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n-\tfree(options.reflog_action);\n-\tfree(options.head_name);\n-\tstrvec_clear(&options.git_am_opts);\n-\tfree(options.gpg_sign_opt);\n-\tstring_list_clear(&options.exec, 0);\n-\tfree(options.strategy);\n-\tstring_list_clear(&options.strategy_opts, 0);\n-\tstrbuf_release(&options.git_format_patch_opt);\n+\trebase_options_release(&options);\n \tfree(squash_onto_name);\n \tfree(keep_base_onto_name);\n \treturn !!ret;\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496509","messageId":"6819bf6116f0b5f7a57263a9564919aff695267a.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 25/27] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:41Z","receivedAt":"2024-06-06T10:29:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::strategy` field is a `char *`, but we do end\nup assigning string constants to it in two cases:\n\n  - When being passed a `--strategy=` option via the command line.\n\n  - When being passed a strategy option via `--strategy-option=`, but\n    not a strategy.\n\nThis will cause warnings once we enable `-Wwrite-strings`.\n\nIdeally, we'd just convert the field to be a `const char *`. But we also\nassign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\nhave to strdup(3P) into it.\n\nInstead, refactor the code to make sure that we only ever assign\nallocated strings to this field.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex adc990b55e..4506bae768 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1061,6 +1061,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n \tconst char *branch_name;\n+\tconst char *strategy_opt = NULL;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n@@ -1175,7 +1176,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n+\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n \t\t\t\tN_(\"option\"),\n@@ -1484,13 +1485,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\tif (strategy_opt)\n+\t\toptions.strategy = xstrdup(strategy_opt);\n+\telse if (options.strategy_opts.nr && !options.strategy)\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496508","messageId":"a1d2149429db608e42cd71870ba1b6f0337d34fd.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 26/27] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:45Z","receivedAt":"2024-06-06T10:29:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496510","messageId":"c714b67199572b9475852cafeac3133081306186.1717667854.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717667854.git.ps@pks.im","subject":"[PATCH v5 27/27] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:29:50Z","receivedAt":"2024-06-06T10:29:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.2.409.g7b0defb391.dirty\n\n"},{"id":"496511","messageId":"ZmGRFAL_hQSNZZHN@tanuki","threadId":"61561","inReplyTo":"20240605104646.GA3972667@coredump.intra.peff.net","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-06T10:36:04Z","receivedAt":"2024-06-06T10:36:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jun 05, 2024 at 06:46:46AM -0400, Jeff King wrote:\n> On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:\n> \n> > I wonder if we can do something to separate these two concerns\n> > apart, using a trick similar to what we often use with an extra\n> > variable \"to_free\".  Doing so would bloat the refspec_item, but\n> > unlike the references themselves, there won't be thousands of them,\n> > so it may not be an issue, perhaps?\n> \n> I had a similar thought while looking at this spot a while ago, so I dug\n> this attempt out of my stash. It's quite ugly, as you need to keep the\n> storage pointer and the const pointer in sync. Especially because\n> there's a lot of clever pointer indirection via match_name_with_pattern().\n> \n> So I don't think it's the best way to refactor this code, but I present\n> it here for your amusement/disgust. This is on top of what you have\n> queued in ps/no-writable-strings.\n\nJust for the record: I'll keep such a refactoring out of this patch\nseries as it is already big enough. I do agree though that we should\nideally fix this interface in a subsequent iteration.\n\nPatrick\n"},{"id":"496542","messageId":"xmqqa5jynimn.fsf@gitster.g","threadId":"61561","inReplyTo":"xmqqmsnyppo3.fsf@gitster.g","subject":"Re: [PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-06T16:25:20Z","receivedAt":"2024-06-06T16:25:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Indeed, that seems to be the case.  With the attached at the tip of\n> the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002\n> tests with SANTIZE=leak.\n>\n> From f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001\n> From: Junio C Hamano <gitster@pobox.com>\n> Date: Wed, 5 Jun 2024 23:03:34 -0700\n> Subject: [PATCH] fixup! object-file: mark cached object buffers as const\n>\n> ---\n>  object-file.c | 4 +++-\n>  1 file changed, 3 insertions(+), 1 deletion(-)\n>\n> diff --git a/object-file.c b/object-file.c\n> index b5b5a59dc6..2d5bd3a211 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n>  \n>  \thash_object_file(the_hash_algo, buf, len, type, oid);\n>  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n> -\t    find_cached_object(oid))\n> +\t    find_cached_object(oid)) {\n> +\t\tfree(co_buf);\n>  \t\treturn 0;\n> +\t}\n>  \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n>  \tco = &cached_objects[cached_object_nr++];\n>  \tco->size = len;\n\nWait.  Why do we need to allocate co_buf that early in the first\nplace?  IOW, shouldn't the fixup be more like this?\n\n object-file.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git c/object-file.c w/object-file.c\nindex b5b5a59dc6..0b58751f94 100644\n--- c/object-file.c\n+++ w/object-file.c\n@@ -1780,9 +1780,6 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tstruct cached_object *co;\n \tchar *co_buf;\n \n-\tco_buf = xmalloc(len);\n-\tmemcpy(co_buf, buf, len);\n-\n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n \t    find_cached_object(oid))\n@@ -1791,6 +1788,8 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n \tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n"},{"id":"496553","messageId":"xmqqfrtqlzye.fsf@gitster.g","threadId":"61561","inReplyTo":"5cd014c22cb2fc7e34666aa6bd036d3cc4ce9039.1717667854.git.ps@pks.im","subject":"Re: [PATCH v5 12/27] object-file: mark cached object buffers as const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-06T17:54:01Z","receivedAt":"2024-06-06T17:54:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> @@ -1778,17 +1778,22 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n>  \t\t\tstruct object_id *oid)\n>  {\n>  \tstruct cached_object *co;\n> +\tchar *co_buf;\n> +\n> +\tco_buf = xmalloc(len);\n> +\tmemcpy(co_buf, buf, len);\n\nI do not see why we need to do this so early.  The copy is not used\nor buf gets modified by the call to hash_object_file(), so ...\n\n>  \thash_object_file(the_hash_algo, buf, len, type, oid);\n>  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n> -\t    find_cached_object(oid))\n> +\t    find_cached_object(oid)) {\n> +\t\tfree(co_buf);\n>  \t\treturn 0;\n> +\t}\n>  \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n>  \tco = &cached_objects[cached_object_nr++];\n>  \tco->size = len;\n>  \tco->type = type;\n> -\tco->buf = xmalloc(len);\n> -\tmemcpy(co->buf, buf, len);\n> +\tco->buf = co_buf;\n\n... wouldn't this be a better place to do the \"copy to the heap\nmemory pointed by a writable pointer and then point that piece of\nmemory with a read-only pointer\" pattern?\n\n>  \toidcpy(&co->oid, oid);\n>  \treturn 0;\n>  }\n"},{"id":"496597","messageId":"ZmKSBwqIkOLUhOlK@tanuki","threadId":"61561","inReplyTo":"xmqqa5jynimn.fsf@gitster.g","subject":"Re: [PATCH v4 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T04:52:23Z","receivedAt":"2024-06-07T04:52:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jun 06, 2024 at 09:25:20AM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Indeed, that seems to be the case.  With the attached at the tip of\n> > the branch and rebuilding 'seen' seems to pass these 6130, 7010, 8002\n> > tests with SANTIZE=leak.\n> >\n> > From f307bbf7bd317d90db29bd1589b49e84b9e37e88 Mon Sep 17 00:00:00 2001\n> > From: Junio C Hamano <gitster@pobox.com>\n> > Date: Wed, 5 Jun 2024 23:03:34 -0700\n> > Subject: [PATCH] fixup! object-file: mark cached object buffers as const\n> >\n> > ---\n> >  object-file.c | 4 +++-\n> >  1 file changed, 3 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/object-file.c b/object-file.c\n> > index b5b5a59dc6..2d5bd3a211 100644\n> > --- a/object-file.c\n> > +++ b/object-file.c\n> > @@ -1785,8 +1785,10 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n> >  \n> >  \thash_object_file(the_hash_algo, buf, len, type, oid);\n> >  \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n> > -\t    find_cached_object(oid))\n> > +\t    find_cached_object(oid)) {\n> > +\t\tfree(co_buf);\n> >  \t\treturn 0;\n> > +\t}\n> >  \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n> >  \tco = &cached_objects[cached_object_nr++];\n> >  \tco->size = len;\n> \n> Wait.  Why do we need to allocate co_buf that early in the first\n> place?  IOW, shouldn't the fixup be more like this?\n\nThat is of course the much more elegant solution here. Will adapt.\n\nPatrick\n"},{"id":"496600","messageId":"cover.1717740365.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1716983704.git.ps@pks.im","subject":"[PATCH v6 00/27] Compile with `-Wwrite-strings`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:34Z","receivedAt":"2024-06-07T06:37:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is v6 of my patch series that starts to compile our codebase with\n`-Wwrite-strings`. This warning turns the type of string constants from\n`char []` to `const char []` and should thus help us to detect cases\nwhere we may accidentally write to or free such a string constant.\n\nThe only change is in patch 12, where we now allocate a buffer later in\ntime to avoid a useless allocation and the need for a call to free.\n\nPatrick\n\nPatrick Steinhardt (27):\n  global: improve const correctness when assigning string constants\n  global: convert intentionally-leaking config strings to consts\n  refs/reftable: stop micro-optimizing refname allocations on copy\n  reftable: cast away constness when assigning constants to records\n  refspec: remove global tag refspec structure\n  builtin/remote: cast away constness in `get_head_names()`\n  diff: cast string constant in `fill_textconv()`\n  line-log: stop assigning string constant to file parent buffer\n  line-log: always allocate the output prefix\n  entry: refactor how we remove items for delayed checkouts\n  ident: add casts for fallback name and GECOS\n  object-file: mark cached object buffers as const\n  object-file: make `buf` parameter of `index_mem()` a constant\n  pretty: add casts for decoration option pointers\n  compat/win32: fix const-correctness with string constants\n  http: do not assign string constant to non-const field\n  parse-options: cast long name for OPTION_ALIAS\n  send-pack: always allocate receive status\n  remote-curl: avoid assigning string constant to non-const variable\n  revision: always store allocated strings in output encoding\n  mailmap: always store allocated strings in mailmap blob\n  imap-send: drop global `imap_server_conf` variable\n  imap-send: fix leaking memory in `imap_server_conf`\n  builtin/rebase: do not assign default backend to non-constant field\n  builtin/rebase: always store allocated string in `options.strategy`\n  builtin/merge: always store allocated strings in `pull_twohead`\n  config.mak.dev: enable `-Wwrite-strings` warning\n\n builtin/bisect.c             |   3 +-\n builtin/blame.c              |   2 +-\n builtin/bugreport.c          |   2 +-\n builtin/check-ignore.c       |   4 +-\n builtin/clone.c              |  14 ++--\n builtin/commit.c             |   6 +-\n builtin/diagnose.c           |   2 +-\n builtin/fetch.c              |  11 ++-\n builtin/log.c                |   2 +-\n builtin/mailsplit.c          |   4 +-\n builtin/merge.c              |  18 +++--\n builtin/pull.c               |  52 +++++++-------\n builtin/rebase.c             |  39 ++++++-----\n builtin/receive-pack.c       |   4 +-\n builtin/remote.c             |  12 ++--\n builtin/revert.c             |   2 +-\n builtin/send-pack.c          |   2 +\n compat/basename.c            |  16 ++++-\n compat/mingw.c               |  28 ++++----\n compat/regex/regcomp.c       |   2 +-\n compat/winansi.c             |   2 +-\n config.mak.dev               |   1 +\n diff.c                       |   6 +-\n diffcore-rename.c            |   6 +-\n entry.c                      |  14 ++--\n fmt-merge-msg.c              |   2 +-\n fsck.c                       |   2 +-\n fsck.h                       |   2 +-\n gpg-interface.c              |   6 +-\n http-backend.c               |   2 +-\n http.c                       |   5 +-\n ident.c                      |   4 +-\n imap-send.c                  | 130 ++++++++++++++++++++---------------\n line-log.c                   |  22 +++---\n mailmap.c                    |   2 +-\n merge-ll.c                   |  11 ++-\n object-file.c                |  22 +++---\n parse-options.h              |   2 +-\n pretty.c                     |   6 +-\n refs.c                       |   2 +-\n refs.h                       |   2 +-\n refs/reftable-backend.c      |  28 ++++----\n refspec.c                    |  13 ----\n refspec.h                    |   1 -\n reftable/basics.c            |  15 ++--\n reftable/basics.h            |   4 +-\n reftable/basics_test.c       |   4 +-\n reftable/block_test.c        |   2 +-\n reftable/merged_test.c       |  44 ++++++------\n reftable/readwrite_test.c    |  32 ++++-----\n reftable/record.c            |   6 +-\n reftable/stack.c             |  10 +--\n reftable/stack_test.c        |  56 +++++++--------\n remote-curl.c                |  53 +++++++-------\n revision.c                   |   3 +-\n run-command.c                |   2 +-\n send-pack.c                  |   2 +-\n t/helper/test-hashmap.c      |   3 +-\n t/helper/test-json-writer.c  |  10 +--\n t/helper/test-regex.c        |   4 +-\n t/helper/test-rot13-filter.c |   5 +-\n t/t3900-i18n-commit.sh       |   1 +\n t/t3901-i18n-patch.sh        |   1 +\n t/unit-tests/t-strbuf.c      |  10 +--\n trailer.c                    |   2 +-\n userdiff.c                   |  10 +--\n userdiff.h                   |  12 ++--\n wt-status.c                  |   2 +-\n 68 files changed, 444 insertions(+), 367 deletions(-)\n\nRange-diff against v5:\n 1:  e01fde88fe =  1:  ba50d96081 global: improve const correctness when assigning string constants\n 2:  92cb0b28c6 =  2:  4769bdb893 global: convert intentionally-leaking config strings to consts\n 3:  379145478c =  3:  90613bd582 refs/reftable: stop micro-optimizing refname allocations on copy\n 4:  d0a2a2f6c5 =  4:  b126783ba3 reftable: cast away constness when assigning constants to records\n 5:  ead27d3d97 =  5:  d10566e0a9 refspec: remove global tag refspec structure\n 6:  7cb5df9182 =  6:  e9b8f9eaef builtin/remote: cast away constness in `get_head_names()`\n 7:  6e631a9ea4 =  7:  8ea984128d diff: cast string constant in `fill_textconv()`\n 8:  ac164651a3 =  8:  777b93a89a line-log: stop assigning string constant to file parent buffer\n 9:  b717af02f0 =  9:  527533540e line-log: always allocate the output prefix\n10:  b46dd3210d = 10:  4d0b8dcbaf entry: refactor how we remove items for delayed checkouts\n11:  030dbd0288 = 11:  0d36b6e5d7 ident: add casts for fallback name and GECOS\n12:  5cd014c22c ! 12:  bfd632fea7 object-file: mark cached object buffers as const\n    @@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object\n      {\n      \tstruct cached_object *co;\n     +\tchar *co_buf;\n    -+\n    -+\tco_buf = xmalloc(len);\n    -+\tmemcpy(co_buf, buf, len);\n      \n      \thash_object_file(the_hash_algo, buf, len, type, oid);\n      \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n    --\t    find_cached_object(oid))\n    -+\t    find_cached_object(oid)) {\n    -+\t\tfree(co_buf);\n    - \t\treturn 0;\n    -+\t}\n    - \tALLOC_GROW(cached_objects, cached_object_nr + 1, cached_object_alloc);\n    +@@ object-file.c: int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n      \tco = &cached_objects[cached_object_nr++];\n      \tco->size = len;\n      \tco->type = type;\n     -\tco->buf = xmalloc(len);\n     -\tmemcpy(co->buf, buf, len);\n    ++\tco_buf = xmalloc(len);\n    ++\tmemcpy(co_buf, buf, len);\n     +\tco->buf = co_buf;\n      \toidcpy(&co->oid, oid);\n      \treturn 0;\n13:  69d904ddce = 13:  4770470a84 object-file: make `buf` parameter of `index_mem()` a constant\n14:  ed8f07aa59 = 14:  02e79d030b pretty: add casts for decoration option pointers\n15:  5953ae1dac = 15:  57402de20a compat/win32: fix const-correctness with string constants\n16:  c80f6eff8c = 16:  71c83468f1 http: do not assign string constant to non-const field\n17:  3afd012a88 = 17:  f3e02df0ca parse-options: cast long name for OPTION_ALIAS\n18:  527755b648 = 18:  a46708b898 send-pack: always allocate receive status\n19:  4598592d2f = 19:  99c88897be remote-curl: avoid assigning string constant to non-const variable\n20:  38fcea2845 = 20:  03556233d6 revision: always store allocated strings in output encoding\n21:  f990bbeb85 = 21:  2bb96449e2 mailmap: always store allocated strings in mailmap blob\n22:  fff2379832 = 22:  8059e013a1 imap-send: drop global `imap_server_conf` variable\n23:  9ab84e459a = 23:  04c9cfd34d imap-send: fix leaking memory in `imap_server_conf`\n24:  81c69da2e8 = 24:  b2b4a01a2e builtin/rebase: do not assign default backend to non-constant field\n25:  6819bf6116 = 25:  09980d211b builtin/rebase: always store allocated string in `options.strategy`\n26:  a1d2149429 = 26:  d5dc1453f2 builtin/merge: always store allocated strings in `pull_twohead`\n27:  c714b67199 = 27:  2c051d1fe6 config.mak.dev: enable `-Wwrite-strings` warning\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496601","messageId":"ba50d960814054dade5fc17a18513d7e070ae71b.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 01/27] global: improve const correctness when assigning string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:39Z","receivedAt":"2024-06-07T06:37:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to enable `-Wwrite-strings`, which changes the type of\nstring constants to `const char[]`. Fix various sites where we assign\nsuch constants to non-const variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/bisect.c             |  3 ++-\n builtin/blame.c              |  2 +-\n builtin/bugreport.c          |  2 +-\n builtin/check-ignore.c       |  4 +--\n builtin/clone.c              |  6 ++---\n builtin/commit.c             |  6 ++---\n builtin/diagnose.c           |  2 +-\n builtin/log.c                |  2 +-\n builtin/mailsplit.c          |  4 +--\n builtin/pull.c               | 52 ++++++++++++++++++------------------\n builtin/receive-pack.c       |  4 +--\n builtin/revert.c             |  2 +-\n compat/regex/regcomp.c       |  2 +-\n diff.c                       |  4 +--\n diffcore-rename.c            |  6 ++---\n fmt-merge-msg.c              |  2 +-\n fsck.c                       |  2 +-\n fsck.h                       |  2 +-\n gpg-interface.c              |  2 +-\n http-backend.c               |  2 +-\n imap-send.c                  |  6 ++---\n pretty.c                     |  2 +-\n refs.c                       |  2 +-\n refs.h                       |  2 +-\n reftable/basics.c            | 15 +++++------\n reftable/basics.h            |  4 +--\n reftable/basics_test.c       |  4 +--\n reftable/record.c            |  6 ++---\n reftable/stack.c             | 10 ++++---\n reftable/stack_test.c        |  8 +++---\n run-command.c                |  2 +-\n t/helper/test-hashmap.c      |  3 ++-\n t/helper/test-json-writer.c  | 10 +++----\n t/helper/test-regex.c        |  4 +--\n t/helper/test-rot13-filter.c |  5 ++--\n t/unit-tests/t-strbuf.c      | 10 ++++---\n trailer.c                    |  2 +-\n wt-status.c                  |  2 +-\n 38 files changed, 106 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex a58432b9d9..dabce9b542 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -262,7 +262,8 @@ static int bisect_reset(const char *commit)\n \treturn bisect_clean_state();\n }\n \n-static void log_commit(FILE *fp, char *fmt, const char *state,\n+static void log_commit(FILE *fp,\n+\t\t       const char *fmt, const char *state,\n \t\t       struct commit *commit)\n {\n \tstruct pretty_print_context pp = {0};\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 838cd476be..98c7629b6a 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -134,7 +134,7 @@ static void get_ac_line(const char *inbuf, const char *what,\n {\n \tstruct ident_split ident;\n \tsize_t len, maillen, namelen;\n-\tchar *tmp, *endp;\n+\tconst char *tmp, *endp;\n \tconst char *namebuf, *mailbuf;\n \n \ttmp = strstr(inbuf, what);\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 25f860a0d9..b3cc77af53 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -107,7 +107,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode diagnose = DIAGNOSE_NONE;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tconst char *user_relative_path = NULL;\n \tchar *prefixed_filename;\n \tsize_t output_path_len;\ndiff --git a/builtin/check-ignore.c b/builtin/check-ignore.c\nindex 6c43430ec4..2bda6a1d46 100644\n--- a/builtin/check-ignore.c\n+++ b/builtin/check-ignore.c\n@@ -35,8 +35,8 @@ static const struct option check_ignore_options[] = {\n \n static void output_pattern(const char *path, struct path_pattern *pattern)\n {\n-\tchar *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n-\tchar *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n+\tconst char *bang  = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE)  ? \"!\" : \"\";\n+\tconst char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? \"/\" : \"\";\n \tif (!nul_term_line) {\n \t\tif (!verbose) {\n \t\t\twrite_name_quoted(path, stdout, '\\n');\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 23993b905b..92ab7d7165 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -71,7 +71,7 @@ static char *option_branch = NULL;\n static struct string_list option_not = STRING_LIST_INIT_NODUP;\n static const char *real_git_dir;\n static const char *ref_format;\n-static char *option_upload_pack = \"git-upload-pack\";\n+static const char *option_upload_pack = \"git-upload-pack\";\n static int option_verbosity;\n static int option_progress = -1;\n static int option_sparse_checkout;\n@@ -177,8 +177,8 @@ static struct option builtin_clone_options[] = {\n \n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n-\tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n-\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstatic const char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n+\tstatic const char *bundle_suffix[] = { \".bundle\", \"\" };\n \tsize_t baselen = path->len;\n \tstruct stat st;\n \tint i;\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..75c741173e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -113,7 +113,7 @@ static char *template_file;\n  * the commit message and/or authorship.\n  */\n static const char *author_message, *author_message_buffer;\n-static char *edit_message, *use_message;\n+static const char *edit_message, *use_message;\n static char *fixup_message, *fixup_commit, *squash_message;\n static const char *fixup_prefix;\n static int all, also, interactive, patch_interactive, only, amend, signoff;\n@@ -121,8 +121,8 @@ static int edit_flag = -1; /* unspecified */\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int config_commit_verbose = -1; /* unspecified */\n static int no_post_rewrite, allow_empty_message, pathspec_file_nul;\n-static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n-static char *sign_commit, *pathspec_from_file;\n+static const char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;\n+static const char *sign_commit, *pathspec_from_file;\n static struct strvec trailer_args = STRVEC_INIT;\n \n /*\ndiff --git a/builtin/diagnose.c b/builtin/diagnose.c\nindex 4f22eb2b55..4857a4395b 100644\n--- a/builtin/diagnose.c\n+++ b/builtin/diagnose.c\n@@ -18,7 +18,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix)\n \tstruct tm tm;\n \tenum diagnose_mode mode = DIAGNOSE_STATS;\n \tchar *option_output = NULL;\n-\tchar *option_suffix = \"%Y-%m-%d-%H%M\";\n+\tconst char *option_suffix = \"%Y-%m-%d-%H%M\";\n \tchar *prefixed_filename;\n \n \tconst struct option diagnose_options[] = {\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..b8846a9458 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1283,7 +1283,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)\n \to2->flags = flags2;\n }\n \n-static void gen_message_id(struct rev_info *info, char *base)\n+static void gen_message_id(struct rev_info *info, const char *base)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addf(&buf, \"%s.%\"PRItime\".git.%s\", base,\ndiff --git a/builtin/mailsplit.c b/builtin/mailsplit.c\nindex 3af9ddb8ae..fe6dbc5d05 100644\n--- a/builtin/mailsplit.c\n+++ b/builtin/mailsplit.c\n@@ -113,8 +113,8 @@ static int populate_maildir_list(struct string_list *list, const char *path)\n \tDIR *dir;\n \tstruct dirent *dent;\n \tchar *name = NULL;\n-\tchar *subs[] = { \"cur\", \"new\", NULL };\n-\tchar **sub;\n+\tconst char *subs[] = { \"cur\", \"new\", NULL };\n+\tconst char **sub;\n \tint ret = -1;\n \n \tfor (sub = subs; *sub; ++sub) {\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d622202bce..2d0429f14f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -71,48 +71,48 @@ static const char * const pull_usage[] = {\n \n /* Shared options */\n static int opt_verbosity;\n-static char *opt_progress;\n+static const char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n static enum rebase_type opt_rebase = -1;\n-static char *opt_diffstat;\n-static char *opt_log;\n-static char *opt_signoff;\n-static char *opt_squash;\n-static char *opt_commit;\n-static char *opt_edit;\n-static char *cleanup_arg;\n-static char *opt_ff;\n-static char *opt_verify_signatures;\n-static char *opt_verify;\n+static const char *opt_diffstat;\n+static const char *opt_log;\n+static const char *opt_signoff;\n+static const char *opt_squash;\n+static const char *opt_commit;\n+static const char *opt_edit;\n+static const char *cleanup_arg;\n+static const char *opt_ff;\n+static const char *opt_verify_signatures;\n+static const char *opt_verify;\n static int opt_autostash = -1;\n static int config_autostash;\n static int check_trust_level = 1;\n static struct strvec opt_strategies = STRVEC_INIT;\n static struct strvec opt_strategy_opts = STRVEC_INIT;\n-static char *opt_gpg_sign;\n+static const char *opt_gpg_sign;\n static int opt_allow_unrelated_histories;\n \n /* Options passed to git-fetch */\n-static char *opt_all;\n-static char *opt_append;\n-static char *opt_upload_pack;\n+static const char *opt_all;\n+static const char *opt_append;\n+static const char *opt_upload_pack;\n static int opt_force;\n-static char *opt_tags;\n-static char *opt_prune;\n-static char *max_children;\n+static const char *opt_tags;\n+static const char *opt_prune;\n+static const char *max_children;\n static int opt_dry_run;\n-static char *opt_keep;\n-static char *opt_depth;\n-static char *opt_unshallow;\n-static char *opt_update_shallow;\n-static char *opt_refmap;\n-static char *opt_ipv4;\n-static char *opt_ipv6;\n+static const char *opt_keep;\n+static const char *opt_depth;\n+static const char *opt_unshallow;\n+static const char *opt_update_shallow;\n+static const char *opt_refmap;\n+static const char *opt_ipv4;\n+static const char *opt_ipv6;\n static int opt_show_forced_updates = -1;\n-static char *set_upstream;\n+static const char *set_upstream;\n static struct strvec opt_fetch = STRVEC_INIT;\n \n static struct option pull_options[] = {\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 01c1f04ece..c8d12ee0a7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1249,7 +1249,7 @@ static int run_proc_receive_hook(struct command *commands,\n \treturn code;\n }\n \n-static char *refuse_unconfigured_deny_msg =\n+static const char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n \t   \"with what you pushed, and will require 'git reset --hard' to match\\n\"\n@@ -1269,7 +1269,7 @@ static void refuse_unconfigured_deny(void)\n \trp_error(\"%s\", _(refuse_unconfigured_deny_msg));\n }\n \n-static char *refuse_unconfigured_deny_delete_current_msg =\n+static const char *refuse_unconfigured_deny_delete_current_msg =\n \tN_(\"By default, deleting the current branch is denied, because the next\\n\"\n \t   \"'git clone' won't result in any file checked out, causing confusion.\\n\"\n \t   \"\\n\"\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 53935d2c68..7bf2b4e11d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -179,7 +179,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix,\n \n \t/* Check for incompatible command line arguments */\n \tif (cmd) {\n-\t\tchar *this_operation;\n+\t\tconst char *this_operation;\n \t\tif (cmd == 'q')\n \t\t\tthis_operation = \"--quit\";\n \t\telse if (cmd == 'c')\ndiff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c\nindex 2bc0f1187a..6c5d455e92 100644\n--- a/compat/regex/regcomp.c\n+++ b/compat/regex/regcomp.c\n@@ -848,7 +848,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)\n {\n   unsigned int table_size;\n #ifndef _LIBC\n-  char *codeset_name;\n+  const char *codeset_name;\n #endif\n \n   memset (dfa, '\\0', sizeof (re_dfa_t));\ndiff --git a/diff.c b/diff.c\nindex e70301df76..ffd867ef6c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3764,7 +3764,7 @@ static void builtin_diff(const char *name_a,\n \treturn;\n }\n \n-static char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n+static const char *get_compact_summary(const struct diff_filepair *p, int is_renamed)\n {\n \tif (!is_renamed) {\n \t\tif (p->status == DIFF_STATUS_ADDED) {\n@@ -4076,7 +4076,7 @@ static int reuse_worktree_file(struct index_state *istate,\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tchar *dirty = \"\";\n+\tconst char *dirty = \"\";\n \n \t/* Are we looking at the work tree? */\n \tif (s->dirty_submodule)\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 5a6e2bcac7..0e1adb87df 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -406,7 +406,7 @@ static const char *get_highest_rename_path(struct strintmap *counts)\n \treturn highest_destination_dir;\n }\n \n-static char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n+static const char *UNKNOWN_DIR = \"/\";  /* placeholder -- short, illegal directory */\n \n static int dir_rename_already_determinable(struct strintmap *counts)\n {\n@@ -429,8 +429,8 @@ static int dir_rename_already_determinable(struct strintmap *counts)\n }\n \n static void increment_count(struct dir_rename_info *info,\n-\t\t\t    char *old_dir,\n-\t\t\t    char *new_dir)\n+\t\t\t    const char *old_dir,\n+\t\t\t    const char *new_dir)\n {\n \tstruct strintmap *counts;\n \tstruct strmap_entry *e;\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 7d144b803a..5af63ab5ab 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -447,7 +447,7 @@ static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n \tint i = 0;\n-\tchar *sep = \"\";\n+\tconst char *sep = \"\";\n \n \tstrbuf_addstr(out, \"Merge \");\n \tfor (i = 0; i < srcs.nr; i++) {\ndiff --git a/fsck.c b/fsck.c\nindex 7dff41413e..61cd48aa25 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1231,7 +1231,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n }\n \n int fsck_buffer(const struct object_id *oid, enum object_type type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options)\n {\n \tif (type == OBJ_BLOB)\ndiff --git a/fsck.h b/fsck.h\nindex 17fa2dda5d..4f0c4e6479 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -202,7 +202,7 @@ int fsck_object(struct object *obj, void *data, unsigned long size,\n  * struct.\n  */\n int fsck_buffer(const struct object_id *oid, enum object_type,\n-\t\tvoid *data, unsigned long size,\n+\t\tconst void *data, unsigned long size,\n \t\tstruct fsck_options *options);\n \n /*\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 5193223714..71a9382a61 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -727,7 +727,7 @@ static int git_gpg_config(const char *var, const char *value,\n \t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n-\tchar *fmtname = NULL;\n+\tconst char *fmtname = NULL;\n \tchar *trust;\n \tint ret;\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 5b65287ac9..5b4dca65ed 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -753,7 +753,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)\n \n int cmd_main(int argc UNUSED, const char **argv UNUSED)\n {\n-\tchar *method = getenv(\"REQUEST_METHOD\");\n+\tconst char *method = getenv(\"REQUEST_METHOD\");\n \tconst char *proto_header;\n \tchar *dir;\n \tstruct service_cmd *cmd = NULL;\ndiff --git a/imap-send.c b/imap-send.c\nindex a5d1510180..8b723b34a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1215,9 +1215,9 @@ static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)\n static void wrap_in_html(struct strbuf *msg)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstatic char *content_type = \"Content-Type: text/html;\\n\";\n-\tstatic char *pre_open = \"<pre>\\n\";\n-\tstatic char *pre_close = \"</pre>\\n\";\n+\tstatic const char *content_type = \"Content-Type: text/html;\\n\";\n+\tstatic const char *pre_open = \"<pre>\\n\";\n+\tstatic const char *pre_close = \"</pre>\\n\";\n \tconst char *body = strstr(msg->buf, \"\\n\\n\");\n \n \tif (!body)\ndiff --git a/pretty.c b/pretty.c\nindex 22a81506b7..ec05db5655 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1325,7 +1325,7 @@ int format_set_trailers_options(struct process_trailer_options *opts,\n static size_t parse_describe_args(const char *start, struct strvec *args)\n {\n \tstruct {\n-\t\tchar *name;\n+\t\tconst char *name;\n \t\tenum {\n \t\t\tDESCRIBE_ARG_BOOL,\n \t\t\tDESCRIBE_ARG_INTEGER,\ndiff --git a/refs.c b/refs.c\nindex 8260c27cde..292e8d947e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -159,7 +159,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref)\n {\n \tstruct ref_namespace_info *info = &ref_namespace[namespace];\n \tif (info->ref_updated)\n-\t\tfree(info->ref);\n+\t\tfree((char *)info->ref);\n \tinfo->ref = ref;\n \tinfo->ref_updated = 1;\n }\ndiff --git a/refs.h b/refs.h\nindex 34568ee1fb..923f751d18 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -975,7 +975,7 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt);\n  */\n \n struct ref_namespace_info {\n-\tchar *ref;\n+\tconst char *ref;\n \tenum decoration_type decoration;\n \n \t/*\ndiff --git a/reftable/basics.c b/reftable/basics.c\nindex fea711db7e..0058619ca6 100644\n--- a/reftable/basics.c\n+++ b/reftable/basics.c\n@@ -67,9 +67,9 @@ void free_names(char **a)\n \treftable_free(a);\n }\n \n-size_t names_length(char **names)\n+size_t names_length(const char **names)\n {\n-\tchar **p = names;\n+\tconst char **p = names;\n \twhile (*p)\n \t\tp++;\n \treturn p - names;\n@@ -102,15 +102,12 @@ void parse_names(char *buf, int size, char ***namesp)\n \t*namesp = names;\n }\n \n-int names_equal(char **a, char **b)\n+int names_equal(const char **a, const char **b)\n {\n-\tint i = 0;\n-\tfor (; a[i] && b[i]; i++) {\n-\t\tif (strcmp(a[i], b[i])) {\n+\tsize_t i = 0;\n+\tfor (; a[i] && b[i]; i++)\n+\t\tif (strcmp(a[i], b[i]))\n \t\t\treturn 0;\n-\t\t}\n-\t}\n-\n \treturn a[i] == b[i];\n }\n \ndiff --git a/reftable/basics.h b/reftable/basics.h\nindex 523ecd5307..c8fec68d4e 100644\n--- a/reftable/basics.h\n+++ b/reftable/basics.h\n@@ -42,10 +42,10 @@ void free_names(char **a);\n void parse_names(char *buf, int size, char ***namesp);\n \n /* compares two NULL-terminated arrays of strings. */\n-int names_equal(char **a, char **b);\n+int names_equal(const char **a, const char **b);\n \n /* returns the array size of a NULL-terminated array of strings. */\n-size_t names_length(char **names);\n+size_t names_length(const char **names);\n \n /* Allocation routines; they invoke the functions set through\n  * reftable_set_alloc() */\ndiff --git a/reftable/basics_test.c b/reftable/basics_test.c\nindex 997c4d9e01..13bc761817 100644\n--- a/reftable/basics_test.c\n+++ b/reftable/basics_test.c\n@@ -58,8 +58,8 @@ static void test_binsearch(void)\n \n static void test_names_length(void)\n {\n-\tchar *a[] = { \"a\", \"b\", NULL };\n-\tEXPECT(names_length(a) == 2);\n+\tconst char *names[] = { \"a\", \"b\", NULL };\n+\tEXPECT(names_length(names) == 2);\n }\n \n static void test_parse_names_normal(void)\ndiff --git a/reftable/record.c b/reftable/record.c\nindex 5506f3e913..a2cba5ef74 100644\n--- a/reftable/record.c\n+++ b/reftable/record.c\n@@ -116,7 +116,7 @@ static int decode_string(struct strbuf *dest, struct string_view in)\n \treturn start_len - in.len;\n }\n \n-static int encode_string(char *str, struct string_view s)\n+static int encode_string(const char *str, struct string_view s)\n {\n \tstruct string_view start = s;\n \tint l = strlen(str);\n@@ -969,9 +969,9 @@ static int reftable_log_record_decode(void *rec, struct strbuf key,\n \treturn REFTABLE_FORMAT_ERROR;\n }\n \n-static int null_streq(char *a, char *b)\n+static int null_streq(const char *a, const char *b)\n {\n-\tchar *empty = \"\";\n+\tconst char *empty = \"\";\n \tif (!a)\n \t\ta = empty;\n \ndiff --git a/reftable/stack.c b/reftable/stack.c\nindex a59ebe038d..09549c51c9 100644\n--- a/reftable/stack.c\n+++ b/reftable/stack.c\n@@ -204,7 +204,8 @@ static struct reftable_reader **stack_copy_readers(struct reftable_stack *st,\n \treturn cur;\n }\n \n-static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n+static int reftable_stack_reload_once(struct reftable_stack *st,\n+\t\t\t\t      const char **names,\n \t\t\t\t      int reuse_open)\n {\n \tsize_t cur_len = !st->merged ? 0 : st->merged->stack_len;\n@@ -222,7 +223,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names,\n \n \twhile (*names) {\n \t\tstruct reftable_reader *rd = NULL;\n-\t\tchar *name = *names++;\n+\t\tconst char *name = *names++;\n \n \t\t/* this is linear; we assume compaction keeps the number of\n \t\t   tables under control so this is not quadratic. */\n@@ -354,7 +355,7 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\t\t\tgoto out;\n \t\t}\n \n-\t\terr = reftable_stack_reload_once(st, names, reuse_open);\n+\t\terr = reftable_stack_reload_once(st, (const char **) names, reuse_open);\n \t\tif (!err)\n \t\t\tbreak;\n \t\tif (err != REFTABLE_NOT_EXIST_ERROR)\n@@ -368,7 +369,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,\n \t\terr = read_lines(st->list_file, &names_after);\n \t\tif (err < 0)\n \t\t\tgoto out;\n-\t\tif (names_equal(names_after, names)) {\n+\t\tif (names_equal((const char **) names_after,\n+\t\t\t\t(const char **) names)) {\n \t\t\terr = REFTABLE_NOT_EXIST_ERROR;\n \t\t\tgoto out;\n \t\t}\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 7889f818d1..07d89b45da 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -83,7 +83,7 @@ static void test_read_file(void)\n \tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n \tint n, err;\n \tchar **names = NULL;\n-\tchar *want[] = { \"line1\", \"line2\", \"line3\" };\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n \tint i = 0;\n \n \tEXPECT(fd > 0);\n@@ -116,9 +116,9 @@ static void test_parse_names(void)\n \n static void test_names_equal(void)\n {\n-\tchar *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tchar *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tchar *c[] = { \"a\", \"b\", NULL };\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n \n \tEXPECT(names_equal(a, a));\n \tEXPECT(!names_equal(a, b));\ndiff --git a/run-command.c b/run-command.c\nindex 1b821042b4..7600531fb6 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -663,7 +663,7 @@ int start_command(struct child_process *cmd)\n \tint need_in, need_out, need_err;\n \tint fdin[2], fdout[2], fderr[2];\n \tint failed_errno;\n-\tchar *str;\n+\tconst char *str;\n \n \t/*\n \t * In case of errors we must keep the promise to close FDs\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 0eb0b3d49c..2912899558 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -36,7 +36,8 @@ static int test_entry_cmp(const void *cmp_data,\n }\n \n static struct test_entry *alloc_test_entry(unsigned int hash,\n-\t\t\t\t\t   char *key, char *value)\n+\t\t\t\t\t   const char *key,\n+\t\t\t\t\t   const char *value)\n {\n \tsize_t klen = strlen(key);\n \tsize_t vlen = strlen(value);\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nindex afe393f597..ed52eb76bf 100644\n--- a/t/helper/test-json-writer.c\n+++ b/t/helper/test-json-writer.c\n@@ -174,7 +174,7 @@ static void make_arr4(int pretty)\n \tjw_end(&arr4);\n }\n \n-static char *expect_nest1 =\n+static const char *expect_nest1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n static struct json_writer nest1 = JSON_WRITER_INIT;\n@@ -195,10 +195,10 @@ static void make_nest1(int pretty)\n \tjw_release(&arr1);\n }\n \n-static char *expect_inline1 =\n+static const char *expect_inline1 =\n \t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n \n-static char *pretty_inline1 =\n+static const char *pretty_inline1 =\n \t(\"{\\n\"\n \t \"  \\\"obj1\\\": {\\n\"\n \t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n@@ -236,10 +236,10 @@ static void make_inline1(int pretty)\n \tjw_end(&inline1);\n }\n \n-static char *expect_inline2 =\n+static const char *expect_inline2 =\n \t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n \n-static char *pretty_inline2 =\n+static const char *pretty_inline2 =\n \t(\"[\\n\"\n \t \"  [\\n\"\n \t \"    1,\\n\"\ndiff --git a/t/helper/test-regex.c b/t/helper/test-regex.c\nindex 80042eafc2..366bd70976 100644\n--- a/t/helper/test-regex.c\n+++ b/t/helper/test-regex.c\n@@ -20,8 +20,8 @@ static struct reg_flag reg_flags[] = {\n \n static int test_regex_bug(void)\n {\n-\tchar *pat = \"[^={} \\t]+\";\n-\tchar *str = \"={}\\nfred\";\n+\tconst char *pat = \"[^={} \\t]+\";\n+\tconst char *str = \"={}\\nfred\";\n \tregex_t r;\n \tregmatch_t m[1];\n \ndiff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c\nindex f8d564c622..7e1d9e0ee4 100644\n--- a/t/helper/test-rot13-filter.c\n+++ b/t/helper/test-rot13-filter.c\n@@ -136,7 +136,7 @@ static void free_delay_entries(void)\n \tstrmap_clear(&delay, 0);\n }\n \n-static void add_delay_entry(char *pathname, int count, int requested)\n+static void add_delay_entry(const char *pathname, int count, int requested)\n {\n \tstruct delay_entry *entry = xcalloc(1, sizeof(*entry));\n \tentry->count = count;\n@@ -189,7 +189,8 @@ static void reply_list_available_blobs_cmd(void)\n static void command_loop(void)\n {\n \tfor (;;) {\n-\t\tchar *buf, *output;\n+\t\tchar *buf;\n+\t\tconst char *output;\n \t\tchar *pathname;\n \t\tstruct delay_entry *entry;\n \t\tstruct strbuf input = STRBUF_INIT;\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex de434a4441..6027dafef7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -2,7 +2,8 @@\n #include \"strbuf.h\"\n \n /* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, void*), void *data)\n+static void setup(void (*f)(struct strbuf*, const void*),\n+\t\t  const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -13,7 +14,8 @@ static void setup(void (*f)(struct strbuf*, void*), void *data)\n }\n \n /* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data)\n+static void setup_populated(void (*f)(struct strbuf*, const void*),\n+\t\t\t    const char *init_str, const void *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -64,7 +66,7 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n \n-static void t_addch(struct strbuf *buf, void *data)\n+static void t_addch(struct strbuf *buf, const void *data)\n {\n \tconst char *p_ch = data;\n \tconst char ch = *p_ch;\n@@ -83,7 +85,7 @@ static void t_addch(struct strbuf *buf, void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n \n-static void t_addstr(struct strbuf *buf, void *data)\n+static void t_addstr(struct strbuf *buf, const void *data)\n {\n \tconst char *text = data;\n \tsize_t len = strlen(text);\ndiff --git a/trailer.c b/trailer.c\nindex 2bcb9ba8f7..72e5136c73 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -63,7 +63,7 @@ struct arg_item {\n \n static LIST_HEAD(conf_head);\n \n-static char *separators = \":\";\n+static const char *separators = \":\";\n \n static int configured;\n \ndiff --git a/wt-status.c b/wt-status.c\nindex ff4be071ca..7912545e4e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -2408,7 +2408,7 @@ static void wt_porcelain_v2_print_unmerged_entry(\n \t\tint mode;\n \t\tstruct object_id oid;\n \t} stages[3];\n-\tchar *key;\n+\tconst char *key;\n \tchar submodule_token[5];\n \tchar unmerged_prefix = 'u';\n \tchar eol_char = s->null_termination ? '\\0' : '\\n';\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496602","messageId":"4769bdb89393c70f2a42847e233fd2d89a90ebd6.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 02/27] global: convert intentionally-leaking config strings to consts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:43Z","receivedAt":"2024-06-07T06:37:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are multiple cases where we intentionally leak config strings:\n\n  - `struct gpg_format` is used to track programs that can be used for\n    signing commits, either via gpg(1), gpgsm(1) or ssh-keygen(1). The\n    user can override the commands via several config variables. As the\n    array is populated once, only, and the struct memers are never\n    written to or free'd.\n\n  - `struct ll_merge_driver` is used to track merge drivers. Same as\n    with the GPG format, these drivers are populated once and then\n    reused. Its data is never written to or free'd, either.\n\n  - `struct userdiff_funcname` and `struct userdiff_driver` can be\n    configured via `diff.<driver>.*` to add additional drivers. Again,\n    these have a global lifetime and are never written to or free'd.\n\nAll of these are intentionally kept alive and are never written to.\nFurthermore, all of these are being assigned both string constants in\nsome places, and allocated strings in other places. This will cause\nwarnings once we enable `-Wwrite-strings`, so let's mark the respective\nfields as `const char *` and cast away the constness when assigning\nthose values.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n gpg-interface.c |  4 ++--\n merge-ll.c      | 11 ++++++++---\n userdiff.c      | 10 +++++-----\n userdiff.h      | 12 ++++++------\n 4 files changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 71a9382a61..5c824aeb25 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -34,7 +34,7 @@ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;\n \n struct gpg_format {\n \tconst char *name;\n-\tchar *program;\n+\tconst char *program;\n \tconst char **verify_args;\n \tconst char **sigs;\n \tint (*verify_signed_buffer)(struct signature_check *sigc,\n@@ -783,7 +783,7 @@ static int git_gpg_config(const char *var, const char *value,\n \n \tif (fmtname) {\n \t\tfmt = get_format_by_name(fmtname);\n-\t\treturn git_config_string(&fmt->program, var, value);\n+\t\treturn git_config_string((char **) &fmt->program, var, value);\n \t}\n \n \treturn 0;\ndiff --git a/merge-ll.c b/merge-ll.c\nindex e29b15fa4a..180c19df67 100644\n--- a/merge-ll.c\n+++ b/merge-ll.c\n@@ -27,7 +27,7 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,\n \n struct ll_merge_driver {\n \tconst char *name;\n-\tchar *description;\n+\tconst char *description;\n \tll_merge_fn fn;\n \tchar *recursive;\n \tstruct ll_merge_driver *next;\n@@ -304,8 +304,13 @@ static int read_merge_config(const char *var, const char *value,\n \t\tll_user_merge_tail = &(fn->next);\n \t}\n \n-\tif (!strcmp(\"name\", key))\n-\t\treturn git_config_string(&fn->description, var, value);\n+\tif (!strcmp(\"name\", key)) {\n+\t\t/*\n+\t\t * The description is leaking, but that's okay as we want to\n+\t\t * keep around the merge drivers anyway.\n+\t\t */\n+\t\treturn git_config_string((char **) &fn->description, var, value);\n+\t}\n \n \tif (!strcmp(\"driver\", key)) {\n \t\tif (!value)\ndiff --git a/userdiff.c b/userdiff.c\nindex 82bc76b910..371032a413 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -399,7 +399,7 @@ static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t\n static int parse_funcname(struct userdiff_funcname *f, const char *k,\n \t\tconst char *v, int cflags)\n {\n-\tif (git_config_string(&f->pattern, k, v) < 0)\n+\tif (git_config_string((char **) &f->pattern, k, v) < 0)\n \t\treturn -1;\n \tf->cflags = cflags;\n \treturn 0;\n@@ -445,15 +445,15 @@ int userdiff_config(const char *k, const char *v)\n \tif (!strcmp(type, \"binary\"))\n \t\treturn parse_tristate(&drv->binary, k, v);\n \tif (!strcmp(type, \"command\"))\n-\t\treturn git_config_string(&drv->external, k, v);\n+\t\treturn git_config_string((char **) &drv->external, k, v);\n \tif (!strcmp(type, \"textconv\"))\n-\t\treturn git_config_string(&drv->textconv, k, v);\n+\t\treturn git_config_string((char **) &drv->textconv, k, v);\n \tif (!strcmp(type, \"cachetextconv\"))\n \t\treturn parse_bool(&drv->textconv_want_cache, k, v);\n \tif (!strcmp(type, \"wordregex\"))\n-\t\treturn git_config_string(&drv->word_regex, k, v);\n+\t\treturn git_config_string((char **) &drv->word_regex, k, v);\n \tif (!strcmp(type, \"algorithm\"))\n-\t\treturn git_config_string(&drv->algorithm, k, v);\n+\t\treturn git_config_string((char **) &drv->algorithm, k, v);\n \n \treturn 0;\n }\ndiff --git a/userdiff.h b/userdiff.h\nindex cc8e5abfef..d726804c3e 100644\n--- a/userdiff.h\n+++ b/userdiff.h\n@@ -7,19 +7,19 @@ struct index_state;\n struct repository;\n \n struct userdiff_funcname {\n-\tchar *pattern;\n+\tconst char *pattern;\n \tint cflags;\n };\n \n struct userdiff_driver {\n \tconst char *name;\n-\tchar *external;\n-\tchar *algorithm;\n+\tconst char *external;\n+\tconst char *algorithm;\n \tint binary;\n \tstruct userdiff_funcname funcname;\n-\tchar *word_regex;\n-\tchar *word_regex_multi_byte;\n-\tchar *textconv;\n+\tconst char *word_regex;\n+\tconst char *word_regex_multi_byte;\n+\tconst char *textconv;\n \tstruct notes_cache *textconv_cache;\n \tint textconv_want_cache;\n };\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496603","messageId":"90613bd58285553a406d447fa41d4b86cacb605b.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 03/27] refs/reftable: stop micro-optimizing refname allocations on copy","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:48Z","receivedAt":"2024-06-07T06:37:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When copying refs, we execute `write_copy_table()` to write the new\ntable. As the names are given to us via `arg->newname` and\n`arg->oldname`, respectively, we optimize away some allocations by\nassigning those fields to the reftable records we are about to write\ndirectly, without duplicating them. This requires us to cast the input\nto `char *` pointers as they are in fact constant strings. Later on, we\nthen unset the refname for all of the records before calling\n`reftable_log_record_release()` on them.\n\nWe also do this when assigning the \"HEAD\" constant, but here we do not\ncast because its type is `char[]` by default. It's about to be turned\ninto `const char *` though once we enable `-Wwrite-strings` and will\nthus cause another warning.\n\nIt's quite dubious whether this micro-optimization really helps. We're\nabout to write to disk anyway, which is going to be way slower than a\nsmall handful of allocations. Let's drop the optimization altogther and\ninstead copy arguments to simplify the code and avoid the future warning\nwith `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/reftable-backend.c | 28 ++++++++++++++++------------\n 1 file changed, 16 insertions(+), 12 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1af86bbdec..e77faa2b9d 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1340,10 +1340,10 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t * old reference.\n \t */\n \trefs[0] = old_ref;\n-\trefs[0].refname = (char *)arg->newname;\n+\trefs[0].refname = xstrdup(arg->newname);\n \trefs[0].update_index = creation_ts;\n \tif (arg->delete_old) {\n-\t\trefs[1].refname = (char *)arg->oldname;\n+\t\trefs[1].refname = xstrdup(arg->oldname);\n \t\trefs[1].value_type = REFTABLE_REF_DELETION;\n \t\trefs[1].update_index = deletion_ts;\n \t}\n@@ -1366,7 +1366,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \t\tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs[logs_nr].update_index = deletion_ts;\n \t\tlogs[logs_nr].value.update.message =\n \t\t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1387,7 +1387,13 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (append_head_reflog) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlogs[logs_nr] = logs[logs_nr - 1];\n-\t\t\tlogs[logs_nr].refname = \"HEAD\";\n+\t\t\tlogs[logs_nr].refname = xstrdup(\"HEAD\");\n+\t\t\tlogs[logs_nr].value.update.name =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.name);\n+\t\t\tlogs[logs_nr].value.update.email =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.email);\n+\t\t\tlogs[logs_nr].value.update.message =\n+\t\t\t\txstrdup(logs[logs_nr].value.update.message);\n \t\t\tlogs_nr++;\n \t\t}\n \t}\n@@ -1398,7 +1404,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n \tfill_reftable_log_record(&logs[logs_nr], &committer_ident);\n-\tlogs[logs_nr].refname = (char *)arg->newname;\n+\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \tlogs[logs_nr].update_index = creation_ts;\n \tlogs[logs_nr].value.update.message =\n \t\txstrndup(arg->logmsg, arg->refs->write_options.block_size / 2);\n@@ -1430,7 +1436,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\t */\n \t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\tlogs[logs_nr] = old_log;\n-\t\tlogs[logs_nr].refname = (char *)arg->newname;\n+\t\tlogs[logs_nr].refname = xstrdup(arg->newname);\n \t\tlogs_nr++;\n \n \t\t/*\n@@ -1439,7 +1445,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \t\tif (arg->delete_old) {\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tmemset(&logs[logs_nr], 0, sizeof(logs[logs_nr]));\n-\t\t\tlogs[logs_nr].refname = (char *)arg->oldname;\n+\t\t\tlogs[logs_nr].refname = xstrdup(arg->oldname);\n \t\t\tlogs[logs_nr].value_type = REFTABLE_LOG_DELETION;\n \t\t\tlogs[logs_nr].update_index = old_log.update_index;\n \t\t\tlogs_nr++;\n@@ -1462,13 +1468,11 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data)\n \treftable_iterator_destroy(&it);\n \tstring_list_clear(&skip, 0);\n \tstrbuf_release(&errbuf);\n-\tfor (i = 0; i < logs_nr; i++) {\n-\t\tif (!strcmp(logs[i].refname, \"HEAD\"))\n-\t\t\tcontinue;\n-\t\tlogs[i].refname = NULL;\n+\tfor (i = 0; i < logs_nr; i++)\n \t\treftable_log_record_release(&logs[i]);\n-\t}\n \tfree(logs);\n+\tfor (i = 0; i < ARRAY_SIZE(refs); i++)\n+\t\treftable_ref_record_release(&refs[i]);\n \treftable_ref_record_release(&old_ref);\n \treftable_log_record_release(&old_log);\n \treturn ret;\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496604","messageId":"b126783ba34de98ab391eef37d16336117ca95c0.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 04/27] reftable: cast away constness when assigning constants to records","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:52Z","receivedAt":"2024-06-07T06:37:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reftable records are used in multiple ways throughout the reftable\nlibrary. In many of those cases they merely act as input to a function\nwithout getting modified by it at all. Most importantly, this happens\nwhen writing records and when querying for records.\n\nWe rely on this in our tests and thus assign string constants to those\nfields, which is about to generate warnings as those fields are of type\n`char *`. While we could go through the process and instead allocate\nthose strings in all of our tests, this feels quite unnecessary.\n\nInstead, add casts to `char *` for all of those strings. As this is part\nof our tests, this also nicely serves as a demonstration that nothing\nwrites or frees those string constants, which would otherwise lead to\nsegfaults.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n reftable/block_test.c     |  2 +-\n reftable/merged_test.c    | 44 +++++++++++++++++------------------\n reftable/readwrite_test.c | 32 +++++++++++++-------------\n reftable/stack_test.c     | 48 +++++++++++++++++++--------------------\n 4 files changed, 63 insertions(+), 63 deletions(-)\n\ndiff --git a/reftable/block_test.c b/reftable/block_test.c\nindex 26a9cfbc83..90aecd5a7c 100644\n--- a/reftable/block_test.c\n+++ b/reftable/block_test.c\n@@ -42,7 +42,7 @@ static void test_block_read_write(void)\n \tblock_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n \t\t\t  header_off, hash_size(GIT_SHA1_FORMAT_ID));\n \n-\trec.u.ref.refname = \"\";\n+\trec.u.ref.refname = (char *) \"\";\n \trec.u.ref.value_type = REFTABLE_REF_DELETION;\n \tn = block_writer_add(&bw, &rec);\n \tEXPECT(n == REFTABLE_API_ERROR);\ndiff --git a/reftable/merged_test.c b/reftable/merged_test.c\nindex 530fc82d1c..6d1159d12d 100644\n--- a/reftable/merged_test.c\n+++ b/reftable/merged_test.c\n@@ -124,13 +124,13 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)\n static void test_merged_between(void)\n {\n \tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = \"b\",\n+\t\t.refname = (char *) \"b\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = { 1, 2, 3, 0 },\n \t} };\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n@@ -165,38 +165,38 @@ static void test_merged(void)\n {\n \tstruct reftable_ref_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n \t\t}\n \t};\n \tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = \"a\",\n+\t\t.refname = (char *) \"a\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t} };\n \tstruct reftable_ref_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"c\",\n+\t\t\t.refname = (char *) \"c\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 2 },\n \t\t},\n \t\t{\n-\t\t\t.refname = \"d\",\n+\t\t\t.refname = (char *) \"d\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_REF_VAL1,\n \t\t\t.value.val1 = { 1 },\n@@ -291,46 +291,46 @@ static void test_merged_logs(void)\n {\n \tstruct reftable_log_record r1[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 2 },\n \t\t\t\t/* deletion */\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message2\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n \t\t\t}\n \t\t},\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message1\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r2[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 3,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value.update = {\n \t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = \"jane doe\",\n-\t\t\t\t.email = \"jane@invalid\",\n-\t\t\t\t.message = \"message3\",\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n \t\t\t}\n \t\t},\n \t};\n \tstruct reftable_log_record r3[] = {\n \t\t{\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 2,\n \t\t\t.value_type = REFTABLE_LOG_DELETION,\n \t\t},\n@@ -406,7 +406,7 @@ static void test_default_write_opts(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \n \tstruct reftable_ref_record rec = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.update_index = 1,\n \t};\n \tint err;\ndiff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c\nindex a6dbd214c5..c55019232b 100644\n--- a/reftable/readwrite_test.c\n+++ b/reftable/readwrite_test.c\n@@ -86,7 +86,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,\n \t\tlog.update_index = update_index;\n \t\tlog.value_type = REFTABLE_LOG_UPDATE;\n \t\tset_test_hash(log.value.update.new_hash, i);\n-\t\tlog.value.update.message = \"message\";\n+\t\tlog.value.update.message = (char *) \"message\";\n \n \t\tn = reftable_writer_add_log(w, &log);\n \t\tEXPECT(n == 0);\n@@ -118,15 +118,15 @@ static void test_log_buffer_size(void)\n \tint err;\n \tint i;\n \tstruct reftable_log_record\n-\t\tlog = { .refname = \"refs/heads/master\",\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n \t\t\t.update_index = 0xa,\n \t\t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = \"hanwen@google.com\",\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n \t\t\t\t\t   .tz_offset = 100,\n \t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = \"commit: 9\\n\",\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n \t\t\t\t   } } };\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n@@ -156,15 +156,15 @@ static void test_log_overflow(void)\n \t};\n \tint err;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 0xa,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.old_hash = { 1 },\n \t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = \"hanwen@google.com\",\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n \t\t\t\t.tz_offset = 100,\n \t\t\t\t.time = 0x5e430672,\n \t\t\t\t.message = msg,\n@@ -293,14 +293,14 @@ static void test_log_zlib_corruption(void)\n \tchar message[100] = { 0 };\n \tint err, i, n;\n \tstruct reftable_log_record log = {\n-\t\t.refname = \"refname\",\n+\t\t.refname = (char *) \"refname\",\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n \t\t\t.update = {\n \t\t\t\t.new_hash = { 1 },\n \t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = \"My Name\",\n-\t\t\t\t.email = \"myname@invalid\",\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n \t\t\t\t.message = message,\n \t\t\t},\n \t\t},\n@@ -728,7 +728,7 @@ static void test_write_empty_key(void)\n \tstruct reftable_writer *w =\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"\",\n+\t\t.refname = (char *) \"\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_DELETION,\n \t};\n@@ -752,18 +752,18 @@ static void test_write_key_order(void)\n \t\treftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);\n \tstruct reftable_ref_record refs[2] = {\n \t\t{\n-\t\t\t.refname = \"b\",\n+\t\t\t.refname = (char *) \"b\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}, {\n-\t\t\t.refname = \"a\",\n+\t\t\t.refname = (char *) \"a\",\n \t\t\t.update_index = 1,\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n \t\t\t.value = {\n-\t\t\t\t.symref = \"target\",\n+\t\t\t\t.symref = (char *) \"target\",\n \t\t\t},\n \t\t}\n \t};\ndiff --git a/reftable/stack_test.c b/reftable/stack_test.c\nindex 07d89b45da..4abf92636d 100644\n--- a/reftable/stack_test.c\n+++ b/reftable/stack_test.c\n@@ -156,10 +156,10 @@ static void test_reftable_stack_add_one(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \tstruct stat stat_result = { 0 };\n@@ -216,16 +216,16 @@ static void test_reftable_stack_uptodate(void)\n \n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"branch2\",\n+\t\t.refname = (char *) \"branch2\",\n \t\t.update_index = 2,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \n@@ -264,10 +264,10 @@ static void test_reftable_stack_transaction_api(void)\n \tstruct reftable_addition *add = NULL;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"HEAD\",\n+\t\t.refname = (char *) \"HEAD\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record dest = { NULL };\n \n@@ -313,7 +313,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tchar name[100];\n \n@@ -356,7 +356,7 @@ static void test_reftable_stack_transaction_api_performs_auto_compaction(void)\n static void test_reftable_stack_auto_compaction_fails_gracefully(void)\n {\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"refs/heads/master\",\n+\t\t.refname = (char *) \"refs/heads/master\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_VAL1,\n \t\t.value.val1 = {0x01},\n@@ -409,16 +409,16 @@ static void test_reftable_stack_update_index_check(void)\n \tstruct reftable_stack *st = NULL;\n \tint err;\n \tstruct reftable_ref_record ref1 = {\n-\t\t.refname = \"name1\",\n+\t\t.refname = (char *) \"name1\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \tstruct reftable_ref_record ref2 = {\n-\t\t.refname = \"name2\",\n+\t\t.refname = (char *) \"name2\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"master\",\n+\t\t.value.symref = (char *) \"master\",\n \t};\n \n \terr = reftable_new_stack(&st, dir, cfg);\n@@ -561,7 +561,7 @@ static void test_reftable_stack_log_normalize(void)\n \tstruct reftable_stack *st = NULL;\n \tchar *dir = get_tmp_dir(__LINE__);\n \tstruct reftable_log_record input = {\n-\t\t.refname = \"branch\",\n+\t\t.refname = (char *) \"branch\",\n \t\t.update_index = 1,\n \t\t.value_type = REFTABLE_LOG_UPDATE,\n \t\t.value = {\n@@ -582,11 +582,11 @@ static void test_reftable_stack_log_normalize(void)\n \terr = reftable_new_stack(&st, dir, cfg);\n \tEXPECT_ERR(err);\n \n-\tinput.value.update.message = \"one\\ntwo\";\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT(err == REFTABLE_API_ERROR);\n \n-\tinput.value.update.message = \"one\";\n+\tinput.value.update.message = (char *) \"one\";\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n \n@@ -594,7 +594,7 @@ static void test_reftable_stack_log_normalize(void)\n \tEXPECT_ERR(err);\n \tEXPECT(0 == strcmp(dest.value.update.message, \"one\\n\"));\n \n-\tinput.value.update.message = \"two\\n\";\n+\tinput.value.update.message = (char *) \"two\\n\";\n \targ.update_index = 2;\n \terr = reftable_stack_add(st, &write_test_log, &arg);\n \tEXPECT_ERR(err);\n@@ -697,9 +697,9 @@ static void test_reftable_stack_hash_id(void)\n \tint err;\n \n \tstruct reftable_ref_record ref = {\n-\t\t.refname = \"master\",\n+\t\t.refname = (char *) \"master\",\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = \"target\",\n+\t\t.value.symref = (char *) \"target\",\n \t\t.update_index = 1,\n \t};\n \tstruct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID };\n@@ -879,7 +879,7 @@ static void test_reftable_stack_auto_compaction(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -913,7 +913,7 @@ static void test_reftable_stack_add_performs_auto_compaction(void)\n \t\tstruct reftable_ref_record ref = {\n \t\t\t.update_index = reftable_stack_next_update_index(st),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \n \t\t/*\n@@ -964,7 +964,7 @@ static void test_reftable_stack_compaction_concurrent(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n@@ -1014,7 +1014,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void)\n \t\t\t.refname = name,\n \t\t\t.update_index = reftable_stack_next_update_index(st1),\n \t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = \"master\",\n+\t\t\t.value.symref = (char *) \"master\",\n \t\t};\n \t\tsnprintf(name, sizeof(name), \"branch%04d\", i);\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496605","messageId":"d10566e0a94c3580ab57bc228a0b6ca0371b3a12.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 05/27] refspec: remove global tag refspec structure","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:37:57Z","receivedAt":"2024-06-07T06:38:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a global tag refspec structure that is used by both git-clone(1)\nand git-fetch(1). Initialization of the structure will break once we\nenable `-Wwrite-strings`, even though the breakage is harmless. While we\ncould just add casts, the structure isn't really required in the first\nplace as we can simply initialize the structures at the respective\ncallsites.\n\nRefactor the code accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c |  8 ++++++--\n builtin/fetch.c | 11 ++++++++---\n refspec.c       | 13 -------------\n refspec.h       |  1 -\n 4 files changed, 14 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 92ab7d7165..bde1d284a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -523,6 +523,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *head = copy_ref(find_ref_by_name(refs, \"HEAD\"));\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n+\tstruct refspec_item tag_refspec;\n+\n+\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n \n \tif (option_single_branch) {\n \t\tstruct ref *remote_head = NULL;\n@@ -545,7 +548,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\t\t\t\t      &tail, 0);\n \n \t\t\t/* if --branch=tag, pull the requested tag explicitly */\n-\t\t\tget_fetch_map(remote_head, tag_refspec, &tail, 0);\n+\t\t\tget_fetch_map(remote_head, &tag_refspec, &tail, 0);\n \t\t}\n \t\tfree_refs(remote_head);\n \t} else {\n@@ -555,8 +558,9 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t}\n \n \tif (!option_mirror && !option_single_branch && !option_no_tags)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t\tget_fetch_map(refs, &tag_refspec, &tail, 0);\n \n+\trefspec_item_clear(&tag_refspec);\n \treturn local_refs;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 75255dc600..06b60867f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -582,11 +582,16 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \n-\tif (tags == TAGS_SET)\n+\tif (tags == TAGS_SET) {\n+\t\tstruct refspec_item tag_refspec;\n+\n \t\t/* also fetch all tags */\n-\t\tget_fetch_map(remote_refs, tag_refspec, &tail, 0);\n-\telse if (tags == TAGS_DEFAULT && *autotags)\n+\t\trefspec_item_init(&tag_refspec, TAG_REFSPEC, 0);\n+\t\tget_fetch_map(remote_refs, &tag_refspec, &tail, 0);\n+\t\trefspec_item_clear(&tag_refspec);\n+\t} else if (tags == TAGS_DEFAULT && *autotags) {\n \t\tfind_non_local_tags(remote_refs, NULL, &ref_map, &tail);\n+\t}\n \n \t/* Now append any refs to be updated opportunistically: */\n \t*tail = orefs;\ndiff --git a/refspec.c b/refspec.c\nindex d60932f4de..1df5de6c2f 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -7,19 +7,6 @@\n #include \"refspec.h\"\n #include \"strbuf.h\"\n \n-static struct refspec_item s_tag_refspec = {\n-\t.force = 0,\n-\t.pattern = 1,\n-\t.matching = 0,\n-\t.exact_sha1 = 0,\n-\t.negative = 0,\n-\t.src = \"refs/tags/*\",\n-\t.dst = \"refs/tags/*\",\n-};\n-\n-/* See TAG_REFSPEC for the string version */\n-const struct refspec_item *tag_refspec = &s_tag_refspec;\n-\n /*\n  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.\n  * Returns 1 if successful and 0 if the refspec is invalid.\ndiff --git a/refspec.h b/refspec.h\nindex 8c0c446993..754be45cee 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -2,7 +2,6 @@\n #define REFSPEC_H\n \n #define TAG_REFSPEC \"refs/tags/*:refs/tags/*\"\n-extern const struct refspec_item *tag_refspec;\n \n /**\n  * A struct refspec_item holds the parsed interpretation of a refspec.  If it\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496606","messageId":"e9b8f9eaef74769c7c6addcecb7c79c1530c31ff.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 06/27] builtin/remote: cast away constness in `get_head_names()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:02Z","receivedAt":"2024-06-07T06:38:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `get_head_names()`, we assign the \"refs/heads/*\" string constant to\n`struct refspec_item::{src,dst}`, which are both non-constant pointers.\nIdeally, we'd refactor the code such that both of these fields were\nconstant. But `struct refspec_item` is used for two different usecases\nwith conflicting requirements:\n\n  - To query for a source or destination based on the given refspec. The\n    caller either sets `src` or `dst` as the branch that we want to\n    search for, and the respective other field gets populated. The\n    fields should be constant when being used as a query parameter,\n    which is owned by the caller, and non-constant when being used as an\n    out parameter, which is owned by the refspec item. This is is\n    contradictory in itself already.\n\n  - To store refspec items with their respective source and destination\n    branches, in which case both fields should be owned by the struct.\n\nIdeally, we'd split up this interface to clearly separate between\nquerying and storing, which would enable us to clarify lifetimes of the\nstrings. This would be a much bigger undertaking though.\n\nInstead, accept the status quo for now and cast away the constness of\nthe source and destination patterns. We know that those are not being\nwritten to or freed, so while this is ugly it certainly is fine for now.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/remote.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d52b1c0e10..b44f580b8c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -493,12 +493,13 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n {\n \tstruct ref *ref, *matches;\n \tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n-\tstruct refspec_item refspec;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n \n-\tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.force = 0;\n-\trefspec.pattern = 1;\n-\trefspec.src = refspec.dst = \"refs/heads/*\";\n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n \tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n \t\t\t\t    fetch_map, 1);\n@@ -507,7 +508,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \n \tfree_refs(fetch_map);\n \tfree_refs(matches);\n-\n \treturn 0;\n }\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496607","messageId":"8ea984128d8aab8af08ba4f4be94452227672b4c.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 07/27] diff: cast string constant in `fill_textconv()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:06Z","receivedAt":"2024-06-07T06:38:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `fill_textconv()` function is responsible for converting an input\nfile with a textconv driver, which is then passed to the caller. Weirdly\nthough, the function also handles the case where there is no textconv\ndriver at all. In that case, it will return either the contents of the\npopulated filespec, or an empty string if the filespec is invalid.\n\nThese two cases have differing memory ownership semantics. When there is\na textconv driver, then the result is an allocated string. Otherwise,\nthe result is either a string constant or owned by the filespec struct.\nAll callers are in fact aware of this weirdness and only end up freeing\nthe output buffer when they had a textconv driver.\n\nIdeally, we'd split up this interface to only perform the conversion via\nthe textconv driver, and BUG in case the caller didn't provide one. This\nwould make memory ownership semantics much more straight forward. For\nnow though, let's simply cast the empty string constant to `char *` to\navoid a warning with `-Wwrite-strings`. This is equivalent to the same\ncast that we already have in `fill_mmfile()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n diff.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/diff.c b/diff.c\nindex ffd867ef6c..cecda216cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -7235,7 +7235,7 @@ size_t fill_textconv(struct repository *r,\n \n \tif (!driver) {\n \t\tif (!DIFF_FILE_VALID(df)) {\n-\t\t\t*outbuf = \"\";\n+\t\t\t*outbuf = (char *) \"\";\n \t\t\treturn 0;\n \t\t}\n \t\tif (diff_populate_filespec(r, df, NULL))\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496608","messageId":"777b93a89aff6db2422c257870225e43ce342eb9.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 08/27] line-log: stop assigning string constant to file parent buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:11Z","receivedAt":"2024-06-07T06:38:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Stop assigning a string constant to the file parent buffer and instead\nassign an allocated string. While the code is fine in practice, it will\nbreak once we compile with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 8ff6ccb772..bd3e663c24 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -1032,6 +1032,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \tstruct range_set tmp;\n \tstruct diff_ranges diff;\n \tmmfile_t file_parent, file_target;\n+\tchar *parent_data_to_free = NULL;\n \n \tassert(pair->two->path);\n \twhile (rg) {\n@@ -1056,7 +1057,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \t\tfile_parent.ptr = pair->one->data;\n \t\tfile_parent.size = pair->one->size;\n \t} else {\n-\t\tfile_parent.ptr = \"\";\n+\t\tfile_parent.ptr = parent_data_to_free = xstrdup(\"\");\n \t\tfile_parent.size = 0;\n \t}\n \n@@ -1075,6 +1076,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n \tdiff_ranges_release(&diff);\n \n+\tfree(parent_data_to_free);\n \treturn ((*diff_out)->parent.nr > 0);\n }\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496609","messageId":"527533540ebabc91b8b484d5caa3099511c4b7aa.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 09/27] line-log: always allocate the output prefix","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:16Z","receivedAt":"2024-06-07T06:38:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The returned string by `output_prefix()` is sometimes a string constant\nand sometimes an allocated string. This has been fine until now because\nwe always leak the allocated strings, and thus we never tried to free\nthe string constant.\n\nFix the code to always return an allocated string and free the returned\nvalue at all callsites.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-log.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex bd3e663c24..67c80b39a0 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -899,14 +899,12 @@ static void print_line(const char *prefix, char first,\n \n static char *output_prefix(struct diff_options *opt)\n {\n-\tchar *prefix = \"\";\n-\n \tif (opt->output_prefix) {\n \t\tstruct strbuf *sb = opt->output_prefix(opt, opt->output_prefix_data);\n-\t\tprefix = sb->buf;\n+\t\treturn sb->buf;\n+\t} else {\n+\t\treturn xstrdup(\"\");\n \t}\n-\n-\treturn prefix;\n }\n \n static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n@@ -927,7 +925,7 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n \n \tif (!pair || !diff)\n-\t\treturn;\n+\t\tgoto out;\n \n \tif (pair->one->oid_valid)\n \t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n@@ -1002,8 +1000,10 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n \t\t\t\t   c_context, c_reset, opt->file);\n \t}\n \n+out:\n \tfree(p_ends);\n \tfree(t_ends);\n+\tfree(prefix);\n }\n \n /*\n@@ -1012,7 +1012,11 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang\n  */\n static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n {\n-\tfprintf(rev->diffopt.file, \"%s\\n\", output_prefix(&rev->diffopt));\n+\tchar *prefix = output_prefix(&rev->diffopt);\n+\n+\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\tfree(prefix);\n+\n \twhile (range) {\n \t\tdump_diff_hacky_one(rev, range);\n \t\trange = range->next;\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496610","messageId":"4d0b8dcbaff0335a3020d2ee742b91393fc99e75.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 10/27] entry: refactor how we remove items for delayed checkouts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:20Z","receivedAt":"2024-06-07T06:38:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When finalizing a delayed checkout, we sort out several strings from the\npassed-in string list by first assigning the empty string to those\nfilters and then calling `string_list_remove_empty_items()`. Assigning\nthe empty string will cause compiler warnings though as the string is\na `char *` once we enable `-Wwrite-strings`.\n\nRefactor the code to use a `NULL` pointer with `filter_string_list()`\ninstead to avoid this warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n entry.c | 14 ++++++++++----\n 1 file changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex b8c257f6f9..f291d8eee6 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -167,6 +167,11 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)\n \treturn !available;\n }\n \n+static int string_is_not_null(struct string_list_item *item, void *data UNUSED)\n+{\n+\treturn !!item->string;\n+}\n+\n int finish_delayed_checkout(struct checkout *state, int show_progress)\n {\n \tint errs = 0;\n@@ -189,7 +194,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\tif (!async_query_available_blobs(filter->string, &available_paths)) {\n \t\t\t\t/* Filter reported an error */\n \t\t\t\terrs = 1;\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (available_paths.nr <= 0) {\n@@ -199,7 +204,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t * filter from the list (see\n \t\t\t\t * \"string_list_remove_empty_items\" call below).\n \t\t\t\t */\n-\t\t\t\tfilter->string = \"\";\n+\t\t\t\tfilter->string = NULL;\n \t\t\t\tcontinue;\n \t\t\t}\n \n@@ -225,7 +230,7 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\t * Do not ask the filter for available blobs,\n \t\t\t\t\t * again, as the filter is likely buggy.\n \t\t\t\t\t */\n-\t\t\t\t\tfilter->string = \"\";\n+\t\t\t\t\tfilter->string = NULL;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tce = index_file_exists(state->istate, path->string,\n@@ -239,7 +244,8 @@ int finish_delayed_checkout(struct checkout *state, int show_progress)\n \t\t\t\t\terrs = 1;\n \t\t\t}\n \t\t}\n-\t\tstring_list_remove_empty_items(&dco->filters, 0);\n+\n+\t\tfilter_string_list(&dco->filters, 0, string_is_not_null, NULL);\n \t}\n \tstop_progress(&progress);\n \tstring_list_clear(&dco->filters, 0);\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496611","messageId":"0d36b6e5d777864559e48b0af874f11e46cb852d.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 11/27] ident: add casts for fallback name and GECOS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:25Z","receivedAt":"2024-06-07T06:38:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `xgetpwuid_self()`, we return a fallback identity when it was not\npossible to look up the current identity. This fallback identity needs\nto be internal and must never be written to by the calles as specified\nby getpwuid(3P). As both the `pw_name` and `pw_gecos` fields are marked\nas non-constant though, it will cause a warning to assign constant\nstrings to them once compiling with `-Wwrite-strings`.\n\nAdd explicit casts to avoid the warning.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ident.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/ident.c b/ident.c\nindex cc7afdbf81..caf41fb2a9 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -46,9 +46,9 @@ static struct passwd *xgetpwuid_self(int *is_bogus)\n \tpw = getpwuid(getuid());\n \tif (!pw) {\n \t\tstatic struct passwd fallback;\n-\t\tfallback.pw_name = \"unknown\";\n+\t\tfallback.pw_name = (char *) \"unknown\";\n #ifndef NO_GECOS_IN_PWENT\n-\t\tfallback.pw_gecos = \"Unknown\";\n+\t\tfallback.pw_gecos = (char *) \"Unknown\";\n #endif\n \t\tpw = &fallback;\n \t\tif (is_bogus)\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496612","messageId":"bfd632fea7b60c379c7ab65628ee07b9c5341599.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 12/27] object-file: mark cached object buffers as const","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:30Z","receivedAt":"2024-06-07T06:38:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The buffers of cached objects are never modified, but are still stored\nas a non-constant pointer. This will cause a compiler warning once we\nenable the `-Wwrite-strings` compiler warning as we assign an empty\nconstant string when initializing the static `empty_tree` cached object.\n\nConvert the field to be constant. This requires us to shuffle around\nthe code a bit because we memcpy(3P) into the allocated buffer in\n`pretend_object_file()`. This is easily fixed though by allocating the\nbuffer into a temporary variable first.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 610b1f465c..08c00dcc02 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -277,7 +277,7 @@ int hash_algo_by_length(int len)\n static struct cached_object {\n \tstruct object_id oid;\n \tenum object_type type;\n-\tvoid *buf;\n+\tconst void *buf;\n \tunsigned long size;\n } *cached_objects;\n static int cached_object_nr, cached_object_alloc;\n@@ -1778,6 +1778,7 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \t\t\tstruct object_id *oid)\n {\n \tstruct cached_object *co;\n+\tchar *co_buf;\n \n \thash_object_file(the_hash_algo, buf, len, type, oid);\n \tif (repo_has_object_file_with_flags(the_repository, oid, OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) ||\n@@ -1787,8 +1788,9 @@ int pretend_object_file(void *buf, unsigned long len, enum object_type type,\n \tco = &cached_objects[cached_object_nr++];\n \tco->size = len;\n \tco->type = type;\n-\tco->buf = xmalloc(len);\n-\tmemcpy(co->buf, buf, len);\n+\tco_buf = xmalloc(len);\n+\tmemcpy(co_buf, buf, len);\n+\tco->buf = co_buf;\n \toidcpy(&co->oid, oid);\n \treturn 0;\n }\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496613","messageId":"4770470a84ef8e76636547347b95cfb50251406f.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 13/27] object-file: make `buf` parameter of `index_mem()` a constant","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:35Z","receivedAt":"2024-06-07T06:38:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `buf` parameter of `index_mem()` is a non-constant string. This will\nbreak once we enable `-Wwrite-strings` because we also pass constants\nfrom at least one callsite.\n\nAdapt the parameter to be a constant. As we cannot free the buffer\nwithout casting now, this also requires us to move the lifetime of the\nnested buffer around.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 08c00dcc02..0b58751f94 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2484,12 +2484,13 @@ static int hash_format_check_report(struct fsck_options *opts UNUSED,\n }\n \n static int index_mem(struct index_state *istate,\n-\t\t     struct object_id *oid, void *buf, size_t size,\n+\t\t     struct object_id *oid,\n+\t\t     const void *buf, size_t size,\n \t\t     enum object_type type,\n \t\t     const char *path, unsigned flags)\n {\n+\tstruct strbuf nbuf = STRBUF_INIT;\n \tint ret = 0;\n-\tint re_allocated = 0;\n \tint write_object = flags & HASH_WRITE_OBJECT;\n \n \tif (!type)\n@@ -2499,11 +2500,10 @@ static int index_mem(struct index_state *istate,\n \t * Convert blobs to git internal format\n \t */\n \tif ((type == OBJ_BLOB) && path) {\n-\t\tstruct strbuf nbuf = STRBUF_INIT;\n \t\tif (convert_to_git(istate, path, buf, size, &nbuf,\n \t\t\t\t   get_conv_flags(flags))) {\n-\t\t\tbuf = strbuf_detach(&nbuf, &size);\n-\t\t\tre_allocated = 1;\n+\t\t\tbuf = nbuf.buf;\n+\t\t\tsize = nbuf.len;\n \t\t}\n \t}\n \tif (flags & HASH_FORMAT_CHECK) {\n@@ -2520,8 +2520,8 @@ static int index_mem(struct index_state *istate,\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n \t\thash_object_file(the_hash_algo, buf, size, type, oid);\n-\tif (re_allocated)\n-\t\tfree(buf);\n+\n+\tstrbuf_release(&nbuf);\n \treturn ret;\n }\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496614","messageId":"02e79d030b38de571c65f33ac0fdb08b26114972.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 14/27] pretty: add casts for decoration option pointers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:39Z","receivedAt":"2024-06-07T06:38:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct decoration_options` have a prefix and suffix field which are\nboth non-constant, but we assign a constant pointer to them. This is\nsafe to do because we pass them to `format_decorations()`, which never\nmodifies these pointers, and then immediately discard the structure. Add\nexplicit casts to avoid compilation warnings with `-Wwrite-strings`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n pretty.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex ec05db5655..1df9d635fb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1584,8 +1584,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tcase 'D':\n \t\t{\n \t\t\tconst struct decoration_options opts = {\n-\t\t\t\t.prefix = \"\",\n-\t\t\t\t.suffix = \"\"\n+\t\t\t\t.prefix = (char *) \"\",\n+\t\t\t\t.suffix = (char *) \"\",\n \t\t\t};\n \n \t\t\tformat_decorations(sb, commit, c->auto_color, &opts);\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496615","messageId":"57402de20a36d1063c4abf0e0af3e3c3c647d868.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 15/27] compat/win32: fix const-correctness with string constants","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:44Z","receivedAt":"2024-06-07T06:38:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Adjust various places in our Win32 compatibility layer where we are not\nassigning string constants to `const char *` variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n compat/basename.c | 16 ++++++++++++++--\n compat/mingw.c    | 28 ++++++++++++++++------------\n compat/winansi.c  |  2 +-\n 3 files changed, 31 insertions(+), 15 deletions(-)\n\ndiff --git a/compat/basename.c b/compat/basename.c\nindex 96bd9533b4..c33579ef61 100644\n--- a/compat/basename.c\n+++ b/compat/basename.c\n@@ -10,7 +10,13 @@ char *gitbasename (char *path)\n \t\tskip_dos_drive_prefix(&path);\n \n \tif (!path || !*path)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * basename(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tfor (base = path; *path; path++) {\n \t\tif (!is_dir_sep(*path))\n@@ -34,7 +40,13 @@ char *gitdirname(char *path)\n \tint dos_drive_prefix;\n \n \tif (!p)\n-\t\treturn \".\";\n+\t\t/*\n+\t\t * dirname(3P) is mis-specified because it returns a\n+\t\t * non-constant pointer even though it is specified to return a\n+\t\t * pointer to internal memory at times. The cast is a result of\n+\t\t * that.\n+\t\t */\n+\t\treturn (char *) \".\";\n \n \tif ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)\n \t\tgoto dot;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 6b06ea540f..d378cd04cb 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -2279,7 +2279,11 @@ struct passwd *getpwuid(int uid)\n \tp->pw_name = user_name;\n \tp->pw_gecos = get_extended_user_info(NameDisplay);\n \tif (!p->pw_gecos)\n-\t\tp->pw_gecos = \"unknown\";\n+\t\t/*\n+\t\t * Data returned by getpwuid(3P) is treated as internal and\n+\t\t * must never be written to or freed.\n+\t\t */\n+\t\tp->pw_gecos = (char *) \"unknown\";\n \tp->pw_dir = NULL;\n \n \tinitialized = 1;\n@@ -2800,16 +2804,16 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\tstrbuf_addf(report, \"'%s' is on a file system that does \"\n \t\t\t\t    \"not record ownership\\n\", path);\n \t\t} else if (report) {\n-\t\t\tLPSTR str1, str2, str3, str4, to_free1 = NULL,\n-\t\t\t    to_free3 = NULL, to_local_free2 = NULL,\n-\t\t\t    to_local_free4 = NULL;\n+\t\t\tPCSTR str1, str2, str3, str4;\n+\t\t\tLPSTR to_free1 = NULL, to_free3 = NULL,\n+\t\t\t    to_local_free2 = NULL, to_local_free4 = NULL;\n \n-\t\t\tif (user_sid_to_user_name(sid, &str1))\n-\t\t\t\tto_free1 = str1;\n+\t\t\tif (user_sid_to_user_name(sid, &to_free1))\n+\t\t\t\tstr1 = to_free1;\n \t\t\telse\n \t\t\t\tstr1 = \"(inconvertible)\";\n-\t\t\tif (ConvertSidToStringSidA(sid, &str2))\n-\t\t\t\tto_local_free2 = str2;\n+\t\t\tif (ConvertSidToStringSidA(sid, &to_local_free2))\n+\t\t\t\tstr2 = to_local_free2;\n \t\t\telse\n \t\t\t\tstr2 = \"(inconvertible)\";\n \n@@ -2822,13 +2826,13 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)\n \t\t\t\tstr4 = \"(invalid)\";\n \t\t\t} else {\n \t\t\t\tif (user_sid_to_user_name(current_user_sid,\n-\t\t\t\t\t\t\t  &str3))\n-\t\t\t\t\tto_free3 = str3;\n+\t\t\t\t\t\t\t  &to_free3))\n+\t\t\t\t\tstr3 = to_free3;\n \t\t\t\telse\n \t\t\t\t\tstr3 = \"(inconvertible)\";\n \t\t\t\tif (ConvertSidToStringSidA(current_user_sid,\n-\t\t\t\t\t\t\t   &str4))\n-\t\t\t\t\tto_local_free4 = str4;\n+\t\t\t\t\t\t\t   &to_local_free4))\n+\t\t\t\t\tstr4 = to_local_free4;\n \t\t\t\telse\n \t\t\t\t\tstr4 = \"(inconvertible)\";\n \t\t\t}\ndiff --git a/compat/winansi.c b/compat/winansi.c\nindex f83610f684..575813bde8 100644\n--- a/compat/winansi.c\n+++ b/compat/winansi.c\n@@ -139,7 +139,7 @@ static void write_console(unsigned char *str, size_t len)\n \t/* convert utf-8 to utf-16 */\n \tint wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);\n \tif (wlen < 0) {\n-\t\twchar_t *err = L\"[invalid]\";\n+\t\tconst wchar_t *err = L\"[invalid]\";\n \t\tWriteConsoleW(console, err, wcslen(err), &dummy, NULL);\n \t\treturn;\n \t}\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496616","messageId":"71c83468f1f654e79877616bcaf471fb192a2b27.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 16/27] http: do not assign string constant to non-const field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:49Z","receivedAt":"2024-06-07T06:38:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `write_accept_language()`, we put all acceptable languages into an\narray. While all entries in that array are allocated strings, the final\nentry in that array is a string constant. This is fine because we\nexplicitly skip over the last entry when freeing the array, but will\ncause warnings once we enable `-Wwrite-strings`.\n\nAdapt the code to also allocate the final entry.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n http.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 67cc47d28f..2dea2d03da 100644\n--- a/http.c\n+++ b/http.c\n@@ -1974,7 +1974,7 @@ static void write_accept_language(struct strbuf *buf)\n \n \t\t/* add '*' */\n \t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n-\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\t\tlanguage_tags[num_langs++] = xstrdup(\"*\");\n \n \t\t/* compute decimal_places */\n \t\tfor (max_q = 1, decimal_places = 0;\n@@ -2004,8 +2004,7 @@ static void write_accept_language(struct strbuf *buf)\n \t\t}\n \t}\n \n-\t/* free language tags -- last one is a static '*' */\n-\tfor (i = 0; i < num_langs - 1; i++)\n+\tfor (i = 0; i < num_langs; i++)\n \t\tfree(language_tags[i]);\n \tfree(language_tags);\n }\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496617","messageId":"f3e02df0ca06d68a05c1faf5e9a643d4fb5093ba.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 17/27] parse-options: cast long name for OPTION_ALIAS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:54Z","receivedAt":"2024-06-07T06:38:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We assign the long name for OPTION_ALIAS options to a non-constant value\nfield. We know that the variable will never be written to, but this will\ncause warnings once we enable `-Wwrite-strings`.\n\nCast away the constness to be prepared for this change.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n parse-options.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex bd62e20268..ae15342390 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -355,7 +355,7 @@ struct option {\n \t.type = OPTION_ALIAS, \\\n \t.short_name = (s), \\\n \t.long_name = (l), \\\n-\t.value = (source_long_name), \\\n+\t.value = (char *)(source_long_name), \\\n }\n \n #define OPT_SUBCOMMAND_F(l, v, fn, f) { \\\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496618","messageId":"a46708b8984d7c1734c8b2f039f26ac2f9d63139.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 18/27] send-pack: always allocate receive status","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:38:58Z","receivedAt":"2024-06-07T06:39:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `receive_status()`, we record the reason why ref updates have been\nrejected by the remote via the `remote_status`. But while we allocate\nthe assigned string when a reason was given, we assign a string constant\nwhen no reason was given.\n\nThis has been working fine so far due to two reasons:\n\n  - We don't ever free the refs in git-send-pack(1)'\n\n  - Remotes always give a reason, at least as implemented by Git proper.\n\nAdapt the code to always allocate the receive status string and free the\nrefs.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/send-pack.c | 2 ++\n send-pack.c         | 2 +-\n 2 files changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 3df9eaad09..17cae6bbbd 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -336,5 +336,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t/* stable plumbing output; do not modify or localize */\n \t\tfprintf(stderr, \"Everything up-to-date\\n\");\n \n+\tfree_refs(remote_refs);\n+\tfree_refs(local_refs);\n \treturn ret;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 37f59d4f66..88e96d000b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -259,7 +259,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tif (p)\n \t\t\t\thint->remote_status = xstrdup(p);\n \t\t\telse\n-\t\t\t\thint->remote_status = \"failed\";\n+\t\t\t\thint->remote_status = xstrdup(\"failed\");\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n \t\t\thint->remote_status = xstrdup_or_null(p);\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496619","messageId":"99c88897be33bc1ed1c50f84e48cae7717a7b10b.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 19/27] remote-curl: avoid assigning string constant to non-const variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:03Z","receivedAt":"2024-06-07T06:39:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When processing remote options, we split the option line into two by\nsearching for a space. If there is one, we replace the space with '\\0',\notherwise we implicitly assume that the value is \"true\" and thus assign\na string constant.\n\nAs the return value of strchr(3P) weirdly enough is a `char *` even\nthough it gets a `const char *` as input, the assigned-to variable also\nis a non-constant. This is fine though because the argument is in fact\nan allocated string, and thus we are allowed to modify it. But this will\nbreak once we enable `-Wwrite-strings`.\n\nRefactor the code stop splitting the fields with '\\0' altogether.\nInstead, we can pass the length of the option name to `set_option()` and\nthen use strncmp(3P) instead of strcmp(3P).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n remote-curl.c | 53 ++++++++++++++++++++++++++-------------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex cae98384da..d0f767df8e 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -58,9 +58,9 @@ struct options {\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n \n-static int set_option(const char *name, const char *value)\n+static int set_option(const char *name, size_t namelen, const char *value)\n {\n-\tif (!strcmp(name, \"verbosity\")) {\n+\tif (!strncmp(name, \"verbosity\", namelen)) {\n \t\tchar *end;\n \t\tint v = strtol(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -68,7 +68,7 @@ static int set_option(const char *name, const char *value)\n \t\toptions.verbosity = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"progress\")) {\n+\telse if (!strncmp(name, \"progress\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.progress = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -77,7 +77,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"depth\")) {\n+\telse if (!strncmp(name, \"depth\", namelen)) {\n \t\tchar *end;\n \t\tunsigned long v = strtoul(value, &end, 10);\n \t\tif (value == end || *end)\n@@ -85,15 +85,15 @@ static int set_option(const char *name, const char *value)\n \t\toptions.depth = v;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-since\")) {\n+\telse if (!strncmp(name, \"deepen-since\", namelen)) {\n \t\toptions.deepen_since = xstrdup(value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-not\")) {\n+\telse if (!strncmp(name, \"deepen-not\", namelen)) {\n \t\tstring_list_append(&options.deepen_not, value);\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"deepen-relative\")) {\n+\telse if (!strncmp(name, \"deepen-relative\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.deepen_relative = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -102,7 +102,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"followtags\")) {\n+\telse if (!strncmp(name, \"followtags\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.followtags = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -111,7 +111,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"dry-run\")) {\n+\telse if (!strncmp(name, \"dry-run\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.dry_run = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -120,7 +120,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"check-connectivity\")) {\n+\telse if (!strncmp(name, \"check-connectivity\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.check_self_contained_and_connected = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -129,7 +129,7 @@ static int set_option(const char *name, const char *value)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n-\telse if (!strcmp(name, \"cas\")) {\n+\telse if (!strncmp(name, \"cas\", namelen)) {\n \t\tstruct strbuf val = STRBUF_INIT;\n \t\tstrbuf_addstr(&val, \"--force-with-lease=\");\n \t\tif (*value != '\"')\n@@ -139,7 +139,7 @@ static int set_option(const char *name, const char *value)\n \t\tstring_list_append(&cas_options, val.buf);\n \t\tstrbuf_release(&val);\n \t\treturn 0;\n-\t} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {\n+\t} else if (!strncmp(name, TRANS_OPT_FORCE_IF_INCLUDES, namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.force_if_includes = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -147,7 +147,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"cloning\")) {\n+\t} else if (!strncmp(name, \"cloning\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.cloning = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -155,7 +155,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"update-shallow\")) {\n+\t} else if (!strncmp(name, \"update-shallow\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.update_shallow = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -163,7 +163,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"pushcert\")) {\n+\t} else if (!strncmp(name, \"pushcert\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -173,7 +173,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"atomic\")) {\n+\t} else if (!strncmp(name, \"atomic\", namelen)) {\n \t\tif (!strcmp(value, \"true\"))\n \t\t\toptions.atomic = 1;\n \t\telse if (!strcmp(value, \"false\"))\n@@ -181,7 +181,7 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"push-option\")) {\n+\t} else if (!strncmp(name, \"push-option\", namelen)) {\n \t\tif (*value != '\"')\n \t\t\tstring_list_append(&options.push_options, value);\n \t\telse {\n@@ -192,7 +192,7 @@ static int set_option(const char *name, const char *value)\n \t\t\t\t\t\t strbuf_detach(&unquoted, NULL));\n \t\t}\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"family\")) {\n+\t} else if (!strncmp(name, \"family\", namelen)) {\n \t\tif (!strcmp(value, \"ipv4\"))\n \t\t\tgit_curl_ipresolve = CURL_IPRESOLVE_V4;\n \t\telse if (!strcmp(value, \"ipv6\"))\n@@ -202,16 +202,16 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"from-promisor\")) {\n+\t} else if (!strncmp(name, \"from-promisor\", namelen)) {\n \t\toptions.from_promisor = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"refetch\")) {\n+\t} else if (!strncmp(name, \"refetch\", namelen)) {\n \t\toptions.refetch = 1;\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"filter\")) {\n+\t} else if (!strncmp(name, \"filter\", namelen)) {\n \t\toptions.filter = xstrdup(value);\n \t\treturn 0;\n-\t} else if (!strcmp(name, \"object-format\")) {\n+\t} else if (!strncmp(name, \"object-format\", namelen)) {\n \t\toptions.object_format = 1;\n \t\tif (strcmp(value, \"true\"))\n \t\t\tdie(_(\"unknown value for object-format: %s\"), value);\n@@ -1588,15 +1588,16 @@ int cmd_main(int argc, const char **argv)\n \t\t\tparse_push(&buf);\n \n \t\t} else if (skip_prefix(buf.buf, \"option \", &arg)) {\n-\t\t\tchar *value = strchr(arg, ' ');\n+\t\t\tconst char *value = strchrnul(arg, ' ');\n+\t\t\tsize_t arglen = value - arg;\n \t\t\tint result;\n \n-\t\t\tif (value)\n-\t\t\t\t*value++ = '\\0';\n+\t\t\tif (*value)\n+\t\t\t\tvalue++; /* skip over SP */\n \t\t\telse\n \t\t\t\tvalue = \"true\";\n \n-\t\t\tresult = set_option(arg, value);\n+\t\t\tresult = set_option(arg, arglen, value);\n \t\t\tif (!result)\n \t\t\t\tprintf(\"ok\\n\");\n \t\t\telse if (result < 0)\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496620","messageId":"03556233d6296973176a4edbcaffb12746cd2f1a.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 20/27] revision: always store allocated strings in output encoding","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:07Z","receivedAt":"2024-06-07T06:39:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `git_log_output_encoding` variable can be set via the `--encoding=`\noption. When doing so, we conditionally either assign it to the passed\nvalue, or if the value is \"none\" we assign it the empty string.\nDepending on which of the both code paths we pick though, the variable\nmay end up being assigned either an allocated string or a string\nconstant.\n\nThis is somewhat risky and may easily lead to bugs when a different code\npath may want to reassign a new value to it, freeing the previous value.\nWe already to this when parsing the \"i18n.logoutputencoding\" config in\n`git_default_i18n_config()`. But because the config is typically parsed\nbefore we parse command line options this has been fine so far.\n\nRegardless of that, safeguard the code such that the variable always\ncontains an allocated string. While at it, also free the old value in\ncase there was any to plug a potential memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c             | 3 ++-\n t/t3900-i18n-commit.sh | 1 +\n t/t3901-i18n-patch.sh  | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..2ee6886078 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2650,10 +2650,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--invert-grep\")) {\n \t\trevs->grep_filter.no_body_match = 1;\n \t} else if ((argcount = parse_long_opt(\"encoding\", argv, &optarg))) {\n+\t\tfree(git_log_output_encoding);\n \t\tif (strcmp(optarg, \"none\"))\n \t\t\tgit_log_output_encoding = xstrdup(optarg);\n \t\telse\n-\t\t\tgit_log_output_encoding = \"\";\n+\t\t\tgit_log_output_encoding = xstrdup(\"\");\n \t\treturn argcount;\n \t} else if (!strcmp(arg, \"--reverse\")) {\n \t\trevs->reverse ^= 1;\ndiff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh\nindex f27d09cfd9..db7b403bc1 100755\n--- a/t/t3900-i18n-commit.sh\n+++ b/t/t3900-i18n-commit.sh\n@@ -5,6 +5,7 @@\n \n test_description='commit and log output encodings'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n compare_with () {\ndiff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh\nindex 4b37f78829..5f0b9afc3f 100755\n--- a/t/t3901-i18n-patch.sh\n+++ b/t/t3901-i18n-patch.sh\n@@ -8,6 +8,7 @@ test_description='i18n settings and format-patch | am pipe'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n check_encoding () {\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496621","messageId":"2bb96449e2a65f3398048b3430822bc7b95d31f0.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 21/27] mailmap: always store allocated strings in mailmap blob","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:12Z","receivedAt":"2024-06-07T06:39:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Same as with the preceding commit, the `git_mailmap_blob` may sometimes\ncontain an allocated string and sometimes it may contain a string\nconstant. This is risky and can easily lead to bugs in case the variable\nis getting re-assigned, where the code may then try to free the previous\nvalue to avoid memory leaks.\n\nSafeguard the code by always storing allocated strings in the variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n mailmap.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/mailmap.c b/mailmap.c\nindex b2efe29b3d..3d1e092fef 100644\n--- a/mailmap.c\n+++ b/mailmap.c\n@@ -216,7 +216,7 @@ int read_mailmap(struct string_list *map)\n \tmap->cmp = namemap_cmp;\n \n \tif (!git_mailmap_blob && is_bare_repository())\n-\t\tgit_mailmap_blob = \"HEAD:.mailmap\";\n+\t\tgit_mailmap_blob = xstrdup(\"HEAD:.mailmap\");\n \n \tif (!startup_info->have_repository || !is_bare_repository())\n \t\terr |= read_mailmap_file(map, \".mailmap\",\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496622","messageId":"8059e013a1fe8e60d49c5e1f435334bee488fbed.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 22/27] imap-send: drop global `imap_server_conf` variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:17Z","receivedAt":"2024-06-07T06:39:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"imap-send.c\", we have a global `sturct imap_server_conf` variable\nthat keeps track of the configuration of the IMAP server. This variable\nis being populated mostly via the Git configuration.\n\nRefactor the code to allocate the structure on the stack instead of\nhaving it globally. This change allows us to track its lifetime more\nclosely.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 57 ++++++++++++++++++++++++++++-------------------------\n 1 file changed, 30 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8b723b34a5..67a7a6c456 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -82,10 +82,6 @@ struct imap_server_conf {\n \tchar *auth_method;\n };\n \n-static struct imap_server_conf server = {\n-\t.ssl_verify = 1,\n-};\n-\n struct imap_socket {\n \tint fd[2];\n \tSSL *ssl;\n@@ -110,6 +106,7 @@ struct imap {\n };\n \n struct imap_store {\n+\tconst struct imap_server_conf *cfg;\n \t/* currently open mailbox */\n \tconst char *name; /* foreign! maybe preset? */\n \tint uidvalidity;\n@@ -194,8 +191,8 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)\n \n #ifdef NO_OPENSSL\n static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n-\t\t\t      int use_tls_only UNUSED,\n-\t\t\t      int verify UNUSED)\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only UNUSED)\n {\n \tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n \treturn -1;\n@@ -250,7 +247,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \t\t     cname, hostname);\n }\n \n-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)\n+static int ssl_socket_connect(struct imap_socket *sock,\n+\t\t\t      const struct imap_server_conf *cfg,\n+\t\t\t      int use_tls_only)\n {\n #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)\n \tconst SSL_METHOD *meth;\n@@ -279,7 +278,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \tif (use_tls_only)\n \t\tSSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);\n \n-\tif (verify)\n+\tif (cfg->ssl_verify)\n \t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n \n \tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n@@ -306,9 +305,9 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t * OpenSSL does not document this function, but the implementation\n \t * returns 1 on success, 0 on failure after calling SSLerr().\n \t */\n-\tret = SSL_set_tlsext_host_name(sock->ssl, server.host);\n+\tret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);\n \tif (ret != 1)\n-\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", server.host);\n+\t\twarning(\"SSL_set_tlsext_host_name(%s) failed.\", cfg->host);\n #endif\n \n \tret = SSL_connect(sock->ssl);\n@@ -317,12 +316,12 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve\n \t\treturn -1;\n \t}\n \n-\tif (verify) {\n+\tif (cfg->ssl_verify) {\n \t\t/* make sure the hostname matches that of the certificate */\n \t\tcert = SSL_get_peer_certificate(sock->ssl);\n \t\tif (!cert)\n \t\t\treturn error(\"unable to get peer certificate.\");\n-\t\tif (verify_hostname(cert, server.host) < 0)\n+\t\tif (verify_hostname(cert, cfg->host) < 0)\n \t\t\treturn -1;\n \t}\n \n@@ -895,7 +894,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tint ret;\n \tchar *response;\n \n-\tresponse = cram(prompt, server.user, server.pass);\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response))\n@@ -935,6 +934,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \tCALLOC_ARRAY(ctx, 1);\n \n+\tctx->cfg = srvc;\n \tctx->imap = CALLOC_ARRAY(imap, 1);\n \timap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;\n \timap->in_progress_append = &imap->in_progress;\n@@ -1035,7 +1035,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[1] = dup(s);\n \n \t\tif (srvc->use_ssl &&\n-\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t    ssl_socket_connect(&imap->buf.sock, srvc, 0)) {\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n@@ -1068,8 +1068,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n \t\t\tif (imap_exec(ctx, NULL, \"STARTTLS\") != RESP_OK)\n \t\t\t\tgoto bail;\n-\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n-\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, srvc, 1))\n \t\t\t\tgoto bail;\n \t\t\t/* capabilities may have changed, so get the new capabilities */\n \t\t\tif (imap_exec(ctx, NULL, \"CAPABILITY\") != RESP_OK)\n@@ -1299,23 +1298,24 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n static int git_imap_config(const char *var, const char *val,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n+\tstruct imap_server_conf *cfg = cb;\n \n \tif (!strcmp(\"imap.sslverify\", var))\n-\t\tserver.ssl_verify = git_config_bool(var, val);\n+\t\tcfg->ssl_verify = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.preformattedhtml\", var))\n-\t\tserver.use_html = git_config_bool(var, val);\n+\t\tcfg->use_html = git_config_bool(var, val);\n \telse if (!strcmp(\"imap.folder\", var))\n-\t\treturn git_config_string(&server.folder, var, val);\n+\t\treturn git_config_string(&cfg->folder, var, val);\n \telse if (!strcmp(\"imap.user\", var))\n-\t\treturn git_config_string(&server.user, var, val);\n+\t\treturn git_config_string(&cfg->user, var, val);\n \telse if (!strcmp(\"imap.pass\", var))\n-\t\treturn git_config_string(&server.pass, var, val);\n+\t\treturn git_config_string(&cfg->pass, var, val);\n \telse if (!strcmp(\"imap.tunnel\", var))\n-\t\treturn git_config_string(&server.tunnel, var, val);\n+\t\treturn git_config_string(&cfg->tunnel, var, val);\n \telse if (!strcmp(\"imap.authmethod\", var))\n-\t\treturn git_config_string(&server.auth_method, var, val);\n+\t\treturn git_config_string(&cfg->auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val, ctx->kvi);\n+\t\tcfg->port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n@@ -1324,11 +1324,11 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 5;\n \t\t\telse if (starts_with(val, \"imaps:\")) {\n \t\t\t\tval += 6;\n-\t\t\t\tserver.use_ssl = 1;\n+\t\t\t\tcfg->use_ssl = 1;\n \t\t\t}\n \t\t\tif (starts_with(val, \"//\"))\n \t\t\t\tval += 2;\n-\t\t\tserver.host = xstrdup(val);\n+\t\t\tcfg->host = xstrdup(val);\n \t\t}\n \t} else\n \t\treturn git_default_config(var, val, ctx, cb);\n@@ -1497,12 +1497,15 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n int cmd_main(int argc, const char **argv)\n {\n+\tstruct imap_server_conf server = {\n+\t\t.ssl_verify = 1,\n+\t};\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n \n \tsetup_git_directory_gently(&nongit_ok);\n-\tgit_config(git_imap_config, NULL);\n+\tgit_config(git_imap_config, &server);\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496623","messageId":"04c9cfd34df33f1bc099bfd8f2729a4259584ce9.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 23/27] imap-send: fix leaking memory in `imap_server_conf`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:22Z","receivedAt":"2024-06-07T06:39:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free any of the config strings that we populate into the\n`struct imap_server_conf`. Fix this by creating a common exit path where\nwe can free resources.\n\nWhile at it, drop the unused member `imap_server_conf::name`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n imap-send.c | 65 ++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 42 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67a7a6c456..da3e7ec17e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -69,7 +69,6 @@ static void imap_warn(const char *, ...);\n static char *next_arg(char **);\n \n struct imap_server_conf {\n-\tconst char *name;\n \tchar *tunnel;\n \tchar *host;\n \tint port;\n@@ -1300,23 +1299,28 @@ static int git_imap_config(const char *var, const char *val,\n {\n \tstruct imap_server_conf *cfg = cb;\n \n-\tif (!strcmp(\"imap.sslverify\", var))\n+\tif (!strcmp(\"imap.sslverify\", var)) {\n \t\tcfg->ssl_verify = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.preformattedhtml\", var))\n+\t} else if (!strcmp(\"imap.preformattedhtml\", var)) {\n \t\tcfg->use_html = git_config_bool(var, val);\n-\telse if (!strcmp(\"imap.folder\", var))\n+\t} else if (!strcmp(\"imap.folder\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n-\telse if (!strcmp(\"imap.user\", var))\n+\t} else if (!strcmp(\"imap.user\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->user, var, val);\n-\telse if (!strcmp(\"imap.pass\", var))\n+\t} else if (!strcmp(\"imap.pass\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->pass, var, val);\n-\telse if (!strcmp(\"imap.tunnel\", var))\n+\t} else if (!strcmp(\"imap.tunnel\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n-\telse if (!strcmp(\"imap.authmethod\", var))\n+\t} else if (!strcmp(\"imap.authmethod\", var)) {\n+\t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n-\telse if (!strcmp(\"imap.port\", var))\n+\t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-\telse if (!strcmp(\"imap.host\", var)) {\n+\t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n \t\t} else {\n@@ -1330,8 +1334,9 @@ static int git_imap_config(const char *var, const char *val,\n \t\t\t\tval += 2;\n \t\t\tcfg->host = xstrdup(val);\n \t\t}\n-\t} else\n+\t} else {\n \t\treturn git_default_config(var, val, ctx, cb);\n+\t}\n \n \treturn 0;\n }\n@@ -1503,6 +1508,7 @@ int cmd_main(int argc, const char **argv)\n \tstruct strbuf all_msgs = STRBUF_INIT;\n \tint total;\n \tint nongit_ok;\n+\tint ret;\n \n \tsetup_git_directory_gently(&nongit_ok);\n \tgit_config(git_imap_config, &server);\n@@ -1529,42 +1535,55 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.folder) {\n \t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n-\t\t\treturn 1;\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n \t\t}\n-\t\tserver.host = \"tunnel\";\n+\t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n \t\tfprintf(stderr, \"nothing to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n \t\tfprintf(stderr, \"no messages to send\\n\");\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \t/* write it to the imap server */\n \n \tif (server.tunnel)\n-\t\treturn append_msgs_to_imap(&server, &all_msgs, total);\n-\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n #ifdef USE_CURL_FOR_IMAP_SEND\n-\tif (use_curl)\n-\t\treturn curl_append_msgs_to_imap(&server, &all_msgs, total);\n+\telse if (use_curl)\n+\t\tret = curl_append_msgs_to_imap(&server, &all_msgs, total);\n #endif\n-\n-\treturn append_msgs_to_imap(&server, &all_msgs, total);\n+\telse\n+\t\tret = append_msgs_to_imap(&server, &all_msgs, total);\n+\n+out:\n+\tfree(server.tunnel);\n+\tfree(server.host);\n+\tfree(server.folder);\n+\tfree(server.user);\n+\tfree(server.pass);\n+\tfree(server.auth_method);\n+\treturn ret;\n }\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496624","messageId":"b2b4a01a2e71bf09c85014da05f74c226596f0f7.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 24/27] builtin/rebase: do not assign default backend to non-constant field","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:26Z","receivedAt":"2024-06-07T06:39:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::default_backend` field is a non-constant\nstring, but is being assigned a constant via `REBASE_OPTIONS_INIT`.\nFix this by using `xstrdup()` to assign the variable and introduce a new\nfunction `rebase_options_release()` that releases memory held by the\nstructure, including the newly-allocated variable.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 25 ++++++++++++++++---------\n 1 file changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 14d4f0a5e6..adc990b55e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -135,7 +135,7 @@ struct rebase_options {\n \t\t.type = REBASE_UNSPECIFIED,\t  \t\\\n \t\t.empty = EMPTY_UNSPECIFIED,\t  \t\\\n \t\t.keep_empty = 1,\t\t\t\\\n-\t\t.default_backend = \"merge\",\t  \t\\\n+\t\t.default_backend = xstrdup(\"merge\"),  \t\\\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n@@ -151,6 +151,19 @@ struct rebase_options {\n \t\t.strategy_opts = STRING_LIST_INIT_NODUP,\\\n \t}\n \n+static void rebase_options_release(struct rebase_options *opts)\n+{\n+\tfree(opts->default_backend);\n+\tfree(opts->reflog_action);\n+\tfree(opts->head_name);\n+\tstrvec_clear(&opts->git_am_opts);\n+\tfree(opts->gpg_sign_opt);\n+\tstring_list_clear(&opts->exec, 0);\n+\tfree(opts->strategy);\n+\tstring_list_clear(&opts->strategy_opts, 0);\n+\tstrbuf_release(&opts->git_format_patch_opt);\n+}\n+\n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n {\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n@@ -796,6 +809,7 @@ static int rebase_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"rebase.backend\")) {\n+\t\tFREE_AND_NULL(opts->default_backend);\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n@@ -1833,14 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n cleanup:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n-\tfree(options.reflog_action);\n-\tfree(options.head_name);\n-\tstrvec_clear(&options.git_am_opts);\n-\tfree(options.gpg_sign_opt);\n-\tstring_list_clear(&options.exec, 0);\n-\tfree(options.strategy);\n-\tstring_list_clear(&options.strategy_opts, 0);\n-\tstrbuf_release(&options.git_format_patch_opt);\n+\trebase_options_release(&options);\n \tfree(squash_onto_name);\n \tfree(keep_base_onto_name);\n \treturn !!ret;\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496625","messageId":"09980d211b125d05f15567d7f7348a064f14ce91.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 25/27] builtin/rebase: always store allocated string in `options.strategy`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:31Z","receivedAt":"2024-06-07T06:39:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `struct rebase_options::strategy` field is a `char *`, but we do end\nup assigning string constants to it in two cases:\n\n  - When being passed a `--strategy=` option via the command line.\n\n  - When being passed a strategy option via `--strategy-option=`, but\n    not a strategy.\n\nThis will cause warnings once we enable `-Wwrite-strings`.\n\nIdeally, we'd just convert the field to be a `const char *`. But we also\nassign to this field via the GIT_TEST_MERGE_ALGORITHM envvar, which we\nhave to strdup(3P) into it.\n\nInstead, refactor the code to make sure that we only ever assign\nallocated strings to this field.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rebase.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex adc990b55e..4506bae768 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1061,6 +1061,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n {\n \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n \tconst char *branch_name;\n+\tconst char *strategy_opt = NULL;\n \tint ret, flags, total_argc, in_progress = 0;\n \tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n@@ -1175,7 +1176,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_OPTARG, parse_opt_rebase_merges),\n \t\tOPT_BOOL(0, \"fork-point\", &options.fork_point,\n \t\t\t N_(\"use 'merge-base --fork-point' to refine upstream\")),\n-\t\tOPT_STRING('s', \"strategy\", &options.strategy,\n+\t\tOPT_STRING('s', \"strategy\", &strategy_opt,\n \t\t\t   N_(\"strategy\"), N_(\"use the given merge strategy\")),\n \t\tOPT_STRING_LIST('X', \"strategy-option\", &options.strategy_opts,\n \t\t\t\tN_(\"option\"),\n@@ -1484,13 +1485,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (options.strategy_opts.nr && !options.strategy)\n-\t\toptions.strategy = \"ort\";\n-\n-\tif (options.strategy) {\n-\t\toptions.strategy = xstrdup(options.strategy);\n+\tif (strategy_opt)\n+\t\toptions.strategy = xstrdup(strategy_opt);\n+\telse if (options.strategy_opts.nr && !options.strategy)\n+\t\toptions.strategy = xstrdup(\"ort\");\n+\tif (options.strategy)\n \t\timply_merge(&options, \"--strategy\");\n-\t}\n \n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496626","messageId":"d5dc1453f21a98650180f7df13ec9e14606478e2.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 26/27] builtin/merge: always store allocated strings in `pull_twohead`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:35Z","receivedAt":"2024-06-07T06:39:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pull_twohead` configuration may sometimes contain an allocated\nstring, and sometimes it may contain a string constant. Refactor this to\ninstead always store an allocated string such that we can release its\nresources without risk.\n\nWhile at it, manage the lifetime of other config strings, as well. Note\nthat we explicitly don't free `cleanup_arg` here. This is because the\nvariable may be assigned a string constant via command line options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..fb3eb15b89 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -611,17 +611,19 @@ static int git_merge_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\"))\n+\tif (!strcmp(k, \"merge.diffstat\") || !strcmp(k, \"merge.stat\")) {\n \t\tshow_diffstat = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"merge.verifysignatures\"))\n+\t} else if (!strcmp(k, \"merge.verifysignatures\")) {\n \t\tverify_signatures = git_config_bool(k, v);\n-\telse if (!strcmp(k, \"pull.twohead\"))\n+\t} else if (!strcmp(k, \"pull.twohead\")) {\n+\t\tFREE_AND_NULL(pull_twohead);\n \t\treturn git_config_string(&pull_twohead, k, v);\n-\telse if (!strcmp(k, \"pull.octopus\"))\n+\t} else if (!strcmp(k, \"pull.octopus\")) {\n+\t\tFREE_AND_NULL(pull_octopus);\n \t\treturn git_config_string(&pull_octopus, k, v);\n-\telse if (!strcmp(k, \"commit.cleanup\"))\n+\t} else if (!strcmp(k, \"commit.cleanup\")) {\n \t\treturn git_config_string(&cleanup_arg, k, v);\n-\telse if (!strcmp(k, \"merge.ff\")) {\n+\t} else if (!strcmp(k, \"merge.ff\")) {\n \t\tint boolval = git_parse_maybe_bool(v);\n \t\tif (0 <= boolval) {\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n@@ -1294,7 +1296,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (!pull_twohead) {\n \t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n-\t\t\tpull_twohead = \"ort\";\n+\t\t\tpull_twohead = xstrdup(\"ort\");\n \t}\n \n \tinit_diff_ui_defaults();\n@@ -1793,6 +1795,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \tstrbuf_release(&buf);\n \tfree(branch_to_free);\n+\tfree(pull_twohead);\n+\tfree(pull_octopus);\n \tdiscard_index(the_repository->index);\n \treturn ret;\n }\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496627","messageId":"2c051d1fe64b087965bc4d3b51a19afbfb1ba4bf.1717740366.git.ps@pks.im","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"[PATCH v6 27/27] config.mak.dev: enable `-Wwrite-strings` warning","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T06:39:40Z","receivedAt":"2024-06-07T06:39:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Writing to string constants is undefined behaviour and must be avoided\nin C. Even so, the compiler does not help us with this by default\nbecause those constants are not in fact marked as `const`. This makes it\nrather easy to accidentally assign a constant to a non-const variable or\nfield and then later on try to either free it or write to it.\n\nEnable `-Wwrite-strings` to catch such mistakes. With this warning\nenabled, the type of string constants is changed to `const char[]` and\nwill thus cause compiler warnings when being assigned to non-const\nfields and variables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.mak.dev | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/config.mak.dev b/config.mak.dev\nindex 981304727c..1ce4c70613 100644\n--- a/config.mak.dev\n+++ b/config.mak.dev\n@@ -37,6 +37,7 @@ DEVELOPER_CFLAGS += -Wpointer-arith\n DEVELOPER_CFLAGS += -Wstrict-prototypes\n DEVELOPER_CFLAGS += -Wunused\n DEVELOPER_CFLAGS += -Wvla\n+DEVELOPER_CFLAGS += -Wwrite-strings\n DEVELOPER_CFLAGS += -fno-common\n \n ifneq ($(filter clang4,$(COMPILER_FEATURES)),)\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496659","messageId":"xmqqa5jwejxw.fsf@gitster.g","threadId":"61561","inReplyTo":"cover.1717740365.git.ps@pks.im","subject":"Re: [PATCH v6 00/27] Compile with `-Wwrite-strings`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-07T17:34:03Z","receivedAt":"2024-06-07T17:34:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The only change is in patch 12, where we now allocate a buffer later in\n> time to avoid a useless allocation and the need for a call to free.\n\nThat makes sense (and is closer to the original anyway).\n\nWill queue.  Thanks.\n\n"},{"id":"496707","messageId":"20240608105929.GA2966571@coredump.intra.peff.net","threadId":"61561","inReplyTo":"xmqqplsvtirr.fsf@gitster.g","subject":"Re: [PATCH 02/19] global: assign non-const strings as required","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-06-08T10:59:29Z","receivedAt":"2024-06-08T10:59:31Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jun 05, 2024 at 10:13:28AM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > On Fri, May 31, 2024 at 08:27:13AM -0700, Junio C Hamano wrote:\n> >\n> >> I wonder if we can do something to separate these two concerns\n> >> apart, using a trick similar to what we often use with an extra\n> >> variable \"to_free\".  Doing so would bloat the refspec_item, but\n> >> unlike the references themselves, there won't be thousands of them,\n> >> so it may not be an issue, perhaps?\n> >\n> > I had a similar thought while looking at this spot a while ago, so I dug\n> > this attempt out of my stash. It's quite ugly, as you need to keep the\n> > storage pointer and the const pointer in sync. Especially because\n> > there's a lot of clever pointer indirection via match_name_with_pattern().\n> \n> Ah, true.  The patch itself does not look _too_ bad, but that may\n> simply be because the original is bad enough ;-)\n\nA lot of it is not how bad the patch itself looks, but how hard it was\nto write. Each site which touches \"src\" needs to consider whether it\nshould be \"src_storage\", and there was a lot of running the test suite\nwith ASan, followed by head-scratching. I _think_ I got all of the right\nspots, but who knows. ;)\n\nAnyway, I think Patrick is quite sensible in not pursuing it further for\nthis series. But just for posterity, here's a slightly less invasive\nversion of the patch I showed earlier. I had added an extra arguments to\nmatch_name_with_pattern() to auto-assign the \"const\" version, but it\nturned out that only one caller cared about it (and had to manually\nassign the const version in its fallback code path anyway!).\n\nSo this version has a little less noise in it.\n\n---\n branch.c         |  6 +++---\n builtin/fetch.c  |  8 ++++----\n builtin/remote.c |  6 +++---\n checkout.c       | 13 +++++++------\n refspec.c        |  6 ++++--\n refspec.h        |  6 ++++--\n remote.c         | 12 +++++++-----\n transport.c      |  2 +-\n 8 files changed, 33 insertions(+), 26 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex df5d24fec6..e1585b10c2 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -38,7 +38,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tswitch (++tracking->matches) {\n \t\tcase 1:\n-\t\t\tstring_list_append_nodup(tracking->srcs, tracking->spec.src);\n+\t\t\tstring_list_append_nodup(tracking->srcs, tracking->spec.src_storage);\n \t\t\ttracking->remote = remote->name;\n \t\t\tbreak;\n \t\tcase 2:\n@@ -47,7 +47,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \t\t\t/* fall through */\n \t\tdefault:\n \t\t\tstring_list_append(&ftb->ambiguous_remotes, remote->name);\n-\t\t\tfree(tracking->spec.src);\n+\t\t\tfree(tracking->spec.src_storage);\n \t\t\tstring_list_clear(tracking->srcs, 0);\n \t\tbreak;\n \t\t}\n@@ -489,7 +489,7 @@ static int check_tracking_branch(struct remote *remote, void *cb_data)\n \tmemset(&query, 0, sizeof(struct refspec_item));\n \tquery.dst = tracking_branch;\n \tres = !remote_find_tracking(remote, &query);\n-\tfree(query.src);\n+\tfree(query.src_storage);\n \treturn res;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 06b60867f5..00a67c99ef 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -443,7 +443,7 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \n \tfor (i = 0; i < rs->nr; i++) {\n \t\tstruct strbuf new_dst = STRBUF_INIT;\n-\t\tchar *old_dst;\n+\t\tconst char *old_dst;\n \t\tconst char *sub = NULL;\n \n \t\tif (rs->items[i].negative)\n@@ -454,8 +454,8 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \t\t\t\t ref_namespace[NAMESPACE_TAGS].ref))) {\n \t\t\tint j;\n \n-\t\t\tfree(rs->items[i].src);\n-\t\t\tfree(rs->items[i].dst);\n+\t\t\tfree(rs->items[i].src_storage);\n+\t\t\tfree(rs->items[i].dst_storage);\n \n \t\t\tfor (j = i + 1; j < rs->nr; j++) {\n \t\t\t\trs->items[j - 1] = rs->items[j];\n@@ -481,7 +481,7 @@ static void filter_prefetch_refspec(struct refspec *rs)\n \t\trs->items[i].dst = strbuf_detach(&new_dst, NULL);\n \t\trs->items[i].force = 1;\n \n-\t\tfree(old_dst);\n+\t\tfree(rs->items[i].dst_storage);\n \t}\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex b44f580b8c..87b97f5ef1 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -496,8 +496,8 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat\n \tstruct refspec_item refspec = {\n \t\t.force = 0,\n \t\t.pattern = 1,\n-\t\t.src = (char *) \"refs/heads/*\",\n-\t\t.dst = (char *) \"refs/heads/*\",\n+\t\t.src = \"refs/heads/*\",\n+\t\t.dst = \"refs/heads/*\",\n \t};\n \n \tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n@@ -981,7 +981,7 @@ static int append_ref_to_tracked_list(const char *refname,\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = refname;\n \tif (!remote_find_tracking(states->remote, &refspec))\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \ndiff --git a/checkout.c b/checkout.c\nindex cfaea4bd10..327ab5f6e3 100644\n--- a/checkout.c\n+++ b/checkout.c\n@@ -8,7 +8,7 @@\n #include \"strbuf.h\"\n \n struct tracking_name_data {\n-\t/* const */ char *src_ref;\n+\tconst char *src_ref;\n \tchar *dst_ref;\n \tstruct object_id *dst_oid;\n \tint num_matches;\n@@ -27,7 +27,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data)\n \tquery.src = cb->src_ref;\n \tif (remote_find_tracking(remote, &query) ||\n \t    repo_get_oid(the_repository, query.dst, cb->dst_oid)) {\n-\t\tfree(query.dst);\n+\t\tfree(query.dst_storage);\n \t\treturn 0;\n \t}\n \tcb->num_matches++;\n@@ -38,10 +38,10 @@ static int check_tracking_name(struct remote *remote, void *cb_data)\n \t\tcb->default_dst_oid = dst;\n \t}\n \tif (cb->dst_ref) {\n-\t\tfree(query.dst);\n+\t\tfree(query.dst_storage);\n \t\treturn 0;\n \t}\n-\tcb->dst_ref = query.dst;\n+\tcb->dst_ref = query.dst_storage;\n \treturn 0;\n }\n \n@@ -50,14 +50,15 @@ char *unique_tracking_name(const char *name, struct object_id *oid,\n {\n \tstruct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT;\n \tconst char *default_remote = NULL;\n+\tchar *src_ref;\n \tif (!git_config_get_string_tmp(\"checkout.defaultremote\", &default_remote))\n \t\tcb_data.default_remote = default_remote;\n-\tcb_data.src_ref = xstrfmt(\"refs/heads/%s\", name);\n+\tcb_data.src_ref = src_ref = xstrfmt(\"refs/heads/%s\", name);\n \tcb_data.dst_oid = oid;\n \tfor_each_remote(check_tracking_name, &cb_data);\n \tif (dwim_remotes_matched)\n \t\t*dwim_remotes_matched = cb_data.num_matches;\n-\tfree(cb_data.src_ref);\n+\tfree(src_ref);\n \tif (cb_data.num_matches == 1) {\n \t\tfree(cb_data.default_dst_ref);\n \t\tfree(cb_data.default_dst_oid);\ndiff --git a/refspec.c b/refspec.c\nindex 1df5de6c2f..a7473c9628 100644\n--- a/refspec.c\n+++ b/refspec.c\n@@ -163,8 +163,10 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,\n \n void refspec_item_clear(struct refspec_item *item)\n {\n-\tFREE_AND_NULL(item->src);\n-\tFREE_AND_NULL(item->dst);\n+\tFREE_AND_NULL(item->src_storage);\n+\titem->src = NULL;\n+\tFREE_AND_NULL(item->dst_storage);\n+\titem->dst = NULL;\n \titem->force = 0;\n \titem->pattern = 0;\n \titem->matching = 0;\ndiff --git a/refspec.h b/refspec.h\nindex 754be45cee..fbc4352397 100644\n--- a/refspec.h\n+++ b/refspec.h\n@@ -24,8 +24,10 @@ struct refspec_item {\n \tunsigned exact_sha1 : 1;\n \tunsigned negative : 1;\n \n-\tchar *src;\n-\tchar *dst;\n+\tconst char *src;\n+\tconst char *dst;\n+\tchar *src_storage;\n+\tchar *dst_storage;\n };\n \n #define REFSPEC_FETCH 1\ndiff --git a/remote.c b/remote.c\nindex d319f28757..d1511ecf83 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -967,7 +967,7 @@ static void query_refspecs_multiple(struct refspec *rs,\n \t\tconst char *key = find_src ? refspec->dst : refspec->src;\n \t\tconst char *value = find_src ? refspec->src : refspec->dst;\n \t\tconst char *needle = find_src ? query->dst : query->src;\n-\t\tchar **result = find_src ? &query->src : &query->dst;\n+\t\tchar **result = find_src ? &query->src_storage : &query->dst_storage;\n \n \t\tif (!refspec->dst || refspec->negative)\n \t\t\tcontinue;\n@@ -985,7 +985,8 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)\n \tint i;\n \tint find_src = !query->src;\n \tconst char *needle = find_src ? query->dst : query->src;\n-\tchar **result = find_src ? &query->src : &query->dst;\n+\tchar **result = find_src ? &query->src_storage : &query->dst_storage;\n+\tconst char **result_const = find_src ? &query->src : &query->dst;\n \n \tif (find_src && !query->dst)\n \t\tBUG(\"query_refspecs: need either src or dst\");\n@@ -1002,11 +1003,12 @@ int query_refspecs(struct refspec *rs, struct refspec_item *query)\n \t\t\tcontinue;\n \t\tif (refspec->pattern) {\n \t\t\tif (match_name_with_pattern(key, needle, value, result)) {\n+\t\t\t\t*result_const = *result;\n \t\t\t\tquery->force = refspec->force;\n \t\t\t\treturn 0;\n \t\t\t}\n \t\t} else if (!strcmp(needle, key)) {\n-\t\t\t*result = xstrdup(value);\n+\t\t\t*result_const = *result = xstrdup(value);\n \t\t\tquery->force = refspec->force;\n \t\t\treturn 0;\n \t\t}\n@@ -1019,12 +1021,12 @@ char *apply_refspecs(struct refspec *rs, const char *name)\n \tstruct refspec_item query;\n \n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.src = (char *)name;\n+\tquery.src = name;\n \n \tif (query_refspecs(rs, &query))\n \t\treturn NULL;\n \n-\treturn query.dst;\n+\treturn query.dst_storage;\n }\n \n int remote_find_tracking(struct remote *remote, struct refspec_item *refspec)\ndiff --git a/transport.c b/transport.c\nindex 83ddea8fbc..0cedda425c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -550,7 +550,7 @@ static void update_one_tracking_ref(struct remote *remote, char *refname,\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n \t\t\t\t\t\"update by push\", rs.dst, new_oid,\n \t\t\t\t\tNULL, 0, 0);\n-\t\tfree(rs.dst);\n+\t\tfree(rs.dst_storage);\n \t}\n }\n \n"}]}